From b5bf9a12f394a4360b5ed69302c2de28fa5ca341 Mon Sep 17 00:00:00 2001 From: Alejandro Villanueva Date: Mon, 18 Apr 2022 10:20:21 -0500 Subject: [PATCH] Pfg 478 prebid 6 (#134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Colossus Bid Adapter: fix validator (#7926) * 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 Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Mykhailo Yaremchuk * Integration examples: update the AMP creative (#7911) * fixing the AMP creative There's no way the original version of this ever worked. AMP requires calling out to Prebid Cache. The original example was calling for Prebid.js, which isn't present in an AMP environment. I just copied the instructions at https://docs.prebid.org/adops/setting-up-prebid-for-amp-in-dfp.html * added quotes * Generic viewability module: add new module (#7643) * - custom viewability core * - IntersectionObserver implementation * - support different type of trackers * - viewability tests wip * - increase test coverage for viewability * - move viewability module js * - remove uneccesary changes * - allow uncomplete observers to be registered again * - import explicitly from utils * - add viewability.md * - add module name to log messages * update for higher legibility * change maintainer email (#7930) * Compass Adapter: fix validator (#7931) * add Compass Adapter * fix * fix * fix * add endpointId * fix * RTD module: allow submodules to setBidRequestData without `waitForIt` (#7862) This change allows all RTD submodules at least one shot to see the `setBidRequestData` hook even if they are not flagged `waitForIt` (previously they would not be run at all in some cases). Addresses https://github.com/prebid/Prebid.js/issues/7117 * Pilotx Bid Adapter: add new bid adapter (#7816) * adding pilotx bid adapter and tests * removing commented code * appending review changes Co-authored-by: AnthonyBoozan * handle weird advertiserDomain scenarios (#7908) * Gnet Bid Adapter: Change endpoint and add parameter (#7934) * Add files via upload * Add files via upload * Change params on gnetBidder * ADJ - Use parseSizesInput to get sizes ADJ - Check serverResponse object ADJ - Remove getUserSyncs function * ADJ - Change prebid endpoint * ADJ - Change endpoint on test * GnetBidAdapter update for Prebid 5.x * Change endpoint and add parameter * import only used function from utils Co-authored-by: Roberto Hsu Wu Co-authored-by: Roberto Hsu Co-authored-by: Denise Balesteros Silva * JwPlayer RTD module: Write to oRTB content segments (#7886) * adds example code * enriches the ortb2 object * resolves build errors * tests content id and data getters * respects data integrity * updates documentation * Prebid 6.7 Release * Increment pre version * Prebid core & currency module: fix race condition on slow fetch of currency conversion file (#7851) The currency module hooks into the auction's `addBidResponse` flow to modify bid responses with currency info. To do this it needs to fetch conversion rates from the network, but the auction expects `addBidResponse` to complete synchronously, and things break down if all bidder network calls complete before the currency file fetch. This fix allows the `addBidResponse` hook to return a promise. If it does, the auction will wait for it before ending. Addresses https://github.com/prebid/Prebid.js/issues/7765 * Brandmetrics RTD Module: add new RTD module (#7756) * First version of brandmetrics RTD- module * Implement brandmetricsRtdProvider * Add gdpr and usp consent- checks * Add user- consent related tests * Set targeting keys in a more generic way * Test- logic updates * Criteo - Add schain support (#7940) * updates for Prebid v5 (#7878) * Adagio: remove referrer. reachedTop validation (#7939) * Gumgum - ADTS-175 Support multiple GG params (#7932) * Expose vendor ID for TCFv2 enforcement (#7927) Co-authored-by: slimkrazy * displayio bid adapter, tests, doc (#7906) Co-authored-by: Anna Philippova * Core: fix pbjs.validateAdUnitPos() to handle 0 value (#7944) Ortb2 spec uses the value `0` for "unknow" position. * Sovrn Bid Adaptter: add video to Sovrn adapter (#7929) * Add video to Sovrn adapter * Update sovrnBidAdapter.js * Update sovrnBidAdapter.md * Update sovrnBidAdapter.md Co-authored-by: Maxim Pakhomov <70204615+maximpakhomov@users.noreply.github.com> * Undertone bid adapter: support for floors module (#7914) * * Update undertone adapter - change parameters - placementId parameter is now optional and not mandatory - undertoneBidAdapter.js * Updated undertone bid adapter tests accordingly - undertoneBidAdapter_spec.js * * Update undertone adapter - change parameters - placementId parameter is now optional and not mandatory - undertoneBidAdapter.js * Updated undertone bid adapter tests accordingly - undertoneBidAdapter_spec.js * fix lint issue in undertone adapter spec * added user sync function to undertone adapter * * Update undertone adapter - change parameters - placementId parameter is now optional and not mandatory - undertoneBidAdapter.js * Updated undertone bid adapter tests accordingly - undertoneBidAdapter_spec.js * added user sync function to undertone adapter * added user sync function to undertone adapter * revert package-lock.json * added user sync function to undertone adapter * Update undertoneBidAdapter.js * Update browsers.json * bidfloor support * package-lock.json master * bidfloor support Co-authored-by: omerko Co-authored-by: Omer Koren Co-authored-by: AnnaPerion Co-authored-by: Oran Hollaender Co-authored-by: tamirnPerion <44399211+tamirnPerion@users.noreply.github.com> * InteractiveOffers - New fields and remap on the internal object (#7946) * appnexus bid adapter - add support for auction level keywords (#7951) * Prebid core: accept and propagate AD_RENDER_FAILED / AD_RENDER_SUCCEEDED events from cross-origin ads (#7917) * Prebid core: accept and propagate AD_RENDER_FAILED / AD_RENDER_SUCCEEDED events from cross-origin ads This adds a new type of cross-origin message ('Prebid Event') to allow PUC and other cross-origin renderings to correctly report rendering result, and generates the appropriate events. Addresses https://github.com/prebid/Prebid.js/issues/7702 Related PUC changes: https://github.com/prebid/prebid-universal-creative/pull/152 Documentation changes TBD * Emit AD_RENDER_FAILED on x-origin communication errors * Do not consider bid won if x-origin render fails * Cleanup (address https://github.com/prebid/Prebid.js/pull/7917#pullrequestreview-856914369) * Prebid 6.8.0 Release * Increment pre version * LunamediaHB Bid Adapter: add user syncs (#7950) * initial * initial * initial * Update lunamediahbBidAdapter.js Define traffic type by mediatype property * Update lunamediahbBidAdapter.md Remove unnecessary parameter * Update lunamediahbBidAdapter_spec.js Test for usage of mediatype for traffic definition * Update lunamediahbBidAdapter.md * Update lunamediahbBidAdapter.js fix native parse * Update lunamediahbBidAdapter_spec.js * rever native changes * remove traffic param * remove traffic param * remove traffic param * add video context handling * add video context handling * add userSync * fix * fix Co-authored-by: Aiholkin * Improve Digital COPPA support (#7948) Co-authored-by: Samiul Amin Shanto <93644987+samiul-shanto@users.noreply.github.com> * pass along the order which bidders were called (#7947) * feat: add aseal bid adapter and test (#7937) * Richaudience Bid Adapter: uncaught error on non-gdpr locations #7955 (#7962) Co-authored-by: sgimenez * 'Ogury Bid Adapter: adding location to bid timeout tracks (#7961) * Weborama RTD submodule: add support to more bid adapters (#7912) * update .submodules.json to include weborama rtd update .submodules.json to include weborama rtd submodule * add support to pubmatic * improve test format * improve test format ~ * add support to appnexus/xandr bidder * fix issue when split set target in two steps: site and user centric data * add support to rubicon old style * fix code, update docs * add support to global and bid ortb2 * remove unused code * refactor code * update bidder list * add global level parameters * change error to warning * update doc * refactor * add field accountId * update example * refactor js concat string * refactor js concat string 2 * update jsdoc * correct jsdoc * add support to pubmatic * improve test format * improve test format ~ * add support to appnexus/xandr bidder * fix issue when split set target in two steps: site and user centric data * add support to rubicon old style * fix code, update docs * add support to global and bid ortb2 * remove unused code * refactor code * update bidder list * add global level parameters * change error to warning * update doc * refactor * add field accountId * update example * refactor js concat string * refactor js concat string 2 * update jsdoc * correct jsdoc * fix log * update example * remove todo * refactor duplicated code in config normalization * fix jsdoc * add onData callback * add unit tests and update docs * fix doc * protect onData callback and refactor code * add new fields * fix condition * Prebid Core: refactor bidderSettings to have only one entry point (#7712) * Refactor bidderSettings to have only one entry point Add thin wrapper around `$$PREBID_GLOBAL$$.bidderSettings` that reduces some duplicate logic around fallback to `bidderSettings.standard` * Do not return direct reference to a global array * Fix bug where result of auction.js/getPriceByGranularity would ignore per-bid granularity after the first call * Remove redundant utils.deepMerge, move utils unit tests * Attempt to trick codacy into being a little less helpful * Use class syntax * Remove caching of standard adServerTargeting * Convert recent 'allowZeroCpmBids' to new bidderSettings api * ogury-adapter: fix some check conditions - US/non-GDPR issue (#7967) * Conversant adapter support for first party data through the ortb2 and ortb2Imp options (#7966) * Prebid core: accept MessageChannel communication from cross-origin creatives (#7953) Accept MessageChannel ports from cross-origin creatives to allow communication with frames behind opaque origins Addresses https://github.com/prebid/Prebid.js/issues/7870 * PBjs Core - Adloader : add brandmetrics module to approved external js- loaders (#7949) * First version of brandmetrics RTD- module * Implement brandmetricsRtdProvider * Add gdpr and usp consent- checks * Add user- consent related tests * Set targeting keys in a more generic way * Test- logic updates * Add brandmetrics to approved external js- loaders * Rise Bid Adapter: support onbid won (#7958) * 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 * add onBidWon event * nurl * add test * eslint * fix corcle * fix circle * fix Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur * Prebid 6.9.0 Release * Increment pre version * Hadron Id Submodule: initial release & deprecate Halo Id submodule (#7938) * rename audigent halo to hadron * revert package-lock changes * re-add halo modules for continuity til Prebid 7 deprecation * revert SSP adapter changes, add halo RTD test * remove hadron refs from ssp tests * add contact blurb * pull hadron from ix bid adapter * pull hadron from tappx test * re-add halo id system spec * Trustpid User ID Module: initial release (#7945) * feature: Add trustpid user id module * refactor: Update trustpidSystem module, comments and md file * refactor: Update trustpidSystem file to handle domain setting regardless of message events * refactor: Update trustpidSystem module and dependent tests and docs * refactor: Update trustpid undefined checks and returns * tests: Update trustpid tests * docs: Update trustpid docs typo * refactor: Update trustpid module with storage and logging utils. Adjust tests. Co-authored-by: Tomasz Januszek * Update malltvBidAdapter.md (#7973) Changing maintainer of bid adapter * YieldOne Bid Adapter: add IMID support. (#7982) * PubMatic Bid Adapter : Added support for considering video params from mediaTypes object of Bid (#7980) * Changed net revenue to True * Checking for video params in mediaTypes obj of bid * Added addiotional check for mediaTypes * Added test cases Co-authored-by: Azhar * Multiple Adapters & Modules: replace corejs polyfills with simpler stubs (#7942) * Replace core-js polyfills with simpler stubs * Update allowedModules / validateImports * Move stubs to their own package to keep npm imports working without changes * Vibrant Media Bid Adapter: add new bid adapter (#7824) * Fluct Bid Adapter: ie polyfill 4 url search params * more specific import * see if corejs is white listed * update to pure core js * update link * change format * add to whitelist * fix path to web * add features path * update path * drop web in path * fix path * try again * update to root * Preliminary draft of Vibrant Prebid Adapter * Refactoring to conform to Prebid.js data structures; Added "bid timeout" and "bid won" handlers * Added prebid server response parser; Improved request checks; Improved test data in readme; Added URL to data passed to the server; Added tests * Improved handling of URL sent to the server * Removed getUserSyncs function (not needed yet); Added support for sizes property outside mediaTypes object; Added mediaSizes property support for video mdias type; Corrected readme ad unit and request examples; * Added support for native; Added native tests; Cleaned up TODOs and logging * Corrected prebid service URL and default currency; Bid request transformer now more resilient to different request formats; Added native ad unit to example prebid service request in the readme; Added and improved unit tests * Removed overly-restrictive ad unit size requirements * Updated adapter to allow a list of bids; Coding standards improvements * Major changes to reflect the server now returning data in largely the Prebid structure; Simplified augmenting ad bids with additional required data. (Four tests fail; video renderer still a work in progress) * Updated render (still work in progress); Fixed failing tests * Corrected the prebid endpoint * Removed unnecessary custom video renderer * Added 'test-' to start of test ad unit codes * Tweaked example in readme * Removed debug lines and data; Code clean-up * Improved description and corrected contact email in readme * Updated example ad requests in the readme * Final version of readme example; Improved bid request transform in adapter * Resolved issues with code style and tests; Reverted package-lock * Fixed missing comma in allowed modules array * Fixed failing tests * Resolved issues with code style and tests; Reverted package-lock * Fix: Bidder code does not match adapter name * Added USP consent support * Changes to clarify company name Co-authored-by: Chris Huie * sspBC Bid Adapter: improvee native support , matching site/placement, & other maintenance (#7963) * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * [sspbc-adapter] fix notification payload * [sspbc-adapter] fix notification payload, fix tests * [sspbc-adapter] add userIds to ortb request * [sspbc-adapter] update to 4.1, change request to be ortb 2.6 compliant * [sspbc-adapter] update tests Co-authored-by: Wojciech Biały * Bump log4js from 6.3.0 to 6.4.1 (#7988) Bumps [log4js](https://github.com/log4js-node/log4js-node) from 6.3.0 to 6.4.1. - [Release notes](https://github.com/log4js-node/log4js-node/releases) - [Changelog](https://github.com/log4js-node/log4js-node/blob/master/CHANGELOG.md) - [Commits](https://github.com/log4js-node/log4js-node/compare/v6.3.0...v6.4.1) --- updated-dependencies: - dependency-name: log4js dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump shelljs from 0.8.4 to 0.8.5 (#7987) Bumps [shelljs](https://github.com/shelljs/shelljs) from 0.8.4 to 0.8.5. - [Release notes](https://github.com/shelljs/shelljs/releases) - [Changelog](https://github.com/shelljs/shelljs/blob/master/CHANGELOG.md) - [Commits](https://github.com/shelljs/shelljs/compare/v0.8.4...v0.8.5) --- updated-dependencies: - dependency-name: shelljs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump trim-off-newlines from 1.0.1 to 1.0.3 (#7986) Bumps [trim-off-newlines](https://github.com/stevemao/trim-off-newlines) from 1.0.1 to 1.0.3. - [Release notes](https://github.com/stevemao/trim-off-newlines/releases) - [Commits](https://github.com/stevemao/trim-off-newlines/compare/v1.0.1...v1.0.3) --- updated-dependencies: - dependency-name: trim-off-newlines dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump cached-path-relative from 1.0.2 to 1.1.0 (#7979) Bumps [cached-path-relative](https://github.com/ashaffer/cached-path-relative) from 1.0.2 to 1.1.0. - [Release notes](https://github.com/ashaffer/cached-path-relative/releases) - [Commits](https://github.com/ashaffer/cached-path-relative/commits) --- updated-dependencies: - dependency-name: cached-path-relative dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update nextMillenniumBidAdapter.js (#7972) Fixed an error that occurred if one of the intermediate objects response.body.ext.responsetimemillis is missing * DSPx adapter: add pbver, pref, mediatypes, pcode (#7964) Co-authored-by: Alexander * add a configurable "bidCacheFilterFunction" (#7993) * add a configurable "bidCacheFilterFunction" to determine whether to use a cached bid * tiny != changed to !== Co-authored-by: Eric Harper * [SPY-10745] fixing value for 'at' ad request field for first price model (#7996) * Mediasquare Adapter - add gvlid (#7970) * PBS Adapter: allow "extra" bids that are not tied to any request (#7885) * Explicitly import / setup PBS adapter test dependencies * Allow "unknown" bidders inside PBS adapter * Remove code duplication around processNativeAdUnitParams * Add transactionId to bid response; make requestId optional * Remove call to bidFactory#createBid from Telaria bid adapter * Auction index * Convert bid response validaton to use auctionIndex * WIP: allow auction to work with optional bid requests (missing - fix pbjs_api_spec.js) * Allow auction to work with optional requestId * Remove getBidderRequest * Mass adapter * Fix priceFloors for optional requestId * Add s2sConfig.allowUnknownBidderCodes toggle * Fix lint * Fix lint * Fix use of auction.getProperties (use getAuctionStart instead) * Fix lint * Fix LGTM alert * Adnuntius Bid Adapter: update on no cookies parameter (#7875) * Added option to pass a user id through ortb2. * RTD provider added. * check if use cookie is present in config, and use it accordingly. * Adding test for no cookie url addition. * Adnuntius RTD Provider tests added * Adnuntius RTD Module * Removed adnuntius RTD provider from this pull request. * Fix error * Adnuntius rtd provider: initial release (#7902) * Added option to pass a user id through ortb2. * RTD provider added. * Adnuntius RTD Provider tests added * Adnuntius Rtd Provider * name change for file. * Removed test for other branch. * Fix to TZO issue in test. * Addressing changes from comments in pull request 7902. * Fixed test issue. * Fixed mergeConfig functions * Prebid core: isolate global and bidder-specific configuration (#7991) * Prebid core: isolate global and bidder-specific configuration https://github.com/prebid/Prebid.js/issues/7956 * Update mergeDeep to not copy arrays by reference * Malltv bid adapter: added gdpr applies and gdpr consent to bid request parameters (#8000) * Update malltvBidAdapter.md Changing maintainer of bid adapter * Added gdprApplies and gpdrConsent to bid request parameters Added gdprApplies and gpdrConsent to bid request parameters * Check for gdprConsent object * Updated gdpr * TheMediaGrid: RTD module support with mergeDeep of user.data (#7813) * Added TheMediaGridNM Bid Adapter * Updated required params for TheMediaGridNM Bid Adapter * Update TheMediGridNM Bid Adapter * Fix tests for TheMediaGridNM Bid Adapter * Fixes after review for TheMediaGridNM Bid Adapter * Add support of multi-format in TheMediaGrid Bid Adapter * Update sync url for grid and gridNM Bid Adapters * TheMediaGrid Bid Adapter: added keywords adUnit parameter * Update TheMediaGrid Bid Adapter to support keywords from config * Implement new request format for TheMediaGrid Bid Adapter * Fix jwpseg params for TheMediaGrid Bid Adapter * Update unit tests for The Media Grid Bid Adapter * Fix typo in TheMediaGrid Bid Adapter * Added test for jwTargeting in TheMediaGrid Bid Adapter * The new request format was made by default in TheMediaGrid Bid Adapter * Update userId format in ad request for TheMediaGrid Bid Adapter * Added bidFloor parameter for TheMediaGrid Bid Adapter * Fix for review TheMediaGrid Bid Adapter * Support floorModule in TheMediaGrid Bid Adapter * Fix empty bidfloor for TheMediaGrid Bid Adapter * Some change to restart autotests * Fix userIds format for TheMediaGrid Bid Adapter * Remove digitrust userId from TheMediaGrid Bid Adapter * Protocols was added in video section in ad request for TheMediaGrid Bid Adapter * TheMediaGrid: fix trouble with alias using * TheMediaGridNM: fix trouble with alias * TheMediaGrid Bid Adapter: added support of PBAdSlot module * TheMediaGrid Bid Adapter: fix typo * GridNM Bid Adapter: use absent in params data from mediaTypes * GridNM Bid Adapter: fix md file + add advertiserDomains support * TheMediaGrid and gridNM Bid Adapter: minor netRevenue fixes * gridNM Bid Adapter updates after review * TheMediaGrid Bid Adapter: fix keywords workflow * fix testing and kick off lgtm again * TheMediaGrid: added ext.bidder.grid.demandSource processing * TheMediaGrid: added user.id from fpd cookie * TheMediaGrid: control cookie setting via bidder config * TheMediaGrid: use localStorage instead cookie * TheMediaGridNM Bid Adapter: update adapter to use /hbjson endpoint * TheMediaGridNM: fix unnecessary conditions * TheMediaGrid: fix bug with nurl field in response * TheMediaGrid: update test * TheMediaGridNM: fix possible bug with nurl * TheMediaGrid: added alias as playwire * TheMediaGrid: added alias as adlivetech * TheMediaGrid: added Ftrack Rtd module support * TheMediaGrid: use mergeDeep instead concat * TheMediaGrid: support ortb2.user.ext.device from bidderConfig as user.ext.device in bid request Co-authored-by: Chris Huie * fix: Add back in correct logic for purpose 1 consent check (#8007) * fix:Add back in correct logic for purpose 1 consent check * Remove redundant type coercion. Co-authored-by: slimkrazy * Prebid 6.10.0 Release * Increment pre version * Revert "Bump log4js from 6.3.0 to 6.4.1 (#7988)" (#8010) This reverts commit ef137775a99f3ebfee9c492b9a8f2d8ba33b5f84. * RTBHouse Bid Adapter: add global vendor list id (#8002) * AdYouLike Bid Adapter: add gvlid (#8005) * 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 * Mediasquare Bid Adapter: add floor module support (#8009) * remove old userSyncs method * Update mediasquareBidAdapter.js * Update mediasquareBidAdapter.js * Update mediasquareBidAdapter_spec.js * Mediasquare Bid Adapter: add floor module support * Prebid Src: pass requestObject to BEFORE_BIDDER_REQUEST event (#7989) * pass requestObject to BEFORE_BIDDER_REQUEST event * kick off circleci tests Co-authored-by: Chris Huie * adot Bid Adapter: upgrade to v6 (#7974) * [adot] upgrade adapter to v6 Co-authored-by: Alexandre Lorin Co-authored-by: Corentin Verpillat * fix `buildRequests` test * delete superfluous trailing arguments * fix navigator language * fix coverage * fix bidder code and undefined sizes Co-authored-by: ramyferjaniadot <90328697+ramyferjaniadot@users.noreply.github.com> Co-authored-by: Alexandre Lorin * IX Bid Adapter: added trustpid.com support and removed a few user providers (#8012) * added trustpid.com support and removed few user providers in diagnostics * small syntax fix * Fixed Test after removing lotamePanoramaId in diag in IX adapter * IQzone Bid Adapter: add new param (#8020) * add IQZone adapter * add endpointId param * Kubient Bid Adapter: support coppa, update sync URL (#7855) * add uspConsent to sunc URL * kubient: add coppa * added test for coppa * fix-test * replace kdmp.kbntx.ch by matching.kubient.net remove iframe pixel support rename us_privacy -> usp rename consent_str -> consent use URLSearchParams for GET params * fix-test-and-code * removed URLSearchParams * early exit for getUserSyncs, updated test * test-back * test-filter-settings * test-filter-settings * test-filter-settings * test-filter-settings * fix for review * fix for review * fix test for ci passing * fix 2 for ci passing * refactored-test-for-ci * fix-for-review * fix * fix Co-authored-by: Artem Aleksashkin Co-authored-by: Artem Aleksashkin * Fix renderer in rubiconBidAdapter (#8017) * fix(priceFloors): retrieve matching bid request (#8025) * Price floors module: expand on bug fix with custom priceFloor schemas tests (#8027) * fix(priceFloors): retrieve matching bid request * Make default fieldMatchingFunctions work without request; add unit tests * Make default fieldMatchingFunctions work without request; add unit tests Co-authored-by: Julie * VidoomyAdapter: add schain and bidfloor to vidoomy adapter (#7965) * feat: add schain and bidfloor to vidoomy adapter * doc: update vidoomy docs * fix: add bidfloor validation * chore: update docs and tests * Revert "Multiple Adapters & Modules: replace corejs polyfills with simpler stubs (#7942)" (#8028) This reverts commit e75daddef6f325df8c5bb5d585f22a3dd99432a4. * Fix `geo` first party data (#8013) that should not be attached to `device` but to `user` litteral Co-authored-by: François Maturel * add bidder fluct (#8016) * RichaudienceBidAdapter: Change currency floor module (#8018) * RichaudienceBidAdapter: Change currency floor module * New Atempt * new Atempt * new Atempt * remove single quotes Co-authored-by: sgimenez * Adkernel Bid Adapter: add motionspots alias (#8034) * Prebid.js 6.11.0 release * Vidazoo bid adapter: support schain param (#8045) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): padding 'schain' param to the server Co-authored-by: roman * 6.12.0-pre * Prebid Core: add filename to pbjsGlobals module append (#7969) * fix: add filename to pbjsGlobals module append * lint * update filename * Novatiq ID module: sharedID changes (#7994) * Novatiq snowflake userId submodule Novatiq snowflake userId submodule initial release * change request updates added novatiq info /modules/userId/userId.md added novatiq info /modules/userId/eids.md added novatiq eids /modules/userId/eids.js added novatiq module in /modules/.submodules.json removed unnecessary value from getId response * Update novatiqIdSystem_spec.js removed unnecessary srcid value * Update novatiqIdSystem.md Novatiq ID System: updated novatiq snowflake ID description * use the sharedId if available and configured * updated docs * test changes * defensive code not required * Use the prebid storage manager instead of using native functions * doc changes * trailing spaces Co-authored-by: novatiq <79258366+novatiq@users.noreply.github.com> * Kubient Bid Adapter: update if bidfloor is zero (#8008) * add uspConsent to sunc URL * kubient: add coppa * added test for coppa * fix-test * replace kdmp.kbntx.ch by matching.kubient.net remove iframe pixel support rename us_privacy -> usp rename consent_str -> consent use URLSearchParams for GET params * fix-test-and-code * removed URLSearchParams * early exit for getUserSyncs, updated test * test-back * test-filter-settings * test-filter-settings * test-filter-settings * test-filter-settings * fix for review * fix for review * fix test for ci passing * fix 2 for ci passing * refactored-test-for-ci * fix-for-review * fix * fix * avoid sending zero bid floor if it is not defined * do not send 0 bidfloor Co-authored-by: Artem Aleksashkin Co-authored-by: Artem Aleksashkin * Automatad Bid Adapter: register on timeout event and endpoint changes (#8030) * added automatad bid adapter * added automatad bid adapter readme * added automatad bidder adapter unit test * updated maintainer email id for automatad adapter * refactored automatadBidAdapter js * refactored automatadBidAdapter unit test * refactored automatadBidAdapter unit test * added usersync code to automatad bid adapter * Added unit test for onBidWon in automatadBidAdapter_spec * removed trailing spaces * removed trailing space * changes for getUserSync function * lint error fixes * updated usersync url * additional test for onBidWon function added * added ajax stub in test * updated winurl params * lint fixes * added adunitCode in bid request * added test for adunit code * add placement in impression object * added code to interpret multiple bid response in seatbid * added bid meta with advertiserDomains * endpoint url changes * added format changes * macro substitution change added * Nexx360 Bid Adapter: add new bid adapter (#8026) * nexx360 added * Update nexx360BidAdapter.js UserEids integrated * Update nexx360BidAdapter.js Indent fix * Build system: specify corejs version for babel (#8065) * sendFloors option to include floor data in the eventCategory field and include ad unit code in eventAction field (#8055) Co-authored-by: Rachel Joyce * collect user.eids (#8022) * TheMediaGrid Bid Adapter: support for Interstitial flag (#8039) * Added TheMediaGridNM Bid Adapter * Updated required params for TheMediaGridNM Bid Adapter * Update TheMediGridNM Bid Adapter * Fix tests for TheMediaGridNM Bid Adapter * Fixes after review for TheMediaGridNM Bid Adapter * Add support of multi-format in TheMediaGrid Bid Adapter * Update sync url for grid and gridNM Bid Adapters * TheMediaGrid Bid Adapter: added keywords adUnit parameter * Update TheMediaGrid Bid Adapter to support keywords from config * Implement new request format for TheMediaGrid Bid Adapter * Fix jwpseg params for TheMediaGrid Bid Adapter * Update unit tests for The Media Grid Bid Adapter * Fix typo in TheMediaGrid Bid Adapter * Added test for jwTargeting in TheMediaGrid Bid Adapter * The new request format was made by default in TheMediaGrid Bid Adapter * Update userId format in ad request for TheMediaGrid Bid Adapter * Added bidFloor parameter for TheMediaGrid Bid Adapter * Fix for review TheMediaGrid Bid Adapter * Support floorModule in TheMediaGrid Bid Adapter * Fix empty bidfloor for TheMediaGrid Bid Adapter * Some change to restart autotests * Fix userIds format for TheMediaGrid Bid Adapter * Remove digitrust userId from TheMediaGrid Bid Adapter * Protocols was added in video section in ad request for TheMediaGrid Bid Adapter * TheMediaGrid: fix trouble with alias using * TheMediaGridNM: fix trouble with alias * TheMediaGrid Bid Adapter: added support of PBAdSlot module * TheMediaGrid Bid Adapter: fix typo * GridNM Bid Adapter: use absent in params data from mediaTypes * GridNM Bid Adapter: fix md file + add advertiserDomains support * TheMediaGrid and gridNM Bid Adapter: minor netRevenue fixes * gridNM Bid Adapter updates after review * TheMediaGrid Bid Adapter: fix keywords workflow * fix testing and kick off lgtm again * TheMediaGrid: added ext.bidder.grid.demandSource processing * TheMediaGrid: added user.id from fpd cookie * TheMediaGrid: control cookie setting via bidder config * TheMediaGrid: use localStorage instead cookie * TheMediaGridNM Bid Adapter: update adapter to use /hbjson endpoint * TheMediaGridNM: fix unnecessary conditions * TheMediaGrid: fix bug with nurl field in response * TheMediaGrid: update test * TheMediaGridNM: fix possible bug with nurl * TheMediaGrid: added alias as playwire * TheMediaGrid: added alias as adlivetech * TheMediaGrid: fix sync url workflow * TrustX: fix sync url worflow + remove old syncurl * TheMediaGrid: added instl support * TheMediaGrid: fix test for instl Co-authored-by: Chris Huie * TrustX Bid Adapter: added instl support (#8042) * TrustX: added instl support * TrustX: fix test * Readme : update built files & add gulp-serve-and-test (#8070) * Readme : update built files & add gulp-serve-and-t Issue #8058 * fix typo * Just Id Userid System: add new ID module (#7985) * just id user module * fix * fix * docs fix * basic mode * fixes * fix * . * . Co-authored-by: pchrominski * CircleCI config: use "browsers" image (#8051) Change the CircleCI image to one that has Chrome to fix failing nightly e2e tests * Big-Richmedia Bid Adapter: initial release (#8033) * feature: add Hubvisor richmedia adapter * feature(hubvisor-bid-adapter): fix lint error * feature: Replay support for Hubvisor richmedia adapter * feature: do not need size 1800x1000 for skin * feature: rename hbvRichmediaAdapter to bigRichmediaAdapter (#7) * feature: add tests and documentation (#8) * Richmedia adapter : rename files (#9) Co-authored-by: Julie Co-authored-by: JulieLorin * BizzClick Bid Adapter: fix schain settings (#8075) * fix schain settings * update to single quotes for linting * remove extra blank line and fix double quote Co-authored-by: Chris Huie * RichaudienceBidAdapter: Add demand type Skin (#8061) * RichaudienceBidAdapter: Add demand type Skin * Test specs * Test specs 2 * test spec 3 * test spec 4 * test spec 5 Co-authored-by: sgimenez * LKQD Adapter Restore with Adomain (#8047) * LKQD: restore adapter, convert to oRTB, add adomain * LKQD: update schain with tests * LKQD: undo test setup modification * LKQD: remove unused vars * LKQD: only import needed from utils * Prebid core & PBS Adapter: debugging tools to intercept bid requests and mock their response (#7801) * Build system: improve build for development workflow This adds a "test-only" gulp task that runs only tests (without clean/lint) and makes the single-spec test environment consistent with the whole-suite case. * Build system: remove tests from 'serve-fast' task; add 'serve-and-test' task * Prebid core & PBS Adapter: debugging tools to intercept bid requests and mock their response This adds the ability to 'intercept' bid requests with a mock response. An intercepted bid does not go through the normal `buildRequests` -> network -> `interpretResponse` flow; a dummy response is instead direclty returned to the auction as if it originated from `interpretResponse`. This is mostly transparent to other parts of the stack (validation, events, etc), with the exception of user syncs - their interface requires the bidder's HTTP response, so they won't run if all bids in the request are intercepted (and thus no network activity happens). * Inject sessionStorage during testing - Firefox won't allow stubbing the real one * Move most debugging logic to its own module * Add `isDebug` flag to bid responses that have been modified by debugging code * Remove some redundancy * Correct merging oversight * IX Bid Adapter: GPID, dfp_ad_unit_code, pageUrl & bid renderer updates (#8059) * refactored GPID, dfp_ad_unit_code, pageUrl & bid renderer positions * resolved gpid for each imp with forEach loop * AdYouLike Bid Adapter: fix icon url issue for Native (#8078) * 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 * Prebid 6.12.0 release * Increment version to 6.13.0-pre * Wider image validation (#8019) * Limelight Digital Bid Adapter: Prevent duplicate iframe and pixel syncs (#8068) * User sync improvements * User sync improvements * Code review fixes Co-authored-by: apykhteyev * Relaido Bid Adapter: change to get the renderer URL for each response (#8085) * add relaido adapter * remove event listener * fixed UserSyncs and e.data * fix conflicts * supports for the case where playerUrl is defined inside ads Co-authored-by: ishigami_shingo Co-authored-by: cmertv-sishigami Co-authored-by: t_bun Co-authored-by: n.maeura * Sovrn Bid Adapter: updated param checks for video adUnits (#8087) * this will work better * rerun CI * zeta_global_sspBidAdapter: provide device.language (#8088) Co-authored-by: Surovenko Alexey * Prebid Core: Adding support for a global return of consent metadata. (#8043) * Adding support for a global return of consent metadata with light test coverage. * removing duplicate test * updates for code consistency, timestamp naming * move tcstring check down, return a zero if not a string * forgot util import! * TTD Bid Adapter: initial release (#8073) * add ttd prebid js adapter * add first party data support fix gpid add multiformat ad unit support remove unnecessary validation remove params (secure, categories, keywords) read bid mediatype from new bid response field * remove minduration as required param, default to 0 if missing * set video.protocols during initilization * increment bidder adapter version * Pubxai Bid Adapter: added extra field to the winning bid object. (#8095) * Added extra field to the winning bid object. * Lint isuues fixes Co-authored-by: Phaneendra Hegde * IQZone Bidder: add user sync (#8072) * add IQZone adapter * add endpointId param * add user sync * orbidderBidAdapter: add unit test for price floor module (#8044) * Update 33acrossBidAdapter.js * Update smaatoBidAdapter.js * zeta_global_sspBidAdapter: remove devicetype (#8098) Co-authored-by: Surovenko Alexey * Revert "Merge branch 'patmmccann-patch-1' into master" (#8103) This reverts commit bd2e9f1cb4c9dac10bfa85f5951083b79f5e2fa9, reversing changes made to 9c0975a937ea55e36a290092c55b7fd0d494fb95. * 33across, Smaato, OneVideo, & Sharethrough Bid Adapters: Add GVLID (#8104) * Update 33acrossBidAdapter.js * Update smaatoBidAdapter.js * Update sharethroughBidAdapter.js * Update oneVideoBidAdapter.js * Medianet Analytics Adapter: Multiple bidResponse with same requestId fix (#8069) Co-authored-by: monis.q * RhythmOne and Unruly Bid Adapter: add gvlid (#8109) * Update rhythmoneBidAdapter.js * Update unrulyBidAdapter.js * Marsmedia adapter: Remove bidderCode from Response (#8108) * Change publisherId to zoneId Add gdpr Add supply chain Add video media type * Remove comments * Fix unit test coverage * fix request id bug add vastXml to video response * Remove bid response default sizes * Change endpoint url * Add unit test for vastXml * Change end point * Remove trailing-space * Add onBidWon function * New adapter - videofy * Marsmedia & Videofy - Add onTimeout onSetTargeting * Create sendbeacon function * - add viewability * remove unnecessary utils.getWindowTop() * Remove bidderCode from response for alias use * Remove unuse that var * NextRoll Bid Adapter: update privacy link and icon (#8105) * Update privacy link and icon for NextRoll BidAdapter * update adapter spec * Criteo - Read mediaTypes.banner.sizes instead of bidRequest.Sizes (#8111) * Smaato: Do not overwrite site.publisher.id (#8112) Co-authored-by: Bernhard Pickenbrock * Prebid 6.13.0 release * Increment version to 6.14.0-pre * VIS.X: fix bug with onTimeout function arguments (#8110) Co-authored-by: Vladimir Fedoseev * Adriver id system (#8057) * initial commit * adriver id submodule add * add id system tests, fix adriver bid adapter tests * add new lines at the end of files * appnexus bid adapter - add support for pubProvidedId userId (#8015) * Prebid core: bidder-specific control over storage access via `bidderSettings.storageAllowed` (#7992) * Prebid core: bidder-specific control over storage access via `bidderSettings.allowStorage` https://github.com/prebid/Prebid.js/issues/7280 * Refactor signature of getStorageManager * Refactor calls to getStorageManager from bid adapters * Use bidderCode for StorageManager enforcement of bidderSettings.storageAllowed * Update adriver adapter to use new getStorageManager signature * appnexus bid adapter - convert keywords different for psp endpoint (#8115) * Rise Bid Adapter: Added support for banner & gpid (#8083) * 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 * starting to combine adUnits to a single request. Added banner media type * improved logic * handle multiple response bids. Updated sent params from buildRequests * removed the old build request function * fixed testMode logic * setting different params by the mediaType, if they're available * updated media format for adUnits (vastXml / html) * updated response structure and some request params * updated request and response structure and logic * fixed netRevenue param * syntax and naming fix * adapter syntax fix and tests partial update according to adapter's new logic * more tests updated. Small adapter tweaks * updated tests to match new request and response structures * fixed config on one of the tests * improved logic * separated query for video and banner keys * added nurl tests * added display tests * updated requests structure and tests video/display together * removed TODO * changes bids object params to camelCase * moved tmax to general params instead of bids. Updated params names in spec file * sending playbackMethod as a single int * sending playbackMethod as a single int * support gpid * changed the playbackMethodValue signal to let * changed vars signal order * moved placement_id key to bids object * changed placementId to camelCase * fixed issue where placementId tried to access wrong object * fixed floorPrice bug * moved currency and netRevenue keys to each bid object * cr * updated placementId value. Added condition to currency and netRevenue * Delete package-lock.json * added package-lock.json back * removed line from package.json * updated some self tests logic * empty commit Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur * Colossus Bidder: update user sync (#8050) * 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 Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Mykhailo Yaremchuk * Get floor from correct places on bid object & replace div ID with size (#8093) Co-authored-by: Rachel Joyce * Improve Digital adapter: custom creative renderer (#7975) * Feature Prebid First Party Data (#2) * HBT-166: Added PageCategory and Genre to request object from First-Party-Data * HBT-166: Added PageCategory and Genre to request object from First-Party-Data and Rewrite Test Cases * Update improvedigitalBidAdapter.js Version increased * Improve Digital COPPA support * Feature/razr integration (#3) Integrated RAZR rich creative renderer * Use renderer interface for Razr Co-authored-by: Faisal Islam <93644923+faisalvs@users.noreply.github.com> Co-authored-by: Samiul Amin Shanto <93644987+samiul-shanto@users.noreply.github.com> Co-authored-by: Faisal Islam * TheMediaGrid Bid Adapter: added support genre and cat from config ortb2.site (#8041) * TheMediaGrid: added support genre and cat from config ortb2.site * TheMediaGrid: fix bug with possible undefined cat or pagecat * Kargo Bid Adapter: Add currency support for bid response (#8134) * Kargo Bid Adapter: Use currency from Bid Response * Kargo Bid Adapter: Fix failed test Co-authored-by: Jeremy Sadwith * Minutemedia Bid Adapter: add new bid adapter (#8056) * 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 * minutemedia adapter * change adresses and mails * mm endpoints * add gvlid * fix * fix tests Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur * PBS adapter: partial support of sizeMapping (and sizeMappingV2) (#8084) * Convert sizeMappingV1 to work on adUnits (and not bid requests) * Convert sizemappingV2 to work on adUnits (and not bidRequests) * Warn about unsupported bidder-level sizeMapping configuration in PBS adapter * Kargo Bid Adapter: Removing Salesforce partner support (#8140) * Kargo Bid Adapter: Use currency from Bid Response * Kargo Bid Adapter: Fix failed test * Removing legacy partner code Co-authored-by: Wei Wong * Prebid 6.14.0 release * Increment version to 6.15.0-pre * New alias for Aniview prebid adapter (#8125) * Multiple adapters and modules: replace core-js polyfills with simpler stubs (#8136) * Replace core-js polyfills with simpler stubs * Update allowedModules / validateImports * Move stubs to their own package to keep npm imports working without changes * Move polyfills under src * GMOSSP Bid Adapte:Add user module, meta_url. (#8128) * GMOSSP Bid Adapte:Add user module, meta_url. * sharedid.id -> pubcid * un use import getWindowSelf. * test code fix. * test code url fix. * Update RELEASE_SCHEDULE.md (#8142) * Yahoossp Bid Adapter: enable aliasing (#8118) * YahooSSP bid adapter: Enable aliasing * Update text description in test case * Validate aliasing support by testing the bidder code propery is not being set by the bid adapter Co-authored-by: slimkrazy * added GVLId to goldbachBidAdapter (#8147) * Gnet Bid Adapter: onBidWon (#8114) * Add files via upload * Add files via upload * Change params on gnetBidder * ADJ - Use parseSizesInput to get sizes ADJ - Check serverResponse object ADJ - Remove getUserSyncs function * ADJ - Change prebid endpoint * ADJ - Change endpoint on test * GnetBidAdapter update for Prebid 5.x * Change endpoint and add parameter * import only used function from utils * NEW - onbidWon function * FIX - buildRequests test * FIX - buildRequests test * FIX - buildRequests test especific ambient error Co-authored-by: Roberto Hsu Wu Co-authored-by: Roberto Hsu Co-authored-by: Denise Balesteros Silva * Restructure data for global window variable. (#8152) (#8153) * Video and banner deal id (#8135) * Adrino Bid Adapter: add new native bid adapter (#8014) * Adrino: new native adapter * add simple check to onBidWon Co-authored-by: Tomasz Mielcarz * Revert "Adrino Bid Adapter: add new native bid adapter (#8014)" (#8155) This reverts commit 72895a3a5bd58966003bca4b98bdd166602c6c56. * Prebid core: add support for asynchronous access to consent data (#8071) This adds `gpdrDataHandler.promise` and `uspDataHandler.promise`, to enable access to USP/GDPR consent data from outside of an auction context (see use case: https://github.com/prebid/Prebid.js/pull/7803) * Adrino bid adapter: do not use core-js polyfills (#8156) * Revert "Revert "Adrino Bid Adapter: add new native bid adapter (#8014)" (#8155)" This reverts commit 3c7b3cdab7c4e6b64bdca1d31171ff07e6895bb2. * Update Adrino bid adapter to not use core-js polyfill * Bump url-parse from 1.5.1 to 1.5.9 (#8131) Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.1 to 1.5.9. - [Release notes](https://github.com/unshiftio/url-parse/releases) - [Commits](https://github.com/unshiftio/url-parse/compare/1.5.1...1.5.9) --- updated-dependencies: - dependency-name: url-parse dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Adloox Analytics/RTD: use refererInfo (#8092) * SmartHub Bid Adapter: initial release (#8064) * add SmartHub Prebid.js adapter * fix unit tests * fix test params * fix * Update smarthubBidAdapter.md add relevant partnerName * trigger circleci * fix response validation * add netRevenue validation to main condition Co-authored-by: maksym.voloshyn * Prebid Server Bid Adapter: add support for all imp parameters (#8159) * pass imp and fix test * fix lint * fix to mergeDeep * add instl test * remove overlapping testing * cwire Bid Adapter: Add new optional parameters (#8143) * add new global config params * move params from global config to bidder params * fix lint * fix wording, remove unused param * Prebid 6.15.0 release * Increment version to 6.16.0-pre * Add issue tracking workflow (#8162) This adds a GH action to automatically create a project item for every new issue, and update it with the issue creation date. * Move issue_tracker.yml to the correct folder * Novatiq ID System: allow configuration of the sync URL & allow callbacks for specific custom integrations (#8089) * Novatiq snowflake userId submodule Novatiq snowflake userId submodule initial release * change request updates added novatiq info /modules/userId/userId.md added novatiq info /modules/userId/eids.md added novatiq eids /modules/userId/eids.js added novatiq module in /modules/.submodules.json removed unnecessary value from getId response * Update novatiqIdSystem_spec.js removed unnecessary srcid value * Update novatiqIdSystem.md Novatiq ID System: updated novatiq snowflake ID description * use the sharedId if available and configured * updated docs * test changes * defensive code not required * Use the prebid storage manager instead of using native functions * doc changes * trailing spaces * Allow configuration of the sync URL and to allow callbacks for specific custom partner integrations * update documentation * attempt to fix firefox test timeout Co-authored-by: novatiq <79258366+novatiq@users.noreply.github.com> * Rubicon Bid Adapter: Pass along prebid bidId to exchange (#8169) * rubicon pass bidId in * remove console log * targetVideo Bid Adapter: Add GVLID (#8170) * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo Bid Adapter update * TargetVideo Bid Adapter implementing requested changes * targetVideo Bid Adapter: Add GVLID * Yahoo SSP Bid Adapter: remove unnecessary warning (#8174) * Insticator Bid Adapter: add support schain and eids (#8123) * InsticatorBidAdapter: add support schain and eids * rerun circle ci build * Revert "rerun circle ci build" This reverts commit 936d712012790f437d4ca0c8ad2255c5cd828d78. * Revert "Revert "rerun circle ci build"" This reverts commit f7c5a66a3276ffee7d4a1f6413a444bb4da69865. * Livewrapped adapter: Collect meta data and deal ids (#8176) * Collect metadata * Collect Deal Id * Prebid Core & Browsi RTD provider: Support Vendor Billing with Billable events (#8119) * 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 * initial check in * first stuff * Rubicon Passes along Billing Events * fix rubi test * update schema * fix rubi test * listen to billing events * merge browsi events * billable events * remove line * camelcase * lint fixes * unit test * unit test * missing imports Co-authored-by: robertrmartinez * Prebid Core and Several Bid Adapters: fix win notification price bug (#8171) * Update ttdBidAdapter.js * Update prebid.js * Update secureCreatives.js * Update apacdexBidAdapter.js * Update brightMountainMediaBidAdapter.js * Update axonixBidAdapter.js * Update koblerBidAdapter.js * Update mediaforceBidAdapter.js * Update ttdBidAdapter.js * Update apacdexBidAdapter.js * Update brightMountainMediaBidAdapter.js * Update ttdBidAdapter.js * Changed first party data (#8180) * AdYouLike bidder adapter: update video endpoint (#8166) * 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 * Adagio: getPrintNumber fix (#8184) use `bidderRequestsCount` instead of `bidRequestsCount` * Rubicon Analytics Adapter: pass along billing events (#8182) * rubicon listens to billing events * billingId is required * remove log * Adprime Bid Adapter: update user sync (#8158) * initial * fix * remove redundant language mod, use player sizes in video traff * test modify * fix * Adding Tests * add keywords param * log * log * log * fix * add idl * add idl * fix test * lint * lint * fix * lint * lint * lint * lint * add sync * fix * add video params, advertiserDomains and getFloor * add audiences param * fix test * update user sync Co-authored-by: Aigolkin1991 Co-authored-by: Aiholkin * MediaFuse bid adapter: initial release (#8113) * created MediaFuse Prebid.js bidder adapter I have created alias from appnexus Prebid.js adapter and created one for Mediafuse * updated alias code * updated spec and BidAdapter.js file - Removed mediafuse alias from appenxusBidAdapter.js - mediafuseBidAdapter_spec.js is created from appnexusBidAdpater_spec.js - Removed mediafuseAnalyticsAdapter.js * update the code as per the PR comment * formatted the comment line code * Magnite GVLID used (#8186) * Prebid core: fix log message when enabling a missing analytics provider (#8181) * TheMediaGrid & TrustX bid adapters: reformat first party data (#8146) * Added TheMediaGridNM Bid Adapter * Updated required params for TheMediaGridNM Bid Adapter * Update TheMediGridNM Bid Adapter * Fix tests for TheMediaGridNM Bid Adapter * Fixes after review for TheMediaGridNM Bid Adapter * Add support of multi-format in TheMediaGrid Bid Adapter * Update sync url for grid and gridNM Bid Adapters * TheMediaGrid Bid Adapter: added keywords adUnit parameter * Update TheMediaGrid Bid Adapter to support keywords from config * Implement new request format for TheMediaGrid Bid Adapter * Fix jwpseg params for TheMediaGrid Bid Adapter * Update unit tests for The Media Grid Bid Adapter * Fix typo in TheMediaGrid Bid Adapter * Added test for jwTargeting in TheMediaGrid Bid Adapter * The new request format was made by default in TheMediaGrid Bid Adapter * Update userId format in ad request for TheMediaGrid Bid Adapter * Added bidFloor parameter for TheMediaGrid Bid Adapter * Fix for review TheMediaGrid Bid Adapter * Support floorModule in TheMediaGrid Bid Adapter * Fix empty bidfloor for TheMediaGrid Bid Adapter * Some change to restart autotests * Fix userIds format for TheMediaGrid Bid Adapter * Remove digitrust userId from TheMediaGrid Bid Adapter * Protocols was added in video section in ad request for TheMediaGrid Bid Adapter * TheMediaGrid: fix trouble with alias using * TheMediaGridNM: fix trouble with alias * TheMediaGrid Bid Adapter: added support of PBAdSlot module * TheMediaGrid Bid Adapter: fix typo * GridNM Bid Adapter: use absent in params data from mediaTypes * GridNM Bid Adapter: fix md file + add advertiserDomains support * TheMediaGrid and gridNM Bid Adapter: minor netRevenue fixes * gridNM Bid Adapter updates after review * TheMediaGrid Bid Adapter: fix keywords workflow * fix testing and kick off lgtm again * TheMediaGrid: added ext.bidder.grid.demandSource processing * TheMediaGrid: added user.id from fpd cookie * TheMediaGrid: control cookie setting via bidder config * TheMediaGrid: use localStorage instead cookie * TheMediaGridNM Bid Adapter: update adapter to use /hbjson endpoint * TheMediaGridNM: fix unnecessary conditions * TheMediaGrid: fix bug with nurl field in response * TheMediaGrid: update test * TheMediaGridNM: fix possible bug with nurl * TheMediaGrid: added alias as playwire * TheMediaGrid: added alias as adlivetech * TheMediaGrid: fix sync url workflow * TrustX: fix sync url worflow + remove old syncurl * TheMediaGrid: added instl support * TheMediaGrid: fix test for instl * TheMediaGrid: update md file * TheMediaGrid: reformat segments for permutive rtd module * TrustX: send all ortb2.user.data in user.data * TheMediaGrid: remove permutive segments reformating * TrustX: remove permutive segments reformating * TheMediaGrid & TrustX: fix typo Co-authored-by: Chris Huie * Rubicon Analytics: handle bad auction case (#8192) * handle bad auction * rename auctionCache * Ad Generation Bid Adapter: add NovatiqSystem (#8178) * update Adgeneration to add NovatiqSystem. * Add IOS check * update adapter version * fix novatiq object * Prebid 6.16.0 release * Increment version to 6.17.0-pre * Dacid User Id Module: add new id module (#8187) * Add dacid user id module * Fix source domain * Add document to explain dac ID * Insticator adapter: add support gpid (#8189) * Insticator adapter: add support gpid * Update insticatorBidAdapter_spec.js Rerun build * Switch to use imp.ext.gpid field * Build system: upgrade webpack (#7935) * Upgrade webpack to 5; gulp build works * Fix karma, except events * Uniform access to events, import * or require (import * from 'events.js' / import events from 'events.js' return different objects, which is a problem for stubbing) * Fix (?) adapters that use `this` inappropriately * Update webpack-bundle-analyzer * Fix warnings * Enable tree shaking * Set webpack mode 'none' (or else tests fail (!)) * Update coreJS version in babelrc - https://github.com/prebid/Prebid.js/issues/7943 * Use babel to translate to commonjs only for unit tests; enable production mode * Merge master * Add fsevents as optional dep * Fix imports of `events` in browsiRtdProvider to work with https://github.com/prebid/Prebid.js/pull/7935 (#8197) * Update prebid.js (#8190) * Open8 Bid Adapter: Add `advertiserDomains` support to comply with Prebid.js 5.0 (#8127) * add open8 adapter * slotkey and fix * add maintainer address * add bidwon * check field populated * Now Open8 adapter produced result object contains Prebid.js v5 mandatory field "meta.advertiserDomains". * Import only used symbols from "utils.js". Co-authored-by: 木田 愛一郎 Co-authored-by: Takahashi Kenta * userId Module: Added getEncryptedSignalfromSources and registerSignalsources fun… (#8117) * ESP: Added getEncrptedSignalfromSources and registerSignalsources function to send eids to GAM * Lint issue resolved * ESP: Changes in code based on review comments * ESP: Changes in code based on review comments * ESP: Example HTML added * ESP: Removed duplicate key from example.html * ESP: GetUserEids by source name function added and change requested done * ESP: SignalSources name changed to encryptedSignalSources under usersync object * ESP: Added encryptedSignalConfig in userSync object having signal sources with customSources * ESP: ESP example HTML modified * ESP: removed unwanted arguments/parameters * Test cases: Module added to test getUserIdsByEidsBySource function * ESP: Added concat function instead of Object.assign * Removed enableSingleRequest() calling from the code - testpage * ESP: Lint issue solved * ESP: LGTM alert issue fixed(This is always true) * ESP: updated userid spec file and removed unwanted code * ESP: Added check if registerDelay timeout is undefined and gtag check * ESP: ESP configs updated and code clean up based on review comments * ESP: Converted normal function to arrow functions * ESP: Test cases position changed * ESP: Handle undefined and null check * add support for the schain option to the conversant adapter (#8203) * Update kargoBidAdapter.js (#8205) * Flashtalking FTRACK User ID Submodule: initial release (#8063) * JDB-496: first commit, copying over files from prebid-js-ftrack-module PR-1.0.0 ftrack user ID submodule code * Addressing the lgtm alert * Addressing the remaining lgtm alerts * Setting VENDOR_ID to null for now * Pulled ftrack out and used a config.param property instead to load ftrack * Cleaning up errors raised by linter * Tweaking a comment * JDB-496: cleaning up docs based on PR feedback * PR fixes Co-authored-by: Jason Lydon * Id ward RTD Module: initial release (#8076) * added idWardRtdProvider module * added idward_segments_example.html to test module * improver description * added 'Testing' section to readme * added missing function description * show sent FPD at the example html page * review comments resolved Co-authored-by: Pavlo * Synacormedia Bid Adapter: ttl and eid update (#8006) * CAP-2474 Synacor Media Bid Adapter: Use OpenRTB parameters to populate bids' `ttl` values * CAP-2474 Revised bid expiration/ttl code to conform to revised requirements w/ adserver tag expiration * CAP-2474 Added tests and cleaned up tag expiration code * feature/CAP-2516: Remove the filtering of eids for synacormediaBidAdapter Co-authored-by: Andrew Fuchs Co-authored-by: Timothy M. Ace Co-authored-by: Tim Ace * FTrackIdSystem & IDWardRtdProvider: fix calls to `getStorageManager` (#8208) * Mediasquare Bid Adapter: add metrics to onBidWon Event (#8209) * remove old userSyncs method * Update mediasquareBidAdapter.js * Update mediasquareBidAdapter.js * Update mediasquareBidAdapter_spec.js * Mediasquare Bid Adapter: add floor module support * Update mediasquareBidAdapter_spec.js * Update mediasquareBidAdapter.js * Rubicon Analytics, pass along gpid (#8210) * PubMatic Bid Adapter: Added multibid support for GroupM (#8193) * Changed net revenue to True * Added miltibid support for GroupM * Added marketplace array to check for values * Added marketplace check while requesting too Co-authored-by: Azhar * Add banner support to readpeak bid adapter (#8179) * Prebid core & PBS adapter: better support for server-side stored impressions (#8154) * Update ad unit validation to allow no bids if ortb2Imp is specified * Move s2sTesting logic to the s2sTesting module * Accept adUnit.storedAuctionResponse in lieu of adUnit.bids; update PBS adapter to work with no-bid adUnits * Do not accept 'storedAuctionResponse' as an alternative to 'ortb2Imp' * Fix short-circuit in building out imp[].ext * Make `s2sConfig.bidders` optional * UserID module: fix esp unit test (#8212) * ESP:Assetion Issue solved * Prebid 6.17.0 release * Increment version to 6.18.0-pre * AdHash Bid Adapter: add brand safety (#8167) * 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 * jpBidAdapter - pass additional param in request (#8188) Tests updated Bump version to 1.8.2 * only map one slotrender to one adunit (#8211) * DFP Ad Server Video: respect original url (#8168) * original url components take precedence over defaults uses object assignment * tests that url is respected * respects url size and cust params * moves url cust param addition to fn * tests that url params are respected * Admaru adapter : add new bid adapter (#8149) * init * modified admaruBidAdapter.js, md, _spec.js * modify for test * Delete .project * update * update admarubidadapter.js * Revert "Delete .project" This reverts commit 0e1bdd4fcadd0a97fea87ba2a92cb502e5e3a19b. * remove .project * modified * modified * Delete .project * modified * Revert "Delete .project" This reverts commit c4e7bd6096fe9521dd5e2fab2b3d5241149dc6ec. * Delete .project * modified * modified Co-authored-by: sung.chung * Missena Bid Adapter - allow custom endpoint. (#8222) * Next Millenium Bid Adapter: Added new parameter group_id (#8200) * 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 * add timeout value to timeout pixel (#8224) * Outbrain bid adapter: added floor module and privacy link support (#8223) * add floor support * add additional validation for bid request format * add privacy link support * fixes * set privacy in mapper * fix test * Improve Digital Bid adapter: use the oRTB server endpoint (#8138) * Major refactoring to use new oRTB server endpoint Co-authored-by: Faisal Islam <100519197+iosfaisal@users.noreply.github.com> Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> * fixed wrong merge * Fixed issue related to site and app (#9) * Fixed errors for test cases. Co-authored-by: Faisal Islam <93644923+faisalvs@users.noreply.github.com> Co-authored-by: Faisal Islam <100519197+iosfaisal@users.noreply.github.com> Co-authored-by: Faisal Islam * NativoBidAdapter - Bid data mapping refactor and added QS params on request (#8196) * 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. Co-authored-by: Joshua Fledderjohn * Floor price : allow having a 0$ floor (#8239) * Prebid 6.18.0 release * Increment version to 6.19.0-pre * IAS RTD Module: Custom key values (#8214) * Mapping table initialization A constant initialises a mapping table where each possible key used by IAS is mapped to itself. The dataProviders config for the IAS RTD module accepts an optional new property: keyMappings. The init function reads this property and uses it to overwrite any key that is already in the pre-initialised mapping table with the new value provided. In a future addition, the mapping table will be used to populate the key-values actually sent to the adserver. * Rename Key Values After merging the key-values for each ad unit, a renaming function is called to ensure that any custom key chosen by the client is used. * Bugfix Call the callback() function in getBidRequestData. * Unit tests changed to include one renamed parameter. * Unit tests corrected and adapted to cover the current features. * Yieldlab Bid Adapter: Add Support for User Matching (#8148) * Build system: add option to generate source maps for production builds (#8220) * Gamoshi Bid Adapter: Handle gdpr applies correctly (#8245) * Nobid Prebid Adapter commit (#4050) * Nobid Prebid Adapter commit * Fixed global replace and unit tests * Fixed find function * Added nobidBidAdapter.md * Removed description and added "Bid Params" section. * Added test siteId 2 for testing. * Refactored the Adapter to remove most references to the nobid object. We still need the nobid object because we have a passback tag in DFP that makes reference to it. * Fix concurrent responses on the page * Cosmetic change to log an error in case of missing ad markup * Keep nobid.bidResponses cross adapters. * Added GDPR support in user sync and added test coverage. gulp test-coverage gulp view-coverage * Padding issues * Fix padding issues * Fix padding * update outstream prod url (#4104) * support pubcid and uids (#4143) * Fix misspelling and minor cleanup of schain docs (#4150) * Prebid 2.31.0 Release * Increment pre version * Rubicon: tuning logged messages (#4157) * Rubicon: tuning logged messages * Update rubiconBidAdapter.js * fixed indentation * Rubicon Video COPPA fix (#4155) * Rubicon Video COPPA fix * Unit test for Rubicon Video COPPA fix * Playground XYZ adapter - iframe usersync bug fix (#4141) * corrected user sync type * removed support for iframe usersync * added unit tests for getUserSyncs * update nvmrc file (#4162) * update gulp-footer package (#4160) * Datablocks bid/analytics adapter (#4128) * add datablocks Analytics and Bidder Adapters * remove preload param * remove preloadid * better coverage of tests * better coverage * IE doesn't support array.find * lint test * update example host * native asset id should be integer * update logic of ad_types field in appnexusBidAdapter (#4065) * Shorten SomoAudience to just Somo (#4163) * Shorten SomoAudience to just Somo * Make package-lock return * Quantcast: Fix for empty video parameters (#4145) * Copy params from bid.params.video. * Added test for missing video parameters. * Include mimes from adunit. * One Video adding Rewarded Video Feature (#4142) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * Module to pass User Ids to DFP (#4140) * first commit * renamed * minor doc change * documentation * small change * EB * removed unused imports * minor changes * reanmaed a const * adding more methods to test shareUserIds module * unit tets cases for shareUserIds * indentation * renamed DFP to GAM * renamed shareUserIds to userIdTargeting * Update userIdTargeting.md * trying to restart CI * digitrust userId case handled * minor comment change * using auctionEnd event instead of requestBids.before * using events.on * Buzzoola bid adapter (#4127) * initial commit for buzzoola adapter * leave only banners for now * fix bid validation * change endpoint url * add video type * restore renderer * fix renderer * add fixed player sizes * switch bids * convert dimentions to strings * write tests * 100% tests * remove new DOM element creation in tests * handle empty response from server * change description * E2e tests for Native and Outstream video Ad formats. (#4116) * reorganize e2e/ tests into separate directories * new test page for e2e-banner testing * add test to check if Banner Ad is getting loaded * change location of the spec files to reflect change in test/e2e directory structure * add test case to check for generation of valid targeting keys * create Native Ad test page * add test case to check validity of the targeting keys and correct rendering of the Ad * update old browser versions to new * update browser version * update title * remove console.log statements * add basic functional test for e2e outstream video ad format * Update LockerDome adUnitId bid param (#4176) This is not a breaking change * fix several issues in appnexus video bids (#4154) * S2s testing disable client side (#4123) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * New testServerOnly flag * Tests and a bug fix * Removed dead code * Fixes requested in review * Check each adUnit * isTestingServerOnly changes per Eric * Fixed IE 11 bug * More tests * improved test case names * New option to Include deal KVPs when enableSendAllBids === false (#4136) * new option to include KVPs which have deals when enableSendAllBids === false * updating tests to be more realistic * Prebid 2.32.0 Release * increment pre version * Rubicon doc: changing video test zone (#4187) * added schain support to sonobi adapter (#4173) * if schain config is not defined then error should not be thrown (#4165) * if schain config is not defiend then error should not be thrown * relaxed mode nodes param not defined error handled * added test cases for config validation * a curly bracket was missing in the example * Rubicon: updating test params (#4190) * myTargetBidAdapter: support currency config (#4188) * Update README.md (#4193) * Update README.md * Update README.md * cedato bid adapter instream video support (#4153) * Added adxpremium prebid analytics adapter (#4181) * feat(OAFLO-186): added support for schain (#4194) * Sonobi - send entire userid payload (#4196) * added userid param to pass the entire userId payload to sonobis bid request endpoint * removed console log git p * fixed lint * OpenX Adapter fix: updating outdated video examples (#4198) * userId - Add support for refreshing the cached user id (#4082) * [userId] Added support for refreshing the cached user id: refreshInSeconds storage parameter, related tests and implementation in id5 module * [userId] Added support for refreshing the cached user id: refreshInSeconds storage parameter, related tests and implementation in id5 module * UserId - ID5 - Updated doc with new contact point for partners * UserId - Merged getStoredValue and getStoredDate * [UserId] - ID5 - Moved back ID5 in ./modules * UserId - ID5 - Fixed incorrect GDPR condition * [UserId] - Doc update and test cleanup * Prebid 2.33.0 Release * Increment pre version * SupplyChainObject support and fires a pixel onTimeout (#4152) * - Implemented the 'onTimeout' callback to fire a pixel when there's a timeout. - Added the ability to serialize an schain object according to the description provided here: https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md * some mods to the schain tag generation * - added tests for schain param checking. * - fixed a malformed url for timeouts * - Removed a trailing ',' while generating a schain param. * - Using the schain object from validBidRequest if present. Reverting to checking if params has it if not. * - reverting changes to merge with master * - Resolving merge issues * Feature/add profile parameter (#4185) * Add optional profile parameter * EMXDigital Bid Adapter: Add video dimensions in request (#4174) * addressed feedback from #3731 ticket * removed commented code from emx test spec * logging removed from spec * flip h & w values from playerSize for video requests * adding Outstream mediaType to EMX Digital * adding device info. update to grab video param. styling changes. * add video dimensions from playerSize * fix test for video dimensions * Added keywords parameter support in TrustX Bid Adapter (#4183) * Add trustx adapter and tests for it * update integration example * Update trustx adapter * Post-review fixes of Trustx adapter * Code improvement for trustx adapter: changed default price type from gross to net * Update TrustX adapter to support the 1.0 version * Make requested changes for TrustX adapter * Updated markdown file for TrustX adapter * Fix TrustX adapter and spec file * Update TrustX adapter: r parameter was added to ad request as cache buster * Add support of gdpr to Trustx Bid Adapter * Add wtimeout to ad request params for TrustX Bid Adapter * TrustX Bid Adapter: remove last ampersand in the ad request * Update TrustX Bid Adapter to support identical uids in parameters * Update TrustX Bid Adapter to ignore bids that sizes do not match the size of the request * Update TrustX Bid Adapter to support instream and outstream video * Added wrapperType and wrapperVersion parameters in ad request for TrustX Bid Adapter * Update TrustX Bid Adapter to use refererInfo instead depricated function utils.getTopWindowUrl * HOTFIX for referrer encodind in TrustX Bid Adapter * Fix test for TrustX Bid Adapter * TrustX Bid Adapter: added keywords passing support * rubicon: avoid passing unknown position (#4207) * rubicon: not passing pos if not specified * added comment * not sending pos for video when undefined * cleaning up test * fixed unit test * correctly reference bidrequest and determine mediatype of bidresponse (#4204) * GumGum: only send gdprConsent when found (#4205) * adds digitrust module, mods gdpr from bool to int * update unit test * only send gdprconsent if present * LKQD: Use refererInfo.referer as fallback pageurl (#4210) * Refactored URL query parameter passthrough for additional values, changed SSP endpoint to v.lkqd.net, and updated associated unit tests * Use refererInfo.referer as fallback pageurl * Removed logs and testing values * [UserId] - ID5 - Fixed case when consentData is undefined (No CMP) (#4215) * create stubs for localStorage in widespaceBidAdapter test file (#4208) * added adId property to adRenderFailed event (#4097) When no bid (therefore no adUnitCode) is available in the adRenderFailed event it can be difficult to identify the erroring slot.But in almost all cases the given slot still has the adId targeting. * OpenX Adapter: Forcing https requests and adding UserID module support for LiveRamp and TTD (#4182) * OpenX Adapter: Updated requests to force https * OpenX Adapter: Added support for TTD's UnifiedID and LiveRamp's IDL * PubMatic to support userId sub-modules (#4191) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * TripleLift support for UnifiedId and IdentityLink (#4197) * Add IdentityLink support and fix UnifiedId. It appears we've been looking for UnifiedId userIds on the bidderRequest object, when they are found on bidRequests. This commit fixes that error, and adds support for IdentityLink. * change maintainer email to group * Added lemma adapter (#4126) * lemmaBidAdapter.js Added lemma bid adapter file * lemmaBidAdapter.md Added lemma bid adapter md file * lemmaBidAdapter_spec.js Added lemma bid adapter test spec file * Update lemmaBidAdapter.js Fixed automated code review alert comparison between inconvertible types * Update lemmaBidAdapter.js Fixed review changes * Update lemmaBidAdapter.md Correct parameter value. * Adkernel adapter new alias (#4221) * Force https scheme for Criteo Bidder (#4227) * assign adapter version number * Ensure that Criteo's bidder is always called through https * Add Video Support for Datablocks Bid Adapter (#4195) * add datablocks Analytics and Bidder Adapters * remove preload param * remove preloadid * better coverage of tests * better coverage * IE doesn't support array.find * lint test * update example host * native asset id should be integer * add datablocks Video * remove isInteger * skip if empty * update adUnit, bidRequest and bidResponse object (#4180) * update adUnit, bidRequest and bidResponse object * add test for mediaTypes object * 3 display banner and video vast support for rads (#4209) * add stv adapter * remove comments from adapter file * start rads adapter * fix adapter and tests * fixes * fix adapter and doc * fix adapter * fix tests * little fix * add ip param * fix dev url * #3 radsBidAdapter.md * #3 radsBidAdapter.md: cleanup * fix code and doc * UserId - Add SameSite and server-side pubcid support (#3869) * Add SameSite and server-side pubcid support * Fix emoteevBidAdapter unit test * added schain to appnexus bid adapter (#4229) * added schain to appnexus bid adapter * semicolon * update doubleclick url (#4179) * Prebid 2.34.0 release * increment pre version * Rubi Analytics handles > 1 bidResponse per bidRequest (#4224) * videoNow bid adapter (#4088) * -- first commit * -- cors and bidder's name fixed * -- almost ready * -- added docs * -- added nurl tracking * -- bid params * -- tests added * -- test fixed * -- replace placeholder in the onBidWon pixel's url * -- commit for restart tests * -- change response data format for display ad * -- tests updated * -- 100% tests coverage * -- a few clean the test's code * -- custom urls from localStorage * -- tests updated * -- a few clean the test's code * -- new init model * -- spec for new init model * -- fix for new init model * -- code cleaned * -- 100% tests coverage * -- 100% tests coverage * -- fixed test * -- commit for restart tests * djax new bidder adapter (#4192) * djax bidder adapter * djax bidder adapter * Update hello_world.html * Added Turk Telekom Bid Adapter (#4203) * Added Turk Telekom Bid Adapter * Fix md file for Turk Telekom Bid Adapter * MicroAd: Use HTTPS in all requests (#4220) * Always use HTTPS endpoint in MicroAd * Update code * Fixed a broken test in MicroAd * Schain: avoiding Object.values as it is breaking on IE11 (#4238) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * avoiding use of Object.values * 3952 delay auction for ids (#4115) * 3952 delay auction for user ids * 3952 add integration example * 3952 add tests * 3952 fix html example * add todos * 3952 continue auction if ids received * 3952 add tests for auction delay * increase test coverage * set config for test * remove todo * add a few more checks to tests * add comment, force tests to rerun * Feature: adUnitBidLimit (#3906) * added new feature to config to limit bids when sendallbids is enabled * cleaned up code. removed extra spaces etc * removed trailing spaces in config * remove .flat() and replaced with spread operator * removed flat function and instead pushing using spread operator * updated to use sendBidsControl instead * updated targeting_spec to test bidLimit * removed trailing spaces from targeting_spec * Update Rubicon Adapter netRevenue default (#4242) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Removed AdastaMadia from alias (#4255) * Update appnexusBidAdapter.js (#4251) * IdentityLink - change expiration time to 30 days (#4239) * Add coppa support for AppNexus adapter (#4253) * Add coppa support for AppNexus adapter * test name * add new longform e2e tests (#4206) * Konduit module (#4184) * Adding Konduit module * Removed superfluous arguments passed to obtainVastUrl function * Removed superfluous arguments passed to obtainVastUrl function. * Build trigger (empty commit) * Module documentation updated according to the comments * Logic in obtainVastUrl function updated according to the review comment. * Removed hook, enabled eslint * Circle CI runs e2e tests on every push (#4200) * run functional tests on circle ci on push to any remote branch * remove extraneous key from config file * add test.localhost as alias to 127.0.0.1 * check 0: execute circle-ci * move /etc/config to a separate command * change bid partner to rubicon * test appnexus bid adapter in ci * comment browserstack command * remove console.log statement * test1: circle-ci * change reference dev -> prod while loading prebid * add console.log statement * check-2: circle-ci * comment browserstack testing * change bid adapter * change bid adapter * remove test case for checking targeting keys * remove the ci flag * uncomment test for checking correct generation of targeting keys * swap AN -> Rubicon for testing targeting keys * Outcon bid adapter. (#4161) * Outcon bid adapter. * Fix identation * Fixes * Fixes * Fixes * Spec fixes * Fixes * Fix urls * Fix * Fix parameters * Fix space operators * Fix bidder timeout * Update * Fix whitespace * no message * Outcon unit test * no message * no message * no message * no message * Fixes * Fixes * Change url * no message * no message * no message * Added bidId * no message * no message * no message * no message * Wrapping url with html * no message * no message * no message * Adding workflow to run end to end tests (#4230) * Adding workflow to run end to end tests * trying self branch * Update to run at 12 every day * cleanup config using aliases * update branch and cron time * add command * update prebid path for e2e test pages (#4274) * Prebid 2.35.0 release * Increment pre version * Add usersync to adpone adapter (#4245) * add user sync to adpone adapter * move adpone usersync to global variable * added withcredentials to http request * fix http request options * fix http request options * add withCredentials: true * add withCredentials: true * added test coverage to usersync * update sync function * add test coverage * adpone adapter * package lock * add more testing * add more testing * testing for onBidWon fucntion * test onbidwon function * trigger build * Revert GumGum Adapter 2.28 resizing changes (#4277) * changed resizing unit tests to return the first size dimensions in the sizes array * added some changes * reverted adapter changes * SpotX Bid Adapter: Support schain, ID5 object, Google consent object, and hide_skin (#4281) * Add SpotXBidAdapter * Minor updates * Undo testing changes to shared files * Fix relative imports * Remove superfluous imports and write a few more tests * Formatting, ID5 object, Google consent objects - Added ID5 object support - Added Google Consent object - Reformatted indentaiton on spec file * Revert content_width and content_height changes in docs - not sure how these got moved, lets put them back * Remove click_to_replay flag in example - no reason to use this one in the example * Spotx adapter - Add schain support and update unit tests * Update schain path in ORTB 2.3 request body - schain object is now added to ortb request body at request.ext.source.ext.schain * Add hide_skin to documentation - whoops, this got removed, let's add it back * Update Rubicon Analytics Adapter `bidId` to match PBS (#4156) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update for rubicon analytics to send seat[].bid.id for PBS video and banner * fixed conditional for server and video or banner * updated with optimized value test for bidid * update changed default value of netRevenue to true * remove var declaration for rightSlot to correct lgtm error for unused variable * update defineSlot div id to match div id defined in html body * update test ad unit test props * revert lock to match remote master * add seatBidId to bidObj in rpBidAdapter interpretResponse * update setTargeting to execute in the bids back handler * remove dev integration test page * meaningless commit to get lgtm to re-run * SmartRTB adapter update (#4246) * modules: Implement SmartRTB adapter and spec. * Fix for-loop syntax to support IE; refactor getDomain out of exported set. * Remove debugs, update doc * Update test for video support * Handle missing syncs. Add video to media types in sample ad unit * Add null response check, update primary endpoint * Note smrtb video requires renderer * Support Vast Track (#4276) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Add parameters if config.cache.vasttrack is true * Use requestId instead of adId * Test new vasttrack payload params * Removed commented out code * Relaxed conditional check per review * Removed commented out line * Added 1000x250 size (#4295) * prepare vidazoo adapter for v3.0 (#4291) * Improve Digital adapter: support schain (#4286) * LiveIntent Identity Module. (#4178) * LiveIntentIdSystem. Initial implementation. * LiveIntentIdSystem. Removed whitespace. * Fixed typo * Renamed variables, cookiesm added md. * Changed the default identity url. * Composite id, with having more than just the lipbid passed around. * Composite id. * Merge conflict resolution. * Changed docs and param description. * Added typedoc & mentioned liveIntentIdSystem in submodule.json. * Extracted the LiveIntentIdSystem under modules, removed it from default userId modules. * Fixing the 204 + no body scenario. * Added liveIntent to submodule.json * Fixing docs indentation. * Updated prebidServer & specs. * Minor specs update. * updating liveintent eids source (#4300) * updating liveintent eids source these are supposed to be domains * updating unit test * fix appnexusBidAdapter view-script regex (#4289) * fix an view script regex * minor syntax update * 33Across adding bidder specific extension field (#4298) * - add 33across specific ext field for statedAt * - fix unit test for 33Across adapter * PubMatic to support LiveIntent User Id sub-module (#4306) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * supporting LiveIntent Id in PubMatic adapter * updated source for liveintent * Finteza Analytics Adapter: fix cookies (#4292) * fix reading and sending cookies * fix lint errors * clear comments * add unit tests * fix calling of setCookies for IE * clear cookies after test * use own setCookie method inside tests * Update LockerDome adapter to support Prebid 3.0 (#4301) * Returning the `IdResponse` type with an obj + callback. Fix for 4304 (#4305) * Returning the `IdResponse` type with an obj + callback. * Renamed resp -> result. * Removed whitespace. * ShowHeroes adapter - expanded outstream support (#4222) * add ShowHeroes Adapter * ShowHeroes adapter - expanded outstream support * Revert "ShowHeroes adapter - expanded outstream support" This reverts commit bfcdb913b52012b5afbf95a84956b906518a4b51. * ShowHeroes adapter - expanded outstream support * ShowHeroes adapter - fixes (#4222) * ShowHeroes adapter - banner and outstream fixes (#4222) * ShowHeroes adapter - description and outstream changes (#4222) * ShowHeroes adapter - increase test coverage and small fix * [Orbidder-Adapter] Add bidRequestCount and remove bid.params.keyValues (#4264) * initial orbidder version in personal github repo * use adUnits from orbidder_example.html * replace obsolete functions * forgot to commit the test * check if bidderRequest object is available * try to fix weird safari/ie issue * ebayK: add more params * update orbidderBidAdapter.md * use spec. instead of this. for consistency reasons * add bidfloor parameter to params object * fix gdpr object handling * default to consentRequired: false when not explicitly given * wip - use onSetTargeting callback * add tests for onSetTargeting callback * fix params and respective tests * remove not used bid.params.keyValues * add bidRequestCount to orbidder.otto.de/bid Post request * add bidRequestCount to test object defaultBidRequest * PulsePoint: remove usage of deprecated utils method / prep for 3.0 (#4257) * 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 * Removing usage of deprecated utils method * minor refactor * Use isArray method (#4288) * Add Parrable ID submodule (#4266) * add parrable id submodule * fix integration test config * fix var name * always refresh sotredId for parrable * add submodulesThatAlwaysRefresh concept * remove comment * add parrable url as one string * add parrable prod endpoint * use .indexOf instead of .includes * add params to test config * comment failing test * uncomment failing assertion * add parrable ID to prebid server adapter * add parrableIdSystem to .submodules.json * extract parrableId unit tests from userId spec * remove breakline between imports * remove unused param * remove userId generic feature from parrableId module * remove trailing space * fix failing test due to none merged conflict * Prebid 2.36.0 Release * Increment pre version * Support schain module and send bidfloor param in Sharethrough adapter (#4271) * Add support for supply chain object module Story: [#168742394](https://www.pivotaltracker.com/story/show/168742394) Co-authored-by: Josh Becker * Add bidfloor parameter to bid request sent to STX Story: [#168742573](https://www.pivotaltracker.com/story/show/168742573) * Platform One Analytics Adapter (#4233) * Added Y1 Analytics Adapter * rename y1AnalyticsAdapter in yieldoneAnalyticsAdapter * Yieldone Bid Adapter: fixes from lint check * Yieldone Analytics Adapter: fix endpoint protocol * Added spec file for yieldone Analytics Adapter * Fix parrable id integration example (#4317) * fix parrableId integration example * add parentheses * Improve Digital adapter: support for video (#4318) * Bid floor, https, native ad update * Update the ad server protocol module * Adding referrer * Improve Digital support for video * Improve Digital adapter: video * adapter version -> 6.0.0 * Gamoshi: Update aliases list. Add support for userSync. (#4319) * Add support for multi-format ad units. Add favoredMediaType property to params. * Add tests for gdpr consent. * Add adId to outbids * Modify media type resolving * Refactor multi-format ad units handler. * Modify the way of sending GDPR data. Update aliases. * Add new consent fields. Add unit test. * Add new consent fields. Add unit test. * Add support for id5 and unified id cookie sync. * Add support for id5 and unified id cookie sync. * Add restricted check for gdpr consent. * fix for userSync endpoint getting called with bidder alias names, instead of actual bidder names (#4265) * modify ixBidAdapater to always use the secure endpoint (#4323) * PubMatic to support Parrable User Id sub-module (#4324) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * PubMatic to support parrable id * VISX: currency validation & fix double escape of referer (#4299) * PubMatic to support coppa (#4336) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * added coppa compliance * vuble: outstream has fullscreen option (#4320) * Mod: vuble oustream has fullscreen option * Add: vuble test for outstream scenario * EMXDigital: hotfix to resolve URIError from decodeURIComponent (#4333) * hotfix to resolve URIError from decodeURIComponent * added unit for decoding adm * Specify second parameter for parseInt for pubmaticBidAdapter (#4347) * Remove usage of getTopWindowUrl in Prebid Adapter (#4341) * Conversant Bid Adapter update for 3.0 (#4284) * Add cpmDistribution function for Google Analytics adapter (#4240) * Add cpmDistribution function for Google Analytics adapter * Add test for the cpmDistribution function * Remove half written comment * fixing SRA p_pos (#4337) * In Sonobi Adapter, only read sizes from bid.mediaTypes (#4311) * Fix mediaTypes (#4332) * Outcon bid adapter. * Fix identation * Fixes * Fixes * Fixes * Spec fixes * Fixes * Fix urls * Fix * Fix parameters * Fix space operators * Fix bidder timeout * Update * Fix whitespace * no message * Outcon unit test * no message * no message * no message * no message * Fixes * Fixes * Change url * no message * no message * no message * Added bidId * no message * no message * no message * no message * Wrapping url with html * no message * no message * no message * Fix mediaTypes * no message * Update outconBidAdapter_spec.js * Adding VAS response * no message * no message * no message * Fix * Changed ttl * no message * supportedMediaTypes * no message * no message * Prebid 2.37.0 release * increment pre version * Add vast xml support and other minor changes to Beachfront adapter (#4350) * Add support for vast xml in the bid response * add secure protocol to outstream player url * add device connection type * add player setting for poster color * add new value for creative Id * Update smartrtbBidAdapter (#4362) * modules: Implement SmartRTB adapter and spec. * Fix for-loop syntax to support IE; refactor getDomain out of exported set. * Remove debugs, update doc * Update test for video support * Handle missing syncs. Add video to media types in sample ad unit * Add null response check, update primary endpoint * Note smrtb video requires renderer * Remove old params checks, fix documentation playerSize field name * Revert "Update smartrtbBidAdapter (#4362)" (#4368) This reverts commit be6704bcec65a28d80b6d09a8d1c51ef9a8ba824. * Add userSync in onetagBidAdapter (#4358) * Minor bug fixing in onetagBidAdapter.js Fixed a minor bug. Updated TTL in response to align the correct specifications. * Update onetagBidAdapter Added additional page info and user sync function. * Update onetagBidAdapter_spec.js Added the test for getUserSyncs function. * Fix about userSync * getUserSyncs: test update with gdpr params * Sovrn adapter updates: schain, digitrust, pixel syncing, and 3.0 upgrades (#4335) * schain and digitrust * pixel beacons * unit tests and fixes from testing * Prebid 3.0 updates * review fix * Add bid adapter for ablida (#4256) * Add ablida adapter * rename category parameter, add documentation * AdKernel: added waardex_ak alias (#4290) * added alias Added a new alias * fixing unit test * Revert "Sovrn adapter updates: schain, digitrust, pixel syncing, and 3.0 upgrades (#4335)" (#4376) This reverts commit 6114a3dba93815dcfb535707d7b4d84f1adb2bc7. * Vrtcal Markets Inc. Bid Adapter Addition (#4259) * Added 3 key Vrtcal Adapter files: adapter,markdown,unit tests * Removed unused getUserSyncs;Added mediaTypes.banner.sizes support;Raised test coverage to 85% * lint formatting errors corrected * Update schain path in ORTB path for spotxBidAdapter (#4377) - Move schain object from request.ext.source.ext.schain to request.source.ext.schain * Update Grid Bid Adapter (#4379) * Added Grid Bid Adapter * remove priceType from TheMediaGrid Bid Adapter * Add video support in Grid Bid Adapter * Added test parameter for video slot * update Grid Bid Adapter to set size in response bid * Update Grid Bid Adapter to support identical uids in parameters * Fix typo in test file for Grid Bid Adapter * Update The Grid Media Bidder Adapter to send refererInfo.referer as 'u' parameter in ad request * Hotfix for referrer in Grid Bid Adapter * Grid Bid Adapter: added wrapperType and wrappweVersion to the ad request * TripleLift: Sending schain (#4375) * Add IdentityLink support and fix UnifiedId. It appears we've been looking for UnifiedId userIds on the bidderRequest object, when they are found on bidRequests. This commit fixes that error, and adds support for IdentityLink. * change maintainer email to group * TripleLift: Sending schain (#1) * Sending schain * null -> undefined * DistrictmDMX: adding support for schain and remove content type to default to prebid selection (#4366) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * Support for ID5 + receive meta data (#4352) * Livewrapped bid and analytics adapter * Fixed some tests for browser compatibility * Fixed some tests for browser compatibility * Changed analytics adapter code name * Fix double quote in debug message * modified how gdpr is being passed * Added support for Publisher Common ID Module * Corrections for ttr in analytics * ANalytics updates * Auction start time stamp changed * Detect recovered ad blocked requests Make it possible to pass dynamic parameters to adapter * Collect info on ad units receiving any valid bid * Support for ID5 Pass metadata from adapter * Typo in test + eids on wrong level * Rubicon Adapter: Always make requests using HTTPS (#4380) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Always make bids requests using https * rp_secure and imp.secure should always be 1 * 7xbid adapter (#4328) * 7xbid adapter * fix error when cli build * - update 33across adapter cookie sync end point (#4345) - update unit test for 33across adapter * Adform adapter: add renderer for outstream bids (#4363) * Prebid 2.38.0 Release * Increment pre version * Adagio: update with external js (#4217) * Add external loader in AdagioBidAdapter * Change adagioAnalyticsAdapter to "endpoint" type * Change _setPredictions for a generic method * Improve AdagioBidAdapter test coverage * Add features detection in Adagio adapter * Fix adagioBidAdapter tests * Add featuresVersion field to bidRequest * Refacto adagio.queue * Expose versions in ADAGIO namespace * Generate a ADAGIO.pageviewId if missing * Move ad-server events tracking to adagioBidAdapter * Store adUnitCodes in ADAGIO namespace * Update documentation Better description of test parameters. * Add internal array to prevent empty pbjs.adUnits * Be sure to access to window.top - does not work in safe-frame env * Add PrintNumber feature * Be sure to compute features on window.top * Bump versions * Add Post-Bid support - ad-server events are listen in current window (instead of window.top) - a new "outerAdUnitElementId" property is set to ADAGIO.pbjsAdUnits array in case of Post-Bid scenario. This property is the 1st parent element id attribute of the iframe in window.top. * Set pagetype param as optional * Add AdThink ad-server support * Improve internal `pbjsAdUnits.sizes` detection Use the adUnit `mediaTypes.banner.sizes` property if exists to build the `ADAGIO.pbjsAdUnits.sizes`. The use of the `sizes` root property is deprecated. * adagioAnalyticsAdapter: add and improve tests * adagioBidAdapter: add and improve tests # Conflicts: # modules/adagioBidAdapter.js # test/spec/modules/adagioBidAdapter_spec.js * adagioBidAdapter: Bump version 1.5 * Adagio: fix import path * PostBid: insure window.top is accessible for specifics functions * Consistency: use Prebid.js utils and fix deprecated * PostBid: do not build a request if in safeframe * Bump version 2.0.0 * Try to fix tests without UA stubing * Try to fix adagioAnalytics failling tests on CI * Consistency: use Prebid loadExternalScript() * Add "adagio" to Prebid.js adloader vendor whitelist * Remove proprietary ad-server listeners * Add RSA validation to adagio external script * add viewdeosDX whitelabel (#4231) * add viewdeosDX hitelabel * Fixed tests and support for sizes * Fix strings * Fix strings * remove only * Fix tests * fix codereview * Fix test + Code review * code review + tests * One video display ad (#4344) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * testing display ad * adding banner * validating banner object * display=1 changes * checking whether diplsy == 1 * html page change * reverting video.html * adding more test cases * spaces * md file change * updated working oneVideoBidAdapter.md file * Update oneVideoBidAdapter.md * Update oneVideoBidAdapter.md * updated the file with both video params and banner * Update video.html * fix double-urlecoded referrer (#4386) * fix double-urlecoded referer (#4388) * PulsePoint Adapter - update for ttl logic (#4400) * 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 * Using the TTL from the bid.ext * Minor refactor * IdentityLink - add logic for sending consent string (#4346) * Fix adagio analytics adapter circleci (#4409) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * update to skip broken circleci tests * skip all * Feature/7xbid remove unneeded params (#4402) * 7xbid adapter * fix error when cli build * remove unneeded params * Empty commit * Empty commit * Remove none ssl (#4406) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * remove none ssl element from all request] * fixed reference to global object (#4412) * ucfunnel adapter support supply chain (#4383) * Add a new ucfunnel Adapter and test page * Add a new ucfunnel Adapter and test page * 1. Use prebid lib in the repo to keep updated 2. Replace var with let 3. Put JSON.parse(JSON.stringify()) into try catch block * utils.getTopWindowLocation is a function * Change to modules from adapters * Migrate to module design * [Dev Fix] Remove width and height which can be got from ad unit id * Update ucfunnelBidAdapter to fit into new spec * Correct the endpoint. Fix the error of query string * Add test case for ucfunnelBidAdapter * Fix lint error * Update version number * Combine all checks on bid request * Add GDPR support for ucfunnel adapter * Add in-stream video and native support for ucfunnel adapter * Remove demo page. Add more test cases. * Change request method from POST to GET * Remove unnecessary comment * Support vastXml and vastUrl for video request * update TTL to 30 mins * Avoid using arrow function which is not discuraged in mocha * ucfunnel tdid support * ucfunnel adapter support supply chain * LiveIntent support in RP Adapter and PBS Adapter update to pass segments (#4303) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * added semi-colon * update eid source to use domain * update video oRTB with liveintent segments * update pbs adapter with liveintent segments support * update rp adapter liveintent support for fastlane * reverted package lock, fix for unintentional update * added unit tests for fastlane.json and ortb, fix to join segments with commas * fix obj property path data.tpid * update remove unnecessary function call * re-ordering query string params * Rubicon Adapter: Add multiple sizes to sizeMap (#4407) * Add Utils to remove item in LocalStorage (#4355) * Making originalCpm and originalCurrency fields in bid object always available (#4396) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * moving originalCurrency declaration from currency to bidderFactory * added a comment * trying to re-run the CI job * added unit test case * trying to re-run the CI job * Placement and inventory (#4353) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * inventory_id and placement * removed unnecessary file * lint error * Update oneVideoBidAdapter.js * lint error fix * Fixes for Platform One Analytics Adapter (#4359) * Added Y1 Analytics Adapter * rename y1AnalyticsAdapter in yieldoneAnalyticsAdapter * Yieldone Bid Adapter: fixes from lint check * Yieldone Analytics Adapter: fix endpoint protocol * Added spec file for yieldone Analytics Adapter * Add adUnitName to analytics data for Yieldone Analytics Adapter * Fix yieldone Analytics Adapter to log only id from adUnitPath * Fix bug with timeout event in Yieldone Analytics Adapter * Added protocol to url (#4395) * initial commit * updated contact and tag details * changes ti support the renderers * changes to pass dimId * fixed names of internal mapping * added comment * added gdpr param to request and other fixes * modified api url * fix * fixed the secure api call * rolled back video event callback till we support it * updated doc with video details * added bid won and timeout pixel * added testcase for bid events * modified testcase * fixed the url logged * tag param values passed ot renderer * added a conditioal check * changes to support new param to adserver for purpose of tracking * passed param to renderer * missing variable defined * added protocol to url * fixed test for protocol * changed urls to secure only * Update emoteev endpoints (#4329) * JustPremium: Update to Prebid 3.0 (#4410) * Update underdogmedia adapter for pbjs 3.0 (#4390) * Update underdogmedia adapter for pbjs 3.0 * Ensure request to endpoint is secure * Update prebid version * Lint fix * Update Consumable adapter for Prebid.js 3.0 (#4401) * Consumable: Clean up tests. * Consumable: Update use of deprecated function. * Consumable: Read sizes from mediaTypes.banner.sizes. * Consumable: Fix lint violation. * CriteoId User Module (#4287) * Add CriteoId module * Update the return type of getId in Criteo Id module Changes: - Use of url parsing function from url lib - Update the return type of getId() - Update the jsdoc to reflect the real return types * Fix failing tests for Criteo user module * Add CriteoIdSystem submodule to .submodule.json. * 2019/10/18 Create Mobsmart bidder adapter (#4339) * Adpod deal support (#4389) * Adpod deal support * Replacing filterBids with minTier * fix potential issue * remove querystringify package (#4422) * Browsi real time data module (#4114) * 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 * Prebid 2.39.0 Release * increment pre version * OpenX Adapter: Prebid 3.0 Compatibility Update (#4413) * Removed usage of deprecated functions * Removed beacons * Banner sizes now reads from bidRequest.mediaTypes.banner.sizes instead of bidRequest.sizes * Updated tests to reflect changes. * GumGum: use mediaTypes.banner.sizes (#4416) * adds digitrust module, mods gdpr from bool to int * update unit test * only send gdprconsent if present * uses mediaTypes before trying bidRequest sizes * removes use of deprecated method * RTBhouse Bid Adapter update for 3.0 (#4428) * add viewable rendering format (#4201) * Feature/adapter (#4219) * feat(bidrequest): code for making bidrequest * feat(bidresponse): format and return the response * feat(tests): added tests for adapter * feat(docs): added docs for the adapter * refactor(url): changed adserver url * test(user sync): added unit tests for the user syncs * refactor(endpoint): changed endpoint for prebid * refactor(endpoint): changed endpoint for prebid * doc(tagid): mandatory param definition added * fix(imp id): fix for correct impression id * fix(width/height): fix for correct width and height sequence * PulsePoint Bid Adapter: Support for schain (#4433) * 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-5938 SupplyChain Object Support * Formatting * Code review * Code review * Fix to currency parsing on response * Add supply chain support for Teads adapter (#4420) * Rubicon: support SupplyChain (schain) (#4315) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Starting schain * More tests for banner schain support * Video tests * Encoding tweaks, required fields and comments * Removed .only() from tests * Change requests per Bret * Add 1ad4good bidder (#4081) * adding bidder code and A bidder for non-profit free ads. more info about this bidder project can be found on project site http://1ad4good.org * removed unused code test coverage is improved to >80% tested for instream video support * removed some legacy code, unused params * hardcoding https to endpoint * Improve Digital adapter fix: don't send sizes for instream video (#4427) * Bid floor, https, native ad update * Update the ad server protocol module * Adding referrer * Improve Digital support for video * Improve Digital adapter: video * adapter version -> 6.0.0 * Improve Digital adapter: don't send sizes for video * Fix a typo in code comment (#4450) * Inventory id and schain support for display (#4426) * supporting schain * Update coinzillaBidAdapter.js (#4438) Update sizes const. * Support schain in ZEDO adapter (#4441) * changes to pass schain * PubMatic supporting updated Criteo User Id module (#4431) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * PubMatic supporting updated Criteo User Id module * added a comment to re-start CI * Remove duplicate param to fix unit tests (#4459) * Brightcom Bid Adapter update for 3.0 (#4343) * add support for min_height field in pbs native requests (#4434) * Supporting Alias via Video Requests (#4460) * New adapter Proxistore (#4365) * add test adapter and documentation * integration test with hello_world * reset package-lock.json * delete useless conditionnal * make integrate test work * revert hello-world * revert hello_world * fix descriptor * change adUnits for integration test * remove proxistore widget * uncomment file * change sizes * remove useless script tag * Implementation of setBidderConfig and bidder-specific data (#4334) * initial implementation of setBidderConfig * fix ie11 test errors * Support new setBidderConfig format. Include props from both config and bidderConfig in _getConfig * Use core-js Set to avoid issues with IE * Fix tests in IE * put registerSyncs back on bidderFactory * run bidder event methods with bidder config enabled * Prebid 2.40.0 Release * Increment pre version * Conversant Bid Adapter checks pubcid directly (#4430) * Cookie Sync functionality (#4457) * changing PID param value for testing * cookie sync integration * merge from upstream * Staq Adapter: update with meta envelope (#4372) * initial dev * fix staq adapter name * fix hello world staq call * get hello world working again * add user agent collection * fix some unite tests * Add STAQ Analytics Adapter doc * clean up hello world * fix tests to play nice with browserstack * fix around issues with browserstack and deep equals of objects * dump variable env testing since we can't mod user agent stuff in browserstack * Update STAQ adapter to stop using deprecated utils for referrer * remove package-lock.json changes via master rebase * improve call frequency for ref util * change ajax content type * adjust ajax request to not expect whitelisting * remove superflous commented-out code * update event package to use meta information in envelope rather than per event basis * fix formatting * more formatting fixes * more formatting! * Rhythmone Adapter - schain support (#4414) Circle CI failing tests are not related to this PR. * Media.net Adapter: Support Prebid 3.0 (#4378) * Media.net Adapter: Support Prebid 3.0 * Media.net Adapter: add tests to increase code coverage * Vi Adapter: Passes additional param in the bid request (#4134) * Add focus check (cherry picked from commit 9d6d6dfb83580d6a5ffed8faa5762db48f8fd44d) * Pass focus as numeric value (cherry picked from commit 9fae56a637f87b0d39cc1d24eeb1f9ff9df88f64) * Add unit test (cherry picked from commit 946710f2e9960b3839613d4bdf730e57ba38a964) * Sovrn adapter updates: schain, digitrust, pixel syncing, and 3.0 upgrades (#4385) * schain and digitrust * pixel beacons * unit tests and fixes from testing * Prebid 3.0 updates * review fix * use backwards compatible flatMap impl * update pixel tests * unit test fix * update one more url to ssl * fixed test * review updates * TheMediaGrid Bid Adapter update (#4447) * Added Grid Bid Adapter * remove priceType from TheMediaGrid Bid Adapter * Add video support in Grid Bid Adapter * Added test parameter for video slot * update Grid Bid Adapter to set size in response bid * Update Grid Bid Adapter to support identical uids in parameters * Fix typo in test file for Grid Bid Adapter * Update The Grid Media Bidder Adapter to send refererInfo.referer as 'u' parameter in ad request * Hotfix for referrer in Grid Bid Adapter * Grid Bid Adapter: added wrapperType and wrappweVersion to the ad request * TheMediaGrid Bid Adapter: added sync url * TheMediaGrid Bid Adapter: added GDPR params to sync url * TheMediaGrid Bid Adapter: added tests for getUserSyncs function * Conversant Bid Adapter adds support for extended ids (#4462) * Adkernel 3.0 compatibility (#4477) * Rubicon Adapter pchain support (#4480) * rubicon pchain support * removed describe.only * Implemented changes required to provide support for video in the IX bidding adapter for Instream and Outstream contexts. (#4424) * Default size filter & KVP support (#4452) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * adding logic upto5 * adding support for removing and shuffle sizes * adding array split test * re-assign none standard size to the request * resolve duplicate format inside format array * update .md and adaptor file for KVP support * remove array helper includes * inforce two digit after decimal * RUn error check nothing on my side but error form another adapter * add id5id to prebid server bid adapter (#4468) * Added _pbjsGlobals for tracking renames. Resolves #4254 (#4419) * Feature/smart video (#4367) * Adding outstream video support. * Fixing unit test. * Adding video instream support. * Handling video startDelay parameter. * Improving unit tests. * Fixing indent. * Handling the request when videoMediaType context is not supported. * Changing maintainer mail address. * Remove video outstream specific code. * Unit test updated. * do not select element that gets removed after dfp render (#4423) * add smms adapter (#4439) * add smms adapter * re-run ci, why adigo adapter failed?? * review comments fix, remove deprecated functions, fix unit test * Prebid 2.41.0 release * Increment pre version * adds schain param (#4442) * Create newborntownWeb adapter (#4455) * Create newborntownWeb adapter * only https protocol * Provide criteoId to server by user.ext.eids (#4478) * ucfunnel adapter fix error message in debug mode (#4338) * Add a new ucfunnel Adapter and test page * Add a new ucfunnel Adapter and test page * 1. Use prebid lib in the repo to keep updated 2. Replace var with let 3. Put JSON.parse(JSON.stringify()) into try catch block * utils.getTopWindowLocation is a function * Change to modules from adapters * Migrate to module design * [Dev Fix] Remove width and height which can be got from ad unit id * Update ucfunnelBidAdapter to fit into new spec * Correct the endpoint. Fix the error of query string * Add test case for ucfunnelBidAdapter * Fix lint error * Update version number * Combine all checks on bid request * Add GDPR support for ucfunnel adapter * Add in-stream video and native support for ucfunnel adapter * Remove demo page. Add more test cases. * Change request method from POST to GET * Remove unnecessary comment * Support vastXml and vastUrl for video request * update TTL to 30 mins * Avoid using arrow function which is not discuraged in mocha * ucfunnel tdid support * ucfunnel fix error message in debug mode * explicitly check undefined to allow falsey values in getConfig (#4486) * Conversant Bid Adapter handles vast xml (#4492) * [feature] Add a config list of submodules that require refreshing the stored ID after each bid request (#4325) * add a feature to always refresh stored id on each bid request for submodules that require that * update test comments * Prebid 2.42.0 Release * Increment pre version * Make adhese adapter prebid 3.0 compatible (#4507) * Added 'adhese' attribute to bid that contains meta data - Jira AD-2642 * added DALE to adhese determination * extra config option: no format, but use size array as format string * Read sizes from mediaTypes.banner.sizes + Apply Eslint suggestions * Use map and join, add originData to response * properly use originData obj * Remove duplicated ids * Update tests * BugFix: Site id missing (#4467) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * adding site id * adding placement and siteis * site id param test case * removing deprecated functions * correcting test cases * indentation * test cases fix * Handle GDPR-Applies correctly. Add support for liveramp id. Co-authored-by: robdubois <53589945+robdubois@users.noreply.github.com> Co-authored-by: sumit116 Co-authored-by: nwlosinski Co-authored-by: Mike Chowla Co-authored-by: Bret Gorsline Co-authored-by: bretg Co-authored-by: Artem Seryak Co-authored-by: Jonathan Mullins Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: htang555 Co-authored-by: Bryan DeLong Co-authored-by: dpapworth-qc <50959025+dpapworth-qc@users.noreply.github.com> Co-authored-by: DeepthiNeeladri Co-authored-by: Harshad Mane Co-authored-by: Roman Co-authored-by: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Co-authored-by: Margaret Liu Co-authored-by: TJ Eastmond Co-authored-by: Robert Ray Martinez III Co-authored-by: Jason Snellbaker Co-authored-by: JonGoSonobi Co-authored-by: Vladimir Fedoseev Co-authored-by: DJ Rosenbaum Co-authored-by: Alex Khmelnitsky Co-authored-by: adxpremium <55161519+adxpremium@users.noreply.github.com> Co-authored-by: Jimmy Tu Co-authored-by: Pierre-Antoine Durgeat Co-authored-by: Eric Harper Co-authored-by: Telaria Engineering <36203956+telariaEng@users.noreply.github.com> Co-authored-by: ujuettner Co-authored-by: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Co-authored-by: PWyrembak Co-authored-by: susyt Co-authored-by: Max Crawford Co-authored-by: Pascal S Co-authored-by: Will Chapin Co-authored-by: Lemma Dev <54662130+lemmadev@users.noreply.github.com> Co-authored-by: Denis Logachov Co-authored-by: Léonard Labat Co-authored-by: onlsol <48312668+onlsol@users.noreply.github.com> Co-authored-by: Paul Yang Co-authored-by: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Co-authored-by: Mike Sperone Co-authored-by: sdbaron Co-authored-by: djaxbidder <55269794+djaxbidder@users.noreply.github.com> Co-authored-by: turktelssp <54801433+turktelssp@users.noreply.github.com> Co-authored-by: nkmt <45026101+strong-zero@users.noreply.github.com> Co-authored-by: Mutasem Aldmour Co-authored-by: r-schweitzer <50628828+r-schweitzer@users.noreply.github.com> Co-authored-by: Isaac A. Dettman Co-authored-by: Adasta Media <55529969+Adasta2019@users.noreply.github.com> Co-authored-by: mamatic <52153441+mamatic@users.noreply.github.com> Co-authored-by: Konduit <55142865+konduit-de… * Prebid core: make GDPR/USP consent data available without requiring an auction (#8185) * Load USP consent data on page load * Load GPDR consent data on page load * Update jsdoc * add image userSync and change default currency (#8244) Co-authored-by: Ignat Khaylov * Colossus Bid Adapter: fix buildRequests (#8236) * 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 Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Mykhailo Yaremchuk * Yandex Bid Adapter: initial release (#8183) * add Yandex Bidder Adapter * add support for adomains Co-authored-by: Taras Saveliev * LiveIntent Id Submodule: Update live-connect build dependency to 2.3.1 (#8198) * Update live-connect to 2.3.1 * Fix tests * Update release version Co-authored-by: Viktor Dreiling Co-authored-by: Viktor Dreiling Co-authored-by: Viktor Dreiling * Adman Bid Adapter: update sync url (#8250) * Add Adman bid adapter * Add supportedMediaTypes property * Update ADman Media bidder adapter * Remove console.log * Fix typo * revert package-json.lock * Delete package-lock.json * back to original package-lock.json * catch pbjs error * catch pbjs error * catch pbjs error * log * remove eu url * remove eu url * remove eu url * remove eu url * remove eu url * Update admanBidAdapter.js add consnet to sync url * Update admanBidAdapter.js fix import * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js check consent object data availability * сompatible with prebid v5 * add Lotame Panorama ID * update getUserSyncs * fix * fix tests * remove package-lock.json * update sync url * update test Co-authored-by: minoru katogi Co-authored-by: minoru katogi Co-authored-by: ADman Media Co-authored-by: SmartyAdman * Glimpse: update api and request shape, optimize and refactor (#8237) - Update Glimpse prebid api endpoint - Pass gvlid to storage manager for gdpr use cases - Include consent info in query params for better efficiency and privacy on the handling of user data - Update the request body shape to optimize the size of the request - update fpd shape and optimize fpd object - Add appendQueryParam and optimizeObject util functions - Remove unnecessary util functions - Refactor and rename of some util functions for improved simplicity - Rename variables for clarity - Update jsdoc comments - Reformat the files - Update test spec: add fpd unit test and remove unnecessary tests * Revert "Glimpse: update api and request shape, optimize and refactor (#8237)" (#8254) This reverts commit 28b4c69e0b96578536b06b750e9fad2f5ab7cc1b. * Seeding alliance Adapter: multiple replacement of auction price bugfix (#8256) * 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 Co-authored-by: Jonas Hilsen * Biddo Bid Adapter: add new bid adapter (#8206) * Add meta.adomain support * Update test * Add Biddo Adapter * Update endpoint * Fix tested module * Update zone id for tests Co-authored-by: Andrew Lays * consumableBidAdapter - add bidResponse fields (#8252) * Adtelligent Bid Adapter: add JANet adapter alias (#8102) * add Janet Adapter * Url add * rerun * Removed MF alias from adapter, since there is a separate adapter Rewrite antiAlias Test * Fix linter * Add DistroScale Bid Adapter (#8221) * Add DistroScale Bid Adapter * linted code Clean up code style issues * Prebid 6.19.0 release * ADD yahoo bidder ssp Co-authored-by: Bill Newman Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Mykhailo Yaremchuk Co-authored-by: bretg Co-authored-by: Aleksa Trajkovic Co-authored-by: Jason Piros Co-authored-by: CompassSSP <95415988+CompassSSP@users.noreply.github.com> Co-authored-by: Demetrio Girardi Co-authored-by: Anthony Boozan Co-authored-by: AnthonyBoozan Co-authored-by: Robert Ray Martinez III Co-authored-by: Grumft <97535031+Grumft@users.noreply.github.com> Co-authored-by: Roberto Hsu Wu Co-authored-by: Roberto Hsu Co-authored-by: Denise Balesteros Silva Co-authored-by: Karim Mourra Co-authored-by: Chris Huie Co-authored-by: johanbrandmetrics <91625093+johanbrandmetrics@users.noreply.github.com> Co-authored-by: Léonard Labat Co-authored-by: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Co-authored-by: Olivier Co-authored-by: Lisa Benmore Co-authored-by: Samuel Adu Co-authored-by: slimkrazy Co-authored-by: philan15 <37775368+philan15@users.noreply.github.com> Co-authored-by: Anna Philippova Co-authored-by: Chris Pabst Co-authored-by: Maxim Pakhomov <70204615+maximpakhomov@users.noreply.github.com> Co-authored-by: tamarm <40788385+tamarm-perion@users.noreply.github.com> Co-authored-by: omerko Co-authored-by: Omer Koren Co-authored-by: AnnaPerion Co-authored-by: Oran Hollaender Co-authored-by: tamirnPerion <44399211+tamirnPerion@users.noreply.github.com> Co-authored-by: IOTiagoFaria <76956619+IOTiagoFaria@users.noreply.github.com> Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: lunamedia <73552749+lunamedia@users.noreply.github.com> Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: Samiul Amin Shanto <93644987+samiul-shanto@users.noreply.github.com> Co-authored-by: Steven Ho Co-authored-by: Rich Audience Co-authored-by: sgimenez Co-authored-by: Mehdi Bouallagui <45876988+mbouallagui@users.noreply.github.com> Co-authored-by: Tiago Peczenyj Co-authored-by: Antoine Zazzera Co-authored-by: johnwier <49074029+johnwier@users.noreply.github.com> Co-authored-by: Noam Tzuberi Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur Co-authored-by: Eric Harper Co-authored-by: Anthony Lauzon Co-authored-by: jkthomas Co-authored-by: Tomasz Januszek Co-authored-by: Myhedin Zika <91465570+ZikaMyhedin@users.noreply.github.com> Co-authored-by: kyoya-takei <50602864+kyoya-takei@users.noreply.github.com> Co-authored-by: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Co-authored-by: Azhar Co-authored-by: Mike <44584080+mike-vibrant@users.noreply.github.com> Co-authored-by: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Co-authored-by: Wojciech Biały Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Malkov Mikhail Co-authored-by: onlsol <48312668+onlsol@users.noreply.github.com> Co-authored-by: Alexander Co-authored-by: harpere Co-authored-by: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Co-authored-by: Mikael Lundin Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: rtbh-lotani <83652735+rtbh-lotani@users.noreply.github.com> Co-authored-by: guiann Co-authored-by: Elad Yosifon Co-authored-by: corentinverpillat <90328658+corentinverpillat@users.noreply.github.com> Co-authored-by: ramyferjaniadot <90328697+ramyferjaniadot@users.noreply.github.com> Co-authored-by: Alexandre Lorin Co-authored-by: li-ran <70167305+li-ran@users.noreply.github.com> Co-authored-by: IQZoneAdx <88879712+IQZoneAdx@users.noreply.github.com> Co-authored-by: Marsel Co-authored-by: Artem Aleksashkin Co-authored-by: Artem Aleksashkin Co-authored-by: Serhii Mozhaiskyi <57671975+smozhaiskyi-rubi@users.noreply.github.com> Co-authored-by: JulieLorin Co-authored-by: Sasan Farrokh <92724385+sasanfarokh@users.noreply.github.com> Co-authored-by: MK Platform <88486298+mediakeys-platform@users.noreply.github.com> Co-authored-by: François Maturel Co-authored-by: eknis Co-authored-by: Denis Logachov Co-authored-by: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Co-authored-by: Udi Talias ⚛️ Co-authored-by: roman Co-authored-by: Miguel Peixe Co-authored-by: rajsidhunovatiq <79534312+rajsidhunovatiq@users.noreply.github.com> Co-authored-by: novatiq <79258366+novatiq@users.noreply.github.com> Co-authored-by: Kanchika - Automatad Co-authored-by: Gabriel Chicoye Co-authored-by: Rachel Joyce Co-authored-by: Rachel Joyce Co-authored-by: Yoko OYAMA Co-authored-by: pchrominski Co-authored-by: pchrominski Co-authored-by: mediaconsortium-develop <76139568+mediaconsortium-develop@users.noreply.github.com> Co-authored-by: JulieLorin Co-authored-by: BizzClick <73241175+BizzClick@users.noreply.github.com> Co-authored-by: David Carver <54326287+david-carver@users.noreply.github.com> Co-authored-by: Love Sharma Co-authored-by: Miko Stern <37616476+mikomgk@users.noreply.github.com> Co-authored-by: Alexander Pykhteyev Co-authored-by: apykhteyev Co-authored-by: relaido <63339139+relaido@users.noreply.github.com> Co-authored-by: ishigami_shingo Co-authored-by: cmertv-sishigami Co-authored-by: t_bun Co-authored-by: n.maeura Co-authored-by: John Rosendahl Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: Surovenko Alexey Co-authored-by: Ian <68525743+decaffeinatedio@users.noreply.github.com> Co-authored-by: minh-daole-ttd <54119199+minh-daole-ttd@users.noreply.github.com> Co-authored-by: pnh-pubx <73683023+pnh-pubx@users.noreply.github.com> Co-authored-by: Phaneendra Hegde Co-authored-by: Hendrik Iseke <53309111+hendrikiseke1979@users.noreply.github.com> Co-authored-by: Patrick McCann Co-authored-by: Monis Qadri Co-authored-by: monis.q Co-authored-by: vladi-mmg Co-authored-by: Abimael Martinez Co-authored-by: el-chuck Co-authored-by: Bernhard Pickenbrock Co-authored-by: Prebid.js automated release Co-authored-by: Vladimir Fedoseev Co-authored-by: Vladimir Fedoseev Co-authored-by: m-oranskaya <99481039+m-oranskaya@users.noreply.github.com> Co-authored-by: OronW <41260031+OronW@users.noreply.github.com> Co-authored-by: noamtzu Co-authored-by: Faisal Islam <93644923+faisalvs@users.noreply.github.com> Co-authored-by: Faisal Islam Co-authored-by: Wei Wong <98759502+wwongkargo@users.noreply.github.com> Co-authored-by: Jeremy Sadwith Co-authored-by: Wei Wong Co-authored-by: Itay Nave <38345760+itaynave@users.noreply.github.com> Co-authored-by: kouichi matsumoto Co-authored-by: dveljovicTX <69788339+dveljovicTX@users.noreply.github.com> Co-authored-by: Michael Callari Co-authored-by: Elber Carneiro <81249884+ym-elber@users.noreply.github.com> Co-authored-by: TM Co-authored-by: Tomasz Mielcarz Co-authored-by: Alexander Clouter Co-authored-by: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Co-authored-by: maksym.voloshyn Co-authored-by: Dragan Bajcic Co-authored-by: Dejan Grbavcic Co-authored-by: ebrandmark2 <73907684+ebrandmark2@users.noreply.github.com> Co-authored-by: Sergey Derbush <66130511+sderbush-tt@users.noreply.github.com> Co-authored-by: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Co-authored-by: omerBrowsi <54346241+omerBrowsi@users.noreply.github.com> Co-authored-by: ghguo Co-authored-by: Adprime <64427228+Adprime@users.noreply.github.com> Co-authored-by: Aigolkin1991 Co-authored-by: balajimediafuse <87535823+balajimediafuse@users.noreply.github.com> Co-authored-by: Takaaki.Kojima Co-authored-by: hisui Co-authored-by: 木田 愛一郎 Co-authored-by: Takahashi Kenta Co-authored-by: Nitin Nimbalkar <96475150+nitin0610@users.noreply.github.com> Co-authored-by: Jason Lydon <95770514+ftxmoJason@users.noreply.github.com> Co-authored-by: Jason Lydon Co-authored-by: Pavlo Kyrylenko Co-authored-by: Pavlo Co-authored-by: Timothy Ace Co-authored-by: Andrew Fuchs Co-authored-by: Timothy M. Ace Co-authored-by: Tim Ace Co-authored-by: readpeaktuomo <66239046+readpeaktuomo@users.noreply.github.com> Co-authored-by: Damyan Co-authored-by: Marcin Grzebyk <35067477+marcin15g@users.noreply.github.com> Co-authored-by: supadm <98890970+supadm@users.noreply.github.com> Co-authored-by: sung.chung Co-authored-by: Petre Damoc Co-authored-by: Yohan Boutin Co-authored-by: Mark Kuhar Co-authored-by: Faisal Islam <100519197+iosfaisal@users.noreply.github.com> Co-authored-by: jsfledd Co-authored-by: Joshua Fledderjohn Co-authored-by: Rocco Barbini <46724608+rbarbini-ias@users.noreply.github.com> Co-authored-by: nkloeber <100145701+nkloeber@users.noreply.github.com> Co-authored-by: Salomon Rada Co-authored-by: robdubois <53589945+robdubois@users.noreply.github.com> Co-authored-by: sumit116 Co-authored-by: nwlosinski Co-authored-by: Mike Chowla Co-authored-by: Bret Gorsline Co-authored-by: Artem Seryak Co-authored-by: Jonathan Mullins Co-authored-by: htang555 Co-authored-by: Bryan DeLong Co-authored-by: dpapworth-qc <50959025+dpapworth-qc@users.noreply.github.com> Co-authored-by: DeepthiNeeladri Co-authored-by: Harshad Mane Co-authored-by: Roman Co-authored-by: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Co-authored-by: Margaret Liu Co-authored-by: TJ Eastmond Co-authored-by: Jason Snellbaker Co-authored-by: JonGoSonobi Co-authored-by: DJ Rosenbaum Co-authored-by: Alex Khmelnitsky Co-authored-by: adxpremium <55161519+adxpremium@users.noreply.github.com> Co-authored-by: Jimmy Tu Co-authored-by: Pierre-Antoine Durgeat Co-authored-by: Telaria Engineering <36203956+telariaEng@users.noreply.github.com> Co-authored-by: ujuettner Co-authored-by: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Co-authored-by: PWyrembak Co-authored-by: susyt Co-authored-by: Max Crawford Co-authored-by: Pascal S Co-authored-by: Will Chapin Co-authored-by: Lemma Dev <54662130+lemmadev@users.noreply.github.com> Co-authored-by: Paul Yang Co-authored-by: Mike Sperone Co-authored-by: sdbaron Co-authored-by: djaxbidder <55269794+djaxbidder@users.noreply.github.com> Co-authored-by: turktelssp <54801433+turktelssp@users.noreply.github.com> Co-authored-by: nkmt <45026101+strong-zero@users.noreply.github.com> Co-authored-by: Mutasem Aldmour Co-authored-by: r-schweitzer <50628828+r-schweitzer@users.noreply.github.com> Co-authored-by: Isaac A. Dettman Co-authored-by: Adasta Media <55529969+Adasta2019@users.noreply.github.com> Co-authored-by: mamatic <52153441+mamatic@users.noreply.github.com> Co-authored-by: Konduit <55142865+konduit-dev@users.noreply.github.com> Co-authored-by: TinchoF <50110327+TinchoF@users.noreply.github.com> Co-authored-by: Jaimin Panchal <7393273+jaiminpanchal27@users.noreply.github.com> Co-authored-by: Jaimin Panchal Co-authored-by: Sergio Co-authored-by: Wayne Yang Co-authored-by: Cody Bonney Co-authored-by: evanmsmrtb Co-authored-by: hdeodhar <35999856+hdeodhar@users.noreply.github.com> Co-authored-by: Oz Weiss Co-authored-by: Janko Ulaga Co-authored-by: thomas-33across <44033452+thomas-33across@users.noreply.github.com> Co-authored-by: Finteza Analytics <45741245+finteza@users.noreply.github.com> Co-authored-by: Vadim Mazzherin Co-authored-by: Hendrik Iseke <39734979+hiseke@users.noreply.github.com> Co-authored-by: Anand Venkatraman Co-authored-by: Eyas Ranjous Co-authored-by: Michael Co-authored-by: hbanalytics <55453525+hbanalytics@users.noreply.github.com> Co-authored-by: Index Exchange 3 Prebid Team Co-authored-by: Michael Kuryshev Co-authored-by: Roffray Co-authored-by: rumesh Co-authored-by: oasis <2394426+bmwcmw@users.noreply.github.com> Co-authored-by: Nepomuk Seiler Co-authored-by: John Salis Co-authored-by: OneTagDevOps <38786435+OneTagDevOps@users.noreply.github.com> Co-authored-by: Ankit Prakash Co-authored-by: Dan Co-authored-by: romanantropov <45817046+romanantropov@users.noreply.github.com> Co-authored-by: msm0504 <51493331+msm0504@users.noreply.github.com> Co-authored-by: vrtcal-dev <50931150+vrtcal-dev@users.noreply.github.com> Co-authored-by: colbertk <50499465+colbertk@users.noreply.github.com> Co-authored-by: Steve Alliance Co-authored-by: 7XBID00 <52267720+7XBID00@users.noreply.github.com> Co-authored-by: Tomas Kovtun Co-authored-by: Gena Co-authored-by: Jonathan Mullins Co-authored-by: ucfunnel <39581136+ucfunnel@users.noreply.github.com> Co-authored-by: ACannuniRP <57228257+ACannuniRP@users.noreply.github.com> Co-authored-by: Hugo Duthil Co-authored-by: skazedo Co-authored-by: 胡雨軒 Петр Co-authored-by: Konrad Dulemba Co-authored-by: Mariya Mego <31904600+mash-a@users.noreply.github.com> Co-authored-by: Daniel Cassidy Co-authored-by: kpis-msa <50609476+kpis-msa@users.noreply.github.com> Co-authored-by: Marcian123 Co-authored-by: koji-eguchi <50477903+koji-eguchi@users.noreply.github.com> Co-authored-by: sourabhg Co-authored-by: Alexis Andrieu Co-authored-by: Vlad Gurgov Co-authored-by: Richard Lee <14349+dlackty@users.noreply.github.com> Co-authored-by: Alex Co-authored-by: Vladislav Yatsun Co-authored-by: vincentproxistore <56686565+vincentproxistore@users.noreply.github.com> Co-authored-by: Rich Snapp Co-authored-by: Rade Popovic <32302052+nanointeractive@users.noreply.github.com> Co-authored-by: Matt Quirion Co-authored-by: rhythmonebhaines <49991465+rhythmonebhaines@users.noreply.github.com> Co-authored-by: binoy-chitale Co-authored-by: Alex Pashkov Co-authored-by: Scott Co-authored-by: tadam75 Co-authored-by: Veronica Kim <43146383+vkimcm@users.noreply.github.com> Co-authored-by: songtungmtp <57524426+songtungmtp@users.noreply.github.com> Co-authored-by: z-sunshine <33084773+z-sunshine@users.noreply.github.com> Co-authored-by: Sander Co-authored-by: Moshe Moses Co-authored-by: Ignat Khaylov Co-authored-by: Ignat Khaylov Co-authored-by: Saveliev Taras Co-authored-by: Taras Saveliev Co-authored-by: Viktor Dreiling <34981284+3link@users.noreply.github.com> Co-authored-by: Viktor Dreiling Co-authored-by: Viktor Dreiling Co-authored-by: Viktor Dreiling Co-authored-by: SmartyAdman <59048845+SmartyAdman@users.noreply.github.com> Co-authored-by: minoru katogi Co-authored-by: minoru katogi Co-authored-by: ADman Media Co-authored-by: SmartyAdman Co-authored-by: EddieYu <32135564+eddieyu1998@users.noreply.github.com> Co-authored-by: SeedingAllianceTech <55976067+SeedingAllianceTech@users.noreply.github.com> Co-authored-by: Jonas Hilsen Co-authored-by: llays Co-authored-by: Andrew Lays Co-authored-by: duancg Co-authored-by: Alejandro Villanueva --- .babelrc.js | 35 +- .circleci/config.yml | 4 +- .eslintrc.js | 9 +- .github/workflows/issue_tracker.yml | 89 + .gitignore | 1 + README.md | 22 +- RELEASE_SCHEDULE.md | 68 +- allowedModules.js | 7 - babelConfig.js | 30 + browsers.json | 50 +- gulpfile.js | 201 +- integrationExamples/gpt/adloox.html | 6 +- integrationExamples/gpt/amp/creative.html | 44 +- integrationExamples/gpt/esp_example.html | 177 + ...le.html => hadronRtdProvider_example.html} | 18 +- .../gpt/idImportLibrary_example.html | 4 +- .../gpt/idward_segments_example.html | 112 + integrationExamples/gpt/userId_example.html | 7 +- .../gpt/weboramaRtdProvider_example.html | 254 +- .../gpt/x-domain/creative.html | 92 +- karma.conf.maker.js | 20 +- modules.json | 3 +- modules/.submodules.json | 9 +- modules/33acrossBidAdapter.js | 293 +- modules/adagioBidAdapter.js | 57 +- modules/adagioBidAdapter.md | 2 +- modules/adbookpspBidAdapter.js | 66 +- modules/addefendBidAdapter.js | 1 + modules/adgenerationBidAdapter.js | 24 +- modules/adhashBidAdapter.js | 94 +- modules/adheseBidAdapter.js | 9 +- modules/adkernelAdnAnalyticsAdapter.js | 2 +- modules/adkernelBidAdapter.js | 233 +- modules/adlivetechBidAdapter.md | 61 + modules/adlooxAdServerVideo.js | 4 +- modules/adlooxAnalyticsAdapter.js | 37 +- modules/adlooxAnalyticsAdapter.md | 1 + modules/adlooxRtdProvider.js | 32 +- modules/admanBidAdapter.js | 15 +- modules/admaruBidAdapter.js | 81 + modules/admaruBidAdapter.md | 34 + modules/admixerBidAdapter.js | 5 +- modules/adnowBidAdapter.js | 8 +- modules/adnuntiusBidAdapter.js | 9 +- modules/adnuntiusRtdProvider.js | 96 + modules/adnuntiusRtdProvider.md | 41 + modules/adomikAnalyticsAdapter.js | 55 +- modules/adotBidAdapter.js | 1087 +- modules/adotBidAdapter.md | 50 +- modules/adplusBidAdapter.js | 203 + modules/adplusBidAdapter.md | 39 + modules/adpod.js | 67 +- modules/adprimeBidAdapter.js | 13 +- modules/adqueryBidAdapter.js | 2 +- modules/adqueryIdSystem.js | 103 + modules/adqueryIdSystem.md | 35 + modules/adrelevantisBidAdapter.js | 32 +- modules/adrinoBidAdapter.js | 74 + modules/adrinoBidAdapter.md | 45 + modules/adriverBidAdapter.js | 12 +- modules/adriverIdSystem.js | 83 + modules/adriverIdSystem.md | 19 + modules/adtargetBidAdapter.js | 10 +- modules/adtelligentBidAdapter.js | 22 +- modules/adtrueBidAdapter.js | 2 +- modules/advangelistsBidAdapter.js | 11 +- modules/adxcgBidAdapter.md | 43 +- modules/adxpremiumAnalyticsAdapter.js | 6 +- modules/adyoulikeBidAdapter.js | 52 +- modules/afpBidAdapter.js | 8 +- modules/airgridRtdProvider.js | 2 +- modules/akamaiDapRtdProvider.js | 2 +- modules/amxBidAdapter.js | 2 +- modules/aniviewBidAdapter.js | 2 +- modules/appnexusBidAdapter.js | 122 +- modules/apstreamBidAdapter.js | 2 +- modules/asealBidAdapter.js | 58 + modules/asealBidAdapter.md | 52 + modules/automatadBidAdapter.js | 17 +- modules/axonixBidAdapter.js | 2 +- modules/beachfrontBidAdapter.js | 54 +- modules/beopBidAdapter.js | 10 +- modules/betweenBidAdapter.js | 17 +- modules/bidViewability.js | 16 +- modules/bidViewabilityIO.js | 6 +- modules/biddoBidAdapter.js | 92 + modules/biddoBidAdapter.md | 30 + modules/big-richmediaBidAdapter.js | 117 + modules/big-richmediaBidAdapter.md | 82 + modules/bizzclickBidAdapter.js | 8 +- modules/bliinkBidAdapter.js | 5 +- modules/bluebillywigBidAdapter.js | 14 +- modules/brandmetricsRtdProvider.js | 168 + modules/brandmetricsRtdProvider.md | 40 + modules/bridgewellBidAdapter.js | 8 +- modules/browsiRtdProvider.js | 20 +- modules/ccxBidAdapter.js | 2 +- modules/cleanmedianetBidAdapter.js | 4 +- modules/codefuelBidAdapter.js | 4 +- modules/codefuelBidAdapter.md | 6 +- modules/colossussspBidAdapter.js | 77 +- modules/colossussspBidAdapter.md | 16 +- modules/compassBidAdapter.js | 208 + modules/compassBidAdapter.md | 79 + modules/concertBidAdapter.js | 2 +- modules/connectIdSystem.js | 4 +- modules/consentManagement.js | 283 +- modules/consentManagementUsp.js | 157 +- modules/consumableBidAdapter.js | 33 +- modules/consumableBidAdapter.md | 2 +- modules/conversantBidAdapter.js | 15 +- modules/craftBidAdapter.js | 32 +- modules/criteoBidAdapter.js | 32 +- modules/criteoIdSystem.js | 43 +- modules/currency.js | 34 +- modules/cwireBidAdapter.js | 66 +- modules/cwireBidAdapter.md | 13 +- modules/dacIdSystem.js | 57 + modules/dacIdSystem.md | 28 + modules/dailyhuntBidAdapter.js | 435 + modules/dailyhuntBidAdapter.md | 4 + modules/datablocksBidAdapter.js | 8 +- modules/dchain.js | 149 + modules/dchain.md | 45 + modules/debugging/bidInterceptor.js | 229 + modules/debugging/index.js | 62 + modules/debugging/pbsInterceptor.js | 38 + modules/deepintentBidAdapter.js | 2 +- modules/deepintentDpesIdSystem.js | 2 +- modules/dfpAdServerVideo.js | 31 +- modules/displayioBidAdapter.js | 157 + modules/displayioBidAdapter.md | 148 + modules/distroscaleBidAdapter.js | 262 + modules/distroscaleBidAdapter.md | 30 + modules/docereeBidAdapter.js | 11 +- modules/docereeBidAdapter.md | 2 + modules/dspxBidAdapter.js | 100 +- modules/dspxBidAdapter.md | 3 +- modules/e_volutionBidAdapter.js | 158 + modules/emx_digitalBidAdapter.js | 32 +- modules/engageyaBidAdapter.js | 134 +- modules/engageyaBidAdapter.md | 4 +- modules/eplanningBidAdapter.js | 3 +- modules/feedadBidAdapter.js | 4 +- modules/feedadBidAdapter.md | 6 +- modules/fluctBidAdapter.js | 20 +- modules/freewheel-sspBidAdapter.js | 6 + modules/ftrackIdSystem.js | 184 + modules/ftrackIdSystem.md | 72 + modules/futureads.md | 48 + modules/gamoshiBidAdapter.js | 47 +- modules/gdprEnforcement.js | 17 +- modules/gjirafaBidAdapter.js | 2 +- modules/glimpseBidAdapter.js | 129 +- modules/glomexBidAdapter.js | 6 +- modules/gmosspBidAdapter.js | 40 +- modules/gnetBidAdapter.js | 26 +- modules/gnetBidAdapter.md | 8 +- modules/goldbachBidAdapter.js | 1177 ++ modules/goldbachBidAdapter.md | 151 + modules/googleAnalyticsAdapter.js | 31 +- modules/googleAnalyticsAdapter.md | 1 + modules/gptPreAuction.js | 78 +- modules/gridBidAdapter.js | 80 +- modules/gridBidAdapter.md | 33 +- modules/gumgumBidAdapter.js | 123 +- modules/hadronIdSystem.js | 96 + modules/hadronIdSystem.md | 35 + modules/hadronRtdProvider.js | 254 + modules/hadronRtdProvider.md | 126 + modules/haloIdSystem.js | 2 +- modules/haloIdSystem.md | 39 +- modules/haloRtdProvider.js | 2 +- modules/haloRtdProvider.md | 129 +- modules/hybridBidAdapter.js | 10 +- modules/iasRtdProvider.js | 43 +- modules/id5AnalyticsAdapter.js | 2 +- modules/id5IdSystem.js | 2 +- modules/id5IdSystem.md | 12 +- modules/idImportLibrary.js | 53 +- modules/idImportLibrary.md | 4 + modules/idWardRtdProvider.js | 104 + modules/idWardRtdProvider.md | 44 + modules/imRtdProvider.js | 41 + modules/improvedigitalBidAdapter.js | 1070 +- modules/insticatorBidAdapter.js | 16 +- modules/instreamTracking.js | 8 +- modules/integr8BidAdapter.js | 2 +- modules/intentIqIdSystem.js | 2 +- modules/interactiveOffersBidAdapter.js | 15 +- modules/intersectionRtdProvider.js | 115 + modules/invibesBidAdapter.js | 80 +- modules/invibesBidAdapter.md | 3 +- modules/iqzoneBidAdapter.js | 37 +- modules/iqzoneBidAdapter.md | 16 + modules/ixBidAdapter.js | 322 +- modules/jixieBidAdapter.js | 2 +- modules/justIdSystem.js | 206 + modules/justIdSystem.md | 70 + modules/justpremiumBidAdapter.js | 21 +- modules/jwplayerRtdProvider.js | 89 +- modules/jwplayerRtdProvider.md | 38 +- modules/kargoBidAdapter.js | 33 +- modules/koblerBidAdapter.js | 2 +- modules/kubientBidAdapter.js | 88 +- modules/limelightDigitalBidAdapter.md | 4 +- modules/liveIntentIdSystem.js | 2 +- modules/livewrappedAnalyticsAdapter.js | 13 +- modules/livewrappedBidAdapter.js | 23 +- modules/lkqdBidAdapter.js | 234 + modules/lkqdBidAdapter.md | 2 +- modules/loglyliftBidAdapter.js | 79 + modules/loglyliftBidAdapter.md | 55 + modules/lotamePanoramaIdSystem.js | 90 +- modules/lunamediahbBidAdapter.js | 36 +- modules/luponmediaBidAdapter.js | 570 + modules/malltvBidAdapter.js | 12 +- modules/malltvBidAdapter.md | 115 +- modules/mantisBidAdapter.js | 2 +- modules/marsmediaBidAdapter.js | 4 +- modules/mass.js | 12 +- modules/mediaforceBidAdapter.js | 2 +- modules/mediafuseBidAdapter.js | 1145 ++ modules/mediafuseBidAdapter.md | 151 + modules/mediakeysBidAdapter.js | 37 +- modules/medianetAnalyticsAdapter.js | 47 +- modules/medianetRtdProvider.js | 8 +- modules/mediasquareBidAdapter.js | 43 +- modules/merkleIdSystem.js | 5 + modules/mgidBidAdapter.js | 2 +- modules/minutemediaBidAdapter.js | 360 + modules/minutemediaBidAdapter.md | 51 + modules/missenaBidAdapter.js | 15 +- modules/multibid/index.js | 2 +- modules/nativoBidAdapter.js | 148 +- modules/nextMillenniumBidAdapter.js | 85 +- modules/nextMillenniumBidAdapter.md | 5 +- modules/nextrollBidAdapter.js | 18 +- modules/nexx360BidAdapter.js | 149 + modules/nexx360BidAdapter.md | 35 + modules/nobidBidAdapter.js | 2 +- modules/novatiqIdSystem.js | 228 +- modules/novatiqIdSystem.md | 75 +- modules/oguryBidAdapter.js | 33 +- modules/oneVideoBidAdapter.js | 1 + modules/onetagBidAdapter.js | 24 +- modules/open8BidAdapter.js | 188 + modules/openwebBidAdapter.js | 10 +- modules/openxAnalyticsAdapter.js | 21 +- modules/openxBidAdapter.js | 21 +- modules/optimeraRtdProvider.js | 29 +- modules/orbidderBidAdapter.js | 33 +- modules/outbrainBidAdapter.js | 49 +- modules/outbrainBidAdapter.js~HEAD | 288 - modules/ozoneBidAdapter.js | 85 +- modules/parrableIdSystem.js | 16 +- modules/permutiveRtdProvider.js | 15 +- modules/pilotxBidAdapter.js | 147 + modules/pilotxBidAdapter.md | 50 + modules/pixfutureBidAdapter.js | 26 +- modules/prebidServerBidAdapter/index.js | 308 +- modules/prebidmanagerAnalyticsAdapter.js | 2 +- modules/priceFloors.js | 67 +- modules/proxistoreBidAdapter.js | 2 - modules/pubCommonId.js | 2 +- modules/pubgeniusBidAdapter.js | 2 +- modules/publinkIdSystem.js | 2 +- modules/pubmaticAnalyticsAdapter.js | 16 + modules/pubmaticBidAdapter.js | 22 +- modules/pubxaiAnalyticsAdapter.js | 30 +- modules/quantcastBidAdapter.js | 14 +- modules/readpeakBidAdapter.js | 41 +- modules/readpeakBidAdapter.md | 55 +- modules/reconciliationRtdProvider.js | 8 +- modules/relaidoBidAdapter.js | 155 +- modules/rhythmoneBidAdapter.js | 1 + modules/richaudienceBidAdapter.js | 26 +- modules/riseBidAdapter.js | 343 +- modules/roxotAnalyticsAdapter.js | 6 +- modules/rtbhouseBidAdapter.js | 28 +- modules/rtdModule/index.js | 91 +- modules/rubiconAnalyticsAdapter.js | 192 +- modules/rubiconBidAdapter.js | 76 +- modules/s2sTesting.js | 77 +- modules/saambaaBidAdapter.js | 419 + modules/saambaaBidAdapter.md | 133 +- modules/seedingAllianceBidAdapter.js | 10 +- modules/seedtagBidAdapter.js | 11 +- modules/sharedIdSystem.js | 28 +- modules/sharethroughBidAdapter.js | 31 +- modules/showheroes-bsBidAdapter.js | 15 +- modules/sigmoidAnalyticsAdapter.js | 6 +- modules/sirdataRtdProvider.js | 8 +- modules/sizeMappingV2.js | 271 +- modules/slimcutBidAdapter.js | 3 +- modules/smaatoBidAdapter.js | 16 +- modules/smarthubBidAdapter.js | 187 + modules/smarthubBidAdapter.md | 94 + modules/smarticoBidAdapter.js | 6 +- modules/smartxBidAdapter.js | 18 +- modules/sortableAnalyticsAdapter.js | 9 +- modules/sovrnAnalyticsAdapter.js | 15 +- modules/sovrnBidAdapter.js | 115 +- modules/sovrnBidAdapter.md | 82 +- modules/spotxBidAdapter.js | 3 +- modules/sspBCBidAdapter.js | 118 +- modules/synacormediaBidAdapter.js | 81 +- modules/talkadsBidAdapter.js | 6 +- modules/tappxBidAdapter.js | 17 +- modules/targetVideoBidAdapter.js | 189 + modules/targetVideoBidAdapter.md | 34 + modules/teadsBidAdapter.js | 5 +- modules/telariaBidAdapter.js | 57 +- modules/trionBidAdapter.js | 3 +- modules/trustpidSystem.js | 197 + modules/trustpidSystem.md | 45 + modules/trustxBidAdapter.js | 104 +- modules/ttdBidAdapter.js | 519 + modules/ttdBidAdapter.md | 122 + modules/ucfunnelBidAdapter.js | 3 +- modules/uid2IdSystem.js | 2 +- modules/undertoneBidAdapter.js | 27 +- modules/unicornBidAdapter.js | 2 +- modules/unrulyBidAdapter.js | 1 + modules/userId/eids.js | 53 +- modules/userId/eids.md | 27 +- modules/userId/index.js | 151 +- modules/userId/userId.md | 41 +- modules/userIdTargeting.js | 2 +- modules/ventes.md | 71 + modules/ventesBidAdapter.js | 155 +- modules/ventesBidAdapter.md | 1 - modules/verizonMediaIdSystem.js | 4 +- modules/vibrantmediaBidAdapter.js | 220 + modules/vibrantmediaBidAdapter.md | 92 + modules/vidazooBidAdapter.js | 7 +- modules/vidoomyBidAdapter.js | 181 +- modules/vidoomyBidAdapter.md | 6 +- modules/viewability.js | 177 + modules/viewability.md | 87 + modules/viewdeosDXBidAdapter.js | 6 +- modules/visxBidAdapter.js | 36 +- modules/visxBidAdapter.md | 10 +- modules/voxBidAdapter.js | 8 +- modules/waardexBidAdapter.js | 4 +- modules/weboramaRtdProvider.js | 593 +- modules/weboramaRtdProvider.md | 135 +- modules/welectBidAdapter.js | 106 + modules/widespaceBidAdapter.js | 13 +- modules/winrBidAdapter.js | 40 +- modules/yahoosspBidAdapter.js | 12 +- modules/yahoosspBidAdapter.md | 2 +- modules/yandexBidAdapter.js | 107 + modules/yandexBidAdapter.md | 40 + modules/yieldlabBidAdapter.js | 88 +- modules/yieldlabBidAdapter.md | 139 +- modules/yieldmoBidAdapter.js | 57 +- modules/yieldoneBidAdapter.js | 132 +- modules/yuktamediaAnalyticsAdapter.js | 10 +- modules/zeotapIdPlusIdSystem.js | 2 +- modules/zetaSspBidAdapter.md | 34 +- modules/zeta_global_sspAnalyticsAdapter.js | 97 + modules/zeta_global_sspAnalyticsAdapter.md | 24 + modules/zeta_global_sspBidAdapter.js | 30 +- package-lock.json | 13911 ++++++---------- package.json | 34 +- plugins/RequireEnsureWithoutJsonp.js | 23 - plugins/pbjsGlobals.js | 29 +- src/AnalyticsAdapter.js | 4 +- src/Renderer.js | 2 +- src/adRendering.js | 38 + src/adapterManager.js | 371 +- src/adapters/bidderFactory.js | 290 +- src/adloader.js | 6 +- src/auction.js | 257 +- src/auctionIndex.js | 85 + src/auctionManager.js | 5 +- src/bidderSettings.js | 69 + src/bidfactory.js | 24 +- src/config.js | 35 +- src/consentHandler.js | 101 + src/constants.json | 3 +- src/cpmBucketManager.js | 2 +- src/debugging.js | 50 +- src/events.js | 5 +- src/hook.js | 6 +- src/native.js | 52 +- src/polyfill.js | 18 + src/prebid.js | 106 +- src/secureCreatives.js | 187 +- src/sizeMapping.js | 49 +- src/storageManager.js | 41 +- src/targeting.js | 31 +- src/userSync.js | 2 +- src/utils.js | 109 +- src/video.js | 23 +- src/videoCache.js | 19 +- test/helpers/indexStub.js | 28 + test/helpers/prebidGlobal.js | 1 + test/helpers/syncPromise.js | 71 + test/spec/AnalyticsAdapter_spec.js | 2 +- test/spec/auctionmanager_spec.js | 359 +- test/spec/config_spec.js | 304 +- test/spec/debugging_spec.js | 35 +- .../longform/basic_w_bidderSettings.spec.js | 2 +- ...asic_w_custom_adserver_translation.spec.js | 2 +- .../e2e/longform/basic_w_priceGran.spec.js | 2 +- .../basic_w_requireExactDuration.spec.js | 2 +- .../basic_wo_brandCategoryExclusion.spec.js | 2 +- .../basic_wo_requireExactDuration.spec.js | 2 +- test/spec/integration/faker/googletag.js | 6 + test/spec/modules/33acrossBidAdapter_spec.js | 378 +- test/spec/modules/adagioBidAdapter_spec.js | 10 - test/spec/modules/adbookpspBidAdapter_spec.js | 20 +- .../modules/adgenerationBidAdapter_spec.js | 42 +- test/spec/modules/adhashBidAdapter_spec.js | 88 +- test/spec/modules/adheseBidAdapter_spec.js | 28 +- test/spec/modules/adlooxAdServerVideo_spec.js | 2 +- .../modules/adlooxAnalyticsAdapter_spec.js | 14 +- test/spec/modules/adlooxRtdProvider_spec.js | 2 +- test/spec/modules/admanBidAdapter_spec.js | 4 +- test/spec/modules/admaruBidAdapter_spec.js | 124 + test/spec/modules/adnuntiusBidAdapter_spec.js | 37 +- .../spec/modules/adnuntiusRtdProvider_spec.js | 145 + .../modules/adomikAnalyticsAdapter_spec.js | 14 +- test/spec/modules/adotBidAdapter_spec.js | 3418 +--- test/spec/modules/adplusBidAdapter_spec.js | 213 + test/spec/modules/adpod_spec.js | 247 +- test/spec/modules/adprimeBidAdapter_spec.js | 31 +- test/spec/modules/adqueryIdSystem_spec.js | 74 + .../modules/adrelevantisBidAdapter_spec.js | 4 +- test/spec/modules/adrinoBidAdapter_spec.js | 204 + test/spec/modules/adriverBidAdapter_spec.js | 37 + test/spec/modules/adriverIdSystem_spec.js | 84 + .../modules/adtelligentBidAdapter_spec.js | 2 +- test/spec/modules/adxcgBidAdapter_spec.js | 21 +- test/spec/modules/adyoulikeBidAdapter_spec.js | 116 +- test/spec/modules/afpBidAdapter_spec.js | 2 +- test/spec/modules/appnexusBidAdapter_spec.js | 128 +- test/spec/modules/asealBidAdapter_spec.js | 144 + test/spec/modules/automatadBidAdapter_spec.js | 30 +- .../spec/modules/beachfrontBidAdapter_spec.js | 66 +- test/spec/modules/beopBidAdapter_spec.js | 2 +- test/spec/modules/betweenBidAdapter_spec.js | 5 +- test/spec/modules/bidViewabilityIO_spec.js | 4 +- test/spec/modules/bidViewability_spec.js | 8 +- test/spec/modules/biddoBidAdapter_spec.js | 172 + .../modules/big-richmediaBidAdapter_spec.js | 310 + test/spec/modules/bizzclickBidAdapter_spec.js | 16 + test/spec/modules/bliinkBidAdapter_spec.js | 2 + .../modules/brandmetricsRtdProvider_spec.js | 191 + test/spec/modules/browsiRtdProvider_spec.js | 103 +- test/spec/modules/codefuelBidAdapter_spec.js | 2 +- .../modules/colossussspBidAdapter_spec.js | 24 +- test/spec/modules/compassBidAdapter_spec.js | 398 + .../spec/modules/consentManagementUsp_spec.js | 77 +- test/spec/modules/consentManagement_spec.js | 81 + .../spec/modules/consumableBidAdapter_spec.js | 88 +- .../spec/modules/conversantBidAdapter_spec.js | 71 +- test/spec/modules/criteoBidAdapter_spec.js | 124 +- test/spec/modules/criteoIdSystem_spec.js | 9 +- test/spec/modules/currency_spec.js | 51 +- test/spec/modules/cwireBidAdapter_spec.js | 165 +- test/spec/modules/dacIdSystem_spec.js | 51 + test/spec/modules/dailyhuntBidAdapter_spec.js | 404 + .../spec/modules/datablocksBidAdapter_spec.js | 6 +- test/spec/modules/dchain_spec.js | 329 + test/spec/modules/debugging_mod_spec.js | 451 + .../modules/deepintentDpesIdsystem_spec.js | 2 +- test/spec/modules/dfpAdServerVideo_spec.js | 53 + test/spec/modules/displayioBidAdapter_spec.js | 239 + .../modules/distroscaleBidAdapter_spec.js | 213 + test/spec/modules/docereeBidAdapter_spec.js | 10 +- test/spec/modules/dspxBidAdapter_spec.js | 35 +- .../spec/modules/e_volutionBidAdapter_spec.js | 242 + test/spec/modules/eids_spec.js | 19 +- .../modules/emx_digitalBidAdapter_spec.js | 10 + test/spec/modules/engageyaBidAdapter_spec.js | 428 +- .../modules/eplanningAnalyticsAdapter_spec.js | 2 +- test/spec/modules/feedadBidAdapter_spec.js | 15 + .../modules/fintezaAnalyticsAdapter_spec.js | 2 +- test/spec/modules/fluctBidAdapter_spec.js | 92 + .../modules/freewheel-sspBidAdapter_spec.js | 19 + test/spec/modules/ftrackIdSystem_spec.js | 238 + test/spec/modules/gdprEnforcement_spec.js | 2 +- test/spec/modules/glimpseBidAdapter_spec.js | 15 + test/spec/modules/gmosspBidAdapter_spec.js | 27 +- test/spec/modules/gnetBidAdapter_spec.js | 27 +- test/spec/modules/goldbachBidAdapter_spec.js | 1359 ++ test/spec/modules/gptPreAuction_spec.js | 241 +- test/spec/modules/gridBidAdapter_spec.js | 130 +- test/spec/modules/gumgumBidAdapter_spec.js | 58 +- test/spec/modules/hadronIdSystem_spec.js | 57 + test/spec/modules/hadronRtdProvider_spec.js | 762 + test/spec/modules/iasRtdProvider_spec.js | 86 +- test/spec/modules/id5AnalyticsAdapter_spec.js | 2 +- test/spec/modules/id5IdSystem_spec.js | 2 +- test/spec/modules/idWardRtdProvider_spec.js | 113 + test/spec/modules/idxIdSystem_spec.js | 2 +- test/spec/modules/imRtdProvider_spec.js | 54 + .../modules/improvedigitalBidAdapter_spec.js | 1065 +- .../spec/modules/insticatorBidAdapter_spec.js | 11 +- .../modules/intersectionRtdProvider_spec.js | 141 + test/spec/modules/invibesBidAdapter_spec.js | 152 +- test/spec/modules/iqzoneBidAdapter_spec.js | 26 + test/spec/modules/ixBidAdapter_spec.js | 536 +- test/spec/modules/justIdSystem_spec.js | 216 + .../modules/justpremiumBidAdapter_spec.js | 3 +- test/spec/modules/jwplayerRtdProvider_spec.js | 197 +- test/spec/modules/kargoBidAdapter_spec.js | 115 +- test/spec/modules/konduitWrapper_spec.js | 1 - test/spec/modules/kubientBidAdapter_spec.js | 270 +- .../limelightDigitalBidAdapter_spec.js | 357 +- test/spec/modules/liveIntentIdSystem_spec.js | 8 +- .../livewrappedAnalyticsAdapter_spec.js | 51 +- .../modules/livewrappedBidAdapter_spec.js | 31 + test/spec/modules/lkqdBidAdapter_spec.js | 323 + test/spec/modules/loglyliftBidAdapter_spec.js | 172 + .../modules/lotamePanoramaIdSystem_spec.js | 227 + .../modules/lunamediahbBidAdapter_spec.js | 28 +- .../spec/modules/luponmediaBidAdapter_spec.js | 412 + .../modules/malltvAnalyticsAdapter_spec.js | 2 +- test/spec/modules/mass_spec.js | 16 +- test/spec/modules/mediafuseBidAdapter_spec.js | 1442 ++ test/spec/modules/mediakeysBidAdapter_spec.js | 16 +- .../modules/medianetAnalyticsAdapter_spec.js | 39 +- .../modules/mediasquareBidAdapter_spec.js | 44 +- test/spec/modules/merkleIdSystem_spec.js | 18 + .../modules/minutemediaBidAdapter_spec.js | 405 + test/spec/modules/missenaBidAdapter_spec.js | 3 + test/spec/modules/multibid_spec.js | 1 - test/spec/modules/nativoBidAdapter_spec.js | 216 +- .../modules/nextMillenniumBidAdapter_spec.js | 78 +- test/spec/modules/nextrollBidAdapter_spec.js | 8 +- test/spec/modules/nexx360BidAdapter_spec.js | 140 + test/spec/modules/novatiqIdSystem_spec.js | 105 +- test/spec/modules/oguryBidAdapter_spec.js | 98 +- test/spec/modules/onetagBidAdapter_spec.js | 2 +- .../spec/modules/ooloAnalyticsAdapter_spec.js | 2 +- test/spec/modules/open8BidAdapter_spec.js | 258 + .../modules/openxAnalyticsAdapter_spec.js | 4 +- test/spec/modules/openxBidAdapter_spec.js | 5 + test/spec/modules/optimeraRtdProvider_spec.js | 65 +- .../modules/optimonAnalyticsAdapter_spec.js | 2 +- test/spec/modules/orbidderBidAdapter_spec.js | 21 + test/spec/modules/outbrainBidAdapter_spec.js | 94 +- .../modules/outbrainBidAdapter_spec.js~HEAD | 562 - test/spec/modules/ozoneBidAdapter_spec.js | 74 +- test/spec/modules/parrableIdSystem_spec.js | 2 +- test/spec/modules/pilotxBidAdapter_spec.js | 244 + .../modules/prebidServerBidAdapter_spec.js | 283 +- test/spec/modules/priceFloors_spec.js | 147 +- test/spec/modules/pubgeniusBidAdapter_spec.js | 6 +- test/spec/modules/publinkIdSystem_spec.js | 2 +- .../modules/pubmaticAnalyticsAdapter_spec.js | 15 +- test/spec/modules/pubmaticBidAdapter_spec.js | 38 +- .../modules/pubstackAnalyticsAdapter_spec.js | 2 +- .../modules/pubxaiAnalyticsAdapter_spec.js | 16 + test/spec/modules/readpeakBidAdapter_spec.js | 536 +- test/spec/modules/realTimeDataModule_spec.js | 246 +- test/spec/modules/relaidoBidAdapter_spec.js | 153 +- .../modules/relevantAnalyticsAdapter_spec.js | 2 +- .../modules/richaudienceBidAdapter_spec.js | 44 +- test/spec/modules/riseBidAdapter_spec.js | 334 +- test/spec/modules/rtbhouseBidAdapter_spec.js | 20 + .../modules/rubiconAnalyticsAdapter_spec.js | 415 +- test/spec/modules/rubiconAnalyticsSchema.json | 46 +- test/spec/modules/rubiconBidAdapter_spec.js | 80 +- .../modules/scaleableAnalyticsAdapter_spec.js | 2 +- test/spec/modules/seedtagBidAdapter_spec.js | 688 +- test/spec/modules/sharedIdSystem_spec.js | 58 +- .../modules/sharethroughBidAdapter_spec.js | 97 +- .../modules/showheroes-bsBidAdapter_spec.js | 2 + test/spec/modules/sizeMappingV2_spec.js | 628 +- test/spec/modules/smaatoBidAdapter_spec.js | 80 +- test/spec/modules/smarthubBidAdapter_spec.js | 392 + test/spec/modules/smartxBidAdapter_spec.js | 9 + .../modules/sortableAnalyticsAdapter_spec.js | 7 +- test/spec/modules/sovrnBidAdapter_spec.js | 746 +- test/spec/modules/sspBCBidAdapter_spec.js | 50 +- .../modules/stroeerCoreBidAdapter_spec.js | 2 +- .../modules/synacormediaBidAdapter_spec.js | 143 +- test/spec/modules/tappxBidAdapter_spec.js | 23 +- .../modules/targetVideoBidAdapter_spec.js | 96 + test/spec/modules/teadsBidAdapter_spec.js | 73 +- test/spec/modules/telariaBidAdapter_spec.js | 4 +- test/spec/modules/trustpidSystem_spec.js | 232 + test/spec/modules/trustxBidAdapter_spec.js | 151 +- test/spec/modules/ttdBidAdapter_spec.js | 1313 ++ test/spec/modules/undertoneBidAdapter_spec.js | 57 +- test/spec/modules/userId_spec.js | 306 +- .../modules/vibrantmediaBidAdapter_spec.js | 1237 ++ test/spec/modules/vidazooBidAdapter_spec.js | 4 +- test/spec/modules/vidoomyBidAdapter_spec.js | 53 +- test/spec/modules/viewability_spec.js | 280 + test/spec/modules/visxBidAdapter_spec.js | 142 +- test/spec/modules/weboramaRtdProvider_spec.js | 1164 +- test/spec/modules/welectBidAdapter_spec.js | 211 + test/spec/modules/widespaceBidAdapter_spec.js | 2 +- test/spec/modules/yahoosspBidAdapter_spec.js | 47 +- test/spec/modules/yandexBidAdapter_spec.js | 166 + test/spec/modules/yieldlabBidAdapter_spec.js | 117 + test/spec/modules/yieldmoBidAdapter_spec.js | 4 + test/spec/modules/yieldoneBidAdapter_spec.js | 387 +- .../spec/modules/zeotapIdPlusIdSystem_spec.js | 4 +- .../zeta_global_sspAnalyticsAdapter_spec.js | 427 + .../modules/zeta_global_sspBidAdapter_spec.js | 41 +- test/spec/native_spec.js | 102 +- test/spec/sizeMapping_spec.js | 2 +- test/spec/unit/core/adapterManager_spec.js | 172 +- test/spec/unit/core/auctionIndex_spec.js | 129 + test/spec/unit/core/bidderFactory_spec.js | 79 +- test/spec/unit/core/bidderSettings_spec.js | 123 + test/spec/unit/core/consentHandler_spec.js | 59 + test/spec/unit/core/storageManager_spec.js | 71 +- test/spec/unit/core/targeting_spec.js | 116 + test/spec/unit/pbjs_api_spec.js | 143 +- test/spec/unit/secureCreatives_spec.js | 169 +- test/spec/utils_spec.js | 45 + test/spec/videoCache_spec.js | 37 +- test/spec/video_spec.js | 112 +- test/test_deps.js | 9 + test/test_index.js | 4 +- wdio.conf.js | 2 +- webpack.conf.js | 55 +- 625 files changed, 55839 insertions(+), 22698 deletions(-) create mode 100644 .github/workflows/issue_tracker.yml create mode 100644 babelConfig.js create mode 100644 integrationExamples/gpt/esp_example.html rename integrationExamples/gpt/{haloRtdProvider_example.html => hadronRtdProvider_example.html} (87%) create mode 100644 integrationExamples/gpt/idward_segments_example.html create mode 100644 modules/adlivetechBidAdapter.md create mode 100644 modules/admaruBidAdapter.js create mode 100644 modules/admaruBidAdapter.md create mode 100644 modules/adnuntiusRtdProvider.js create mode 100644 modules/adnuntiusRtdProvider.md create mode 100644 modules/adplusBidAdapter.js create mode 100644 modules/adplusBidAdapter.md create mode 100644 modules/adqueryIdSystem.js create mode 100644 modules/adqueryIdSystem.md create mode 100644 modules/adrinoBidAdapter.js create mode 100644 modules/adrinoBidAdapter.md create mode 100644 modules/adriverIdSystem.js create mode 100644 modules/adriverIdSystem.md create mode 100644 modules/asealBidAdapter.js create mode 100644 modules/asealBidAdapter.md create mode 100644 modules/biddoBidAdapter.js create mode 100644 modules/biddoBidAdapter.md create mode 100644 modules/big-richmediaBidAdapter.js create mode 100644 modules/big-richmediaBidAdapter.md create mode 100644 modules/brandmetricsRtdProvider.js create mode 100644 modules/brandmetricsRtdProvider.md create mode 100644 modules/compassBidAdapter.js create mode 100644 modules/compassBidAdapter.md create mode 100644 modules/dacIdSystem.js create mode 100644 modules/dacIdSystem.md create mode 100644 modules/dailyhuntBidAdapter.js create mode 100644 modules/dchain.js create mode 100644 modules/dchain.md create mode 100644 modules/debugging/bidInterceptor.js create mode 100644 modules/debugging/index.js create mode 100644 modules/debugging/pbsInterceptor.js create mode 100644 modules/displayioBidAdapter.js create mode 100644 modules/displayioBidAdapter.md create mode 100644 modules/distroscaleBidAdapter.js create mode 100644 modules/distroscaleBidAdapter.md create mode 100644 modules/e_volutionBidAdapter.js create mode 100644 modules/ftrackIdSystem.js create mode 100644 modules/ftrackIdSystem.md create mode 100644 modules/futureads.md create mode 100644 modules/goldbachBidAdapter.js create mode 100644 modules/goldbachBidAdapter.md create mode 100644 modules/hadronIdSystem.js create mode 100644 modules/hadronIdSystem.md create mode 100644 modules/hadronRtdProvider.js create mode 100644 modules/hadronRtdProvider.md create mode 100644 modules/idWardRtdProvider.js create mode 100644 modules/idWardRtdProvider.md create mode 100644 modules/intersectionRtdProvider.js create mode 100644 modules/justIdSystem.js create mode 100644 modules/justIdSystem.md create mode 100644 modules/lkqdBidAdapter.js create mode 100644 modules/loglyliftBidAdapter.js create mode 100644 modules/loglyliftBidAdapter.md create mode 100755 modules/luponmediaBidAdapter.js create mode 100644 modules/mediafuseBidAdapter.js create mode 100644 modules/mediafuseBidAdapter.md create mode 100644 modules/minutemediaBidAdapter.js create mode 100644 modules/minutemediaBidAdapter.md create mode 100644 modules/nexx360BidAdapter.js create mode 100644 modules/nexx360BidAdapter.md create mode 100644 modules/open8BidAdapter.js delete mode 100644 modules/outbrainBidAdapter.js~HEAD create mode 100644 modules/pilotxBidAdapter.js create mode 100644 modules/pilotxBidAdapter.md create mode 100644 modules/saambaaBidAdapter.js create mode 100644 modules/smarthubBidAdapter.js create mode 100644 modules/smarthubBidAdapter.md create mode 100644 modules/targetVideoBidAdapter.js create mode 100644 modules/targetVideoBidAdapter.md create mode 100644 modules/trustpidSystem.js create mode 100644 modules/trustpidSystem.md create mode 100644 modules/ttdBidAdapter.js create mode 100644 modules/ttdBidAdapter.md create mode 100644 modules/ventes.md create mode 100644 modules/vibrantmediaBidAdapter.js create mode 100644 modules/vibrantmediaBidAdapter.md create mode 100644 modules/viewability.js create mode 100644 modules/viewability.md create mode 100644 modules/welectBidAdapter.js create mode 100644 modules/yandexBidAdapter.js create mode 100644 modules/yandexBidAdapter.md create mode 100644 modules/zeta_global_sspAnalyticsAdapter.js create mode 100644 modules/zeta_global_sspAnalyticsAdapter.md delete mode 100644 plugins/RequireEnsureWithoutJsonp.js create mode 100644 src/adRendering.js create mode 100644 src/auctionIndex.js create mode 100644 src/bidderSettings.js create mode 100644 src/consentHandler.js create mode 100644 src/polyfill.js create mode 100644 test/helpers/indexStub.js create mode 100644 test/helpers/syncPromise.js create mode 100644 test/spec/modules/admaruBidAdapter_spec.js create mode 100644 test/spec/modules/adnuntiusRtdProvider_spec.js create mode 100644 test/spec/modules/adplusBidAdapter_spec.js create mode 100644 test/spec/modules/adqueryIdSystem_spec.js create mode 100644 test/spec/modules/adrinoBidAdapter_spec.js create mode 100644 test/spec/modules/adriverIdSystem_spec.js create mode 100644 test/spec/modules/asealBidAdapter_spec.js create mode 100644 test/spec/modules/biddoBidAdapter_spec.js create mode 100644 test/spec/modules/big-richmediaBidAdapter_spec.js create mode 100644 test/spec/modules/brandmetricsRtdProvider_spec.js create mode 100644 test/spec/modules/compassBidAdapter_spec.js create mode 100644 test/spec/modules/dacIdSystem_spec.js create mode 100644 test/spec/modules/dailyhuntBidAdapter_spec.js create mode 100644 test/spec/modules/dchain_spec.js create mode 100644 test/spec/modules/debugging_mod_spec.js create mode 100644 test/spec/modules/displayioBidAdapter_spec.js create mode 100644 test/spec/modules/distroscaleBidAdapter_spec.js create mode 100644 test/spec/modules/e_volutionBidAdapter_spec.js create mode 100644 test/spec/modules/ftrackIdSystem_spec.js create mode 100644 test/spec/modules/goldbachBidAdapter_spec.js create mode 100644 test/spec/modules/hadronIdSystem_spec.js create mode 100644 test/spec/modules/hadronRtdProvider_spec.js create mode 100644 test/spec/modules/idWardRtdProvider_spec.js create mode 100644 test/spec/modules/intersectionRtdProvider_spec.js create mode 100644 test/spec/modules/justIdSystem_spec.js create mode 100644 test/spec/modules/lkqdBidAdapter_spec.js create mode 100644 test/spec/modules/loglyliftBidAdapter_spec.js create mode 100755 test/spec/modules/luponmediaBidAdapter_spec.js create mode 100644 test/spec/modules/mediafuseBidAdapter_spec.js create mode 100644 test/spec/modules/minutemediaBidAdapter_spec.js create mode 100644 test/spec/modules/nexx360BidAdapter_spec.js create mode 100644 test/spec/modules/open8BidAdapter_spec.js delete mode 100644 test/spec/modules/outbrainBidAdapter_spec.js~HEAD create mode 100644 test/spec/modules/pilotxBidAdapter_spec.js create mode 100644 test/spec/modules/smarthubBidAdapter_spec.js create mode 100644 test/spec/modules/targetVideoBidAdapter_spec.js create mode 100644 test/spec/modules/trustpidSystem_spec.js create mode 100644 test/spec/modules/ttdBidAdapter_spec.js create mode 100644 test/spec/modules/vibrantmediaBidAdapter_spec.js create mode 100644 test/spec/modules/viewability_spec.js create mode 100644 test/spec/modules/welectBidAdapter_spec.js create mode 100644 test/spec/modules/yandexBidAdapter_spec.js create mode 100644 test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js create mode 100644 test/spec/unit/core/auctionIndex_spec.js create mode 100644 test/spec/unit/core/bidderSettings_spec.js create mode 100644 test/spec/unit/core/consentHandler_spec.js create mode 100644 test/test_deps.js diff --git a/.babelrc.js b/.babelrc.js index 74d26caf0fd..add243a5b5d 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -1,34 +1 @@ - -let path = require('path'); - -function useLocal(module) { - return require.resolve(module, { - paths: [ - __dirname - ] - }) -} - -module.exports = { - "presets": [ - [ - useLocal('@babel/preset-env'), - { - "targets": { - "browsers": [ - "chrome >= 75", - "safari >=10", - "edge >= 70", - "ff >= 70", - "ie >= 11", - "ios >= 11" - ] - } - } - ] - ], - "plugins": [ - path.resolve(__dirname, './plugins/pbjsGlobals.js'), - useLocal('babel-plugin-transform-object-assign') - ] -}; +module.exports = require('./babelConfig.js')(); diff --git a/.circleci/config.yml b/.circleci/config.yml index a141b1ce4dd..ef43ee75bf3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ aliases: - &environment docker: # specify the version you desire here - - image: circleci/node:12.16.1 + - image: circleci/node:12.16.1-browsers resource_class: xlarge # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images @@ -51,7 +51,7 @@ aliases: - &unit_test_steps - checkout - restore_cache: *restore_dep_cache - - run: npm install + - run: npm ci - save_cache: *save_dep_cache - run: *install - run: *setup_browserstack diff --git a/.eslintrc.js b/.eslintrc.js index 78e4fb1bb33..d3379d70919 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,10 +23,13 @@ module.exports = { 'BROWSERSTACK_USERNAME': false, 'BROWSERSTACK_KEY': false }, + // use babel as parser for fancy syntax + parser: '@babel/eslint-parser', parserOptions: { sourceType: 'module', ecmaVersion: 2018, }, + rules: { 'comma-dangle': 'off', semi: 'off', @@ -49,5 +52,9 @@ module.exports = { rules: { 'prebid/validate-imports': ['error', allowedModules[key]] } - })) + })).concat([{ + // code in other packages (such as plugins/eslint) is not "seen" by babel and its parser will complain. + files: 'plugins/*/**/*.js', + parser: 'esprima' + }]) }; diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml new file mode 100644 index 00000000000..4397337b4c7 --- /dev/null +++ b/.github/workflows/issue_tracker.yml @@ -0,0 +1,89 @@ +name: Issue tracking +on: + issues: + types: + - opened +jobs: + track_issue: + runs-on: ubuntu-latest + steps: + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0 + with: + app_id: ${{ secrets.ISSUE_APP_ID }} + private_key: ${{ secrets.ISSUE_APP_PEM }} + + - name: Get project data + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + ORGANIZATION: prebid + DATE_FIELD: Created on + PROJECT_NUMBER: 2 + run: | + gh api graphql -f query=' + query($org: String!, $number: Int!) { + organization(login: $org){ + projectNext(number: $number) { + id + fields(first:100) { + nodes { + id + name + settings + } + } + } + } + }' -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 + + - name: Add issue to project + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + ISSUE_ID: ${{ github.event.issue.node_id }} + run: | + gh api graphql -f query=' + mutation($project:ID!, $issue:ID!) { + addProjectNextItem(input: {projectId: $project, contentId: $issue}) { + projectNextItem { + id, + content { + ... on Issue { + createdAt + } + ... on PullRequest { + createdAt + } + } + } + } + }' -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 + + - name: Set fields + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + gh api graphql -f query=' + mutation ( + $project: ID! + $item: ID! + $date_field: ID! + $date_value: String! + ) { + set_creation_date: updateProjectNextItemField(input: { + projectId: $project + itemId: $item + fieldId: $date_field + value: $date_value + }) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f item=$ITEM_ID -f date_field=$DATE_FIELD_ID -f date_value=$ITEM_CREATION_DATE --silent diff --git a/.gitignore b/.gitignore index c0452b7b3d0..82995ff1c23 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ build test/app gpt.html gpt-each-bidder3.html +fixConflict.sh # Selenium files bin diff --git a/README.md b/README.md index be16e9e3547..42d747b20b6 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ module.exports = { loader: 'babel-loader', // presets and plugins for Prebid.js must be manually specified separate from your other babel rule. // this can be accomplished by requiring prebid's .babelrc.js file (requires Babel 7 and Node v8.9.0+) + // as of Prebid 6, babelrc.js only targets modern browsers. One can change the targets and build for + // older browsers if they prefer, but integration tests on ie11 were removed in Prebid.js 6.0 options: require('prebid.js/.babelrc.js') } } @@ -128,16 +130,22 @@ Once setup, run the following command to globally install the `gulp-cli` package ## Build for Development -To build the project on your local machine, run: +To build the project on your local machine we recommend, running: - $ gulp serve + $ gulp serve-and-test --file -This runs some code quality checks, starts a web server at `http://localhost:9999` serving from the project root and generates the following files: +This will run testing but not linting. A web server will start at `http://localhost:9999` serving from the project root and generates the following files: + `./build/dev/prebid.js` - Full source code for dev and debug + `./build/dev/prebid.js.map` - Source map for dev and debug -+ `./build/dist/prebid.js` - Minified production code -+ `./prebid.js_.zip` - Distributable zip archive ++ `./build/dev/prebid-core.js` ++ `./build/dev/prebid-core.js.map` + + +Development may be a bit slower but if you prefer linting and additional watch files you can also still run just: + + $ gulp serve + ### Build Optimization @@ -272,7 +280,7 @@ As you make code changes, the bundles will be rebuilt and the page reloaded auto ## Contribute -Many SSPs, bidders, and publishers have contributed to this project. [Hundreds of bidders](https://github.com/prebid/Prebid.js/tree/master/src/adapters) are supported by Prebid.js. +Many SSPs, bidders, and publishers have contributed to this project. [Hundreds of bidders](https://github.com/prebid/Prebid.js/tree/master/modules) are supported by Prebid.js. For guidelines, see [Contributing](./CONTRIBUTING.md). @@ -314,7 +322,7 @@ For instructions on writing tests for Prebid.js, see [Testing Prebid.js](http:// ### Supported Browsers -Prebid.js is supported on IE11 and modern browsers. +Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to target >0.25%; not Opera Mini; not IE11. ### Governance Review our governance model [here](https://github.com/prebid/Prebid.js/tree/master/governance.md). diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md index bfbd0772c3e..b68495ed4ae 100644 --- a/RELEASE_SCHEDULE.md +++ b/RELEASE_SCHEDULE.md @@ -3,12 +3,7 @@ - [Release Process](#release-process) - [1. Make sure that all PRs have been named and labeled properly per the PR Process](#1-make-sure-that-all-prs-have-been-named-and-labeled-properly-per-the-pr-process) - [2. Make sure all browserstack tests are passing](#2-make-sure-all-browserstack-tests-are-passing) - - [3. Prepare Prebid Code](#3-prepare-prebid-code) - - [4. Verify the Release](#4-verify-the-release) - - [5. Create a GitHub release](#5-create-a-github-release) - - [6. Update coveralls _(skip for legacy)_](#6-update-coveralls-skip-for-legacy) - - [7. Distribute the code](#7-distribute-the-code) - - [8. Increment Version for Next Release](#8-increment-version-for-next-release) + - [3. Start the release](#3-start-the-release) - [Beta Releases](#beta-releases) - [FAQs](#faqs) @@ -21,12 +16,10 @@ it will be about a week before the Prebid Org [Download Page](http://prebid.org/ You can determine what is in a given build using the [releases page](https://github.com/prebid/Prebid.js/releases) -Announcements regarding releases will be made to the #headerbidding-dev channel in subredditadops.slack.com. +Announcements regarding releases will be made to the #prebid-js channel in prebid.slack.com. ## Release Process -_Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for your repo, all of the following git commands will have to be modified to reference the proper remote (e.g. `upstream`)_ - ### 1. Make sure that all PRs have been named and labeled properly per the [PR Process](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md#general-pr-review-process) * Do this by checking the latest draft release from the [releases page](https://github.com/prebid/Prebid.js/releases) and make sure nothing appears in the first section called "In This Release". If they do, please open the PRs and add the appropriate labels. * Do a quick check that all the titles/descriptions look ok, and if not, adjust the PR title. @@ -57,61 +50,10 @@ _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for ``` -### 3. Prepare Prebid Code - - Update the package.json version to become the current release. Then commit your changes. - - ``` - git commit -m "Prebid 4.x.x Release" - git push - ``` - -### 4. Verify the Release - - Make sure your there are no more merges to master branch. Prebid code is clean and up to date. - -### 5. Create a GitHub release - - Edit the most recent [release notes](https://github.com/prebid/Prebid.js/releases) draft and make sure the correct version is set and the master branch is selected in the dropdown. Click `Publish release`. GitHub will create release tag. - - Pull these changes locally by running command - ``` - git pull - git fetch --tags - ``` - - and verify the tag. - -### 6. Update coveralls _(skip for legacy)_ - - We use https://coveralls.io/ to show parts of code covered by unit tests. - - Set the environment variables. You may want to add these to your `~/.bashrc` for convenience. - ``` - export COVERALLS_SERVICE_NAME="travis-ci" - export COVERALLS_REPO_TOKEN="talk to Matt Kendall" - ``` - - Run `gulp coveralls` to update code coverage history. - -### 7. Distribute the code - - _Note: do not go to step 8 until step 7 has been verified completed._ - - Reach out to any of the Appnexus folks to trigger the jenkins job. - - // TODO: - Jenkins job is moving files to appnexus cdn, pushing prebid.js to npm, purging cache and sending notification to slack. - Move all the files from Appnexus CDN to jsDelivr and create bash script to do above tasks. - -### 8. Increment Version for Next Release - - Update the version by manually editing Prebid's `package.json` to become "4.x.x-pre" (using the values for the next release). Then commit your changes. - ``` - git commit -m "Increment pre version" - git push - ``` +### 3. Start the release +Follow the instructions at https://github.com/prebid/prebidjs-releaser. Note that you will need to be a member of the [https://github.com/orgs/prebid/teams/prebidjs-release](prebidjs-release) GitHub team. + ## Beta Releases Prebid.js features may be released as Beta or as Generally Available (GA). diff --git a/allowedModules.js b/allowedModules.js index 81920cdc15f..be9a2dc2abf 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -1,12 +1,5 @@ const sharedWhiteList = [ - 'core-js-pure/features/array/find', // no ie11 - 'core-js-pure/features/array/includes', // no ie11 - 'core-js-pure/features/set', // ie11 supports Set but not Set#values - 'core-js-pure/features/string/includes', // no ie11 - 'core-js-pure/features/number/is-integer', // no ie11, - 'core-js-pure/features/array/from', // no ie11 - 'core-js-pure/web/url-search-params' // no ie11 ]; module.exports = { diff --git a/babelConfig.js b/babelConfig.js new file mode 100644 index 00000000000..c1ddc11b689 --- /dev/null +++ b/babelConfig.js @@ -0,0 +1,30 @@ + +let path = require('path'); + +function useLocal(module) { + return require.resolve(module, { + paths: [ + __dirname + ] + }) +} + +module.exports = function (test = false) { + return { + 'presets': [ + [ + useLocal('@babel/preset-env'), + { + 'useBuiltIns': 'entry', + 'corejs': '3.13.0', + // a lot of tests use sinon.stub & others that stopped working on ES6 modules with webpack 5 + 'modules': test ? 'commonjs' : 'auto', + } + ] + ], + 'plugins': [ + path.resolve(__dirname, './plugins/pbjsGlobals.js'), + useLocal('babel-plugin-transform-object-assign'), + ], + } +} diff --git a/browsers.json b/browsers.json index aad51ca6383..bd6bd5772d6 100644 --- a/browsers.json +++ b/browsers.json @@ -1,73 +1,49 @@ { - "bs_edge_17_windows_10": { + "bs_edge_latest_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "edge", - "browser_version": "17.0", + "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_edge_90_windows_10": { - "base": "BrowserStack", - "os_version": "10", - "browser": "edge", - "browser_version": "90.0", - "device": null, - "os": "Windows" - }, - "bs_ie_11_windows_10": { - "base": "BrowserStack", - "os_version": "10", - "browser": "ie", - "browser_version": "11.0", - "device": null, - "os": "Windows" - }, - "bs_chrome_90_windows_10": { + "bs_chrome_latest_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "90.0", + "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_chrome_79_windows_10": { + "bs_chrome_87_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "79.0", - "device": null, - "os": "Windows" - }, - "bs_firefox_88_windows_10": { - "base": "BrowserStack", - "os_version": "10", - "browser": "firefox", - "browser_version": "88.0", + "browser_version": "87.0", "device": null, "os": "Windows" }, - "bs_firefox_72_windows_10": { + "bs_firefox_latest_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "firefox", - "browser_version": "72.0", + "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_safari_14_mac_bigsur": { + "bs_safari_latest_mac_bigsur": { "base": "BrowserStack", "os_version": "Big Sur", "browser": "safari", - "browser_version": "14.0", + "browser_version": "latest", "device": null, "os": "OS X" }, - "bs_safari_12_mac_mojave": { + "bs_safari_15_catalina": { "base": "BrowserStack", - "os_version": "Mojave", + "os_version": "Catalina", "browser": "safari", - "browser_version": "12.0", + "browser_version": "13.1", "device": null, "os": "OS X" } diff --git a/gulpfile.js b/gulpfile.js index 8609177a8b9..0c4ebc50653 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -8,7 +8,6 @@ var gutil = require('gulp-util'); var connect = require('gulp-connect'); var webpack = require('webpack'); var webpackStream = require('webpack-stream'); -var terser = require('gulp-terser'); var gulpClean = require('gulp-clean'); var KarmaServer = require('karma').Server; var karmaConfMaker = require('./karma.conf.maker.js'); @@ -76,6 +75,7 @@ function lint(done) { 'modules/**/*.js', 'test/**/*.js', 'plugins/**/*.js', + '!plugins/**/node_modules/**', './*.js' ], { base: './' }) .pipe(gulpif(argv.nolintfix, eslint(), eslint({ fix: true }))) @@ -114,41 +114,12 @@ function viewReview(done) { viewReview.displayName = 'view-review'; -// Watch Task with Live Reload -function watch(done) { - var mainWatcher = gulp.watch([ - 'src/**/*.js', - 'modules/**/*.js', - 'test/spec/**/*.js', - '!test/spec/loaders/**/*.js' - ]); - var loaderWatcher = gulp.watch([ - 'loaders/**/*.js', - 'test/spec/loaders/**/*.js' - ]); - - connect.server({ - https: argv.https, - port: port, - host: FAKE_SERVER_HOST, - root: './', - livereload: true - }); - - mainWatcher.on('all', gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))); - loaderWatcher.on('all', gulp.series(lint)); - done(); -}; - -function makeModuleList(modules) { - return modules.map(module => { - return '"' + module + '"' - }); -} - function makeDevpackPkg() { var cloned = _.cloneDeep(webpackConfig); - cloned.devtool = 'source-map'; + Object.assign(cloned, { + devtool: 'source-map', + mode: 'development' + }) var externalModules = helpers.getArgModules(); const analyticsSources = helpers.getAnalyticsSources(); @@ -157,14 +128,15 @@ function makeDevpackPkg() { return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) - .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules))) .pipe(gulp.dest('build/dev')) .pipe(connect.reload()); } function makeWebpackPkg() { var cloned = _.cloneDeep(webpackConfig); - delete cloned.devtool; + if (!argv.sourceMaps) { + delete cloned.devtool; + } var externalModules = helpers.getArgModules(); @@ -174,12 +146,19 @@ function makeWebpackPkg() { return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) - .pipe(terser()) - .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules))) - .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid }))) .pipe(gulp.dest('build/dist')); } +function addBanner() { + const sm = argv.sourceMaps; + + return gulp.src(['build/dist/prebid-core.js']) + .pipe(gulpif(sm, sourcemaps.init({loadMaps: true}))) + .pipe(header(banner, {prebid})) + .pipe(gulpif(sm, sourcemaps.write('.'))) + .pipe(gulp.dest('build/dist')) +} + function getModulesListToAddInBanner(modules) { return (modules.length > 0) ? modules.join(', ') : 'All available modules in current version.'; } @@ -204,6 +183,7 @@ function nodeBundle(modules) { function bundle(dev, moduleArr) { var modules = moduleArr || helpers.getArgModules(); var allModules = helpers.getModuleNames(modules); + const sm = dev || argv.sourceMaps; if (modules.length === 0) { modules = allModules.filter(module => explicitModules.indexOf(module) === -1); @@ -235,13 +215,13 @@ function bundle(dev, moduleArr) { ) // Need to uodate the "Modules: ..." section in comment with the current modules list .pipe(replace(/(Modules: )(.*?)(\*\/)/, ('$1' + getModulesListToAddInBanner(helpers.getArgModules()) + ' $3'))) - .pipe(gulpif(dev, sourcemaps.init({ loadMaps: true }))) + .pipe(gulpif(sm, sourcemaps.init({ loadMaps: true }))) .pipe(concat(outputFileName)) .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', { global: prebid.globalVarName } ))) - .pipe(gulpif(dev, sourcemaps.write('.'))); + .pipe(gulpif(sm, sourcemaps.write('.'))); } // Run the unit tests. @@ -254,60 +234,68 @@ function bundle(dev, moduleArr) { // If --browsers is given, browsers can be chosen explicitly. e.g. --browsers=chrome,firefox,ie9 // If --notest is given, it will immediately skip the test task (useful for developing changes with `gulp serve --notest`) -function test(done) { - if (argv.notest) { - done(); - } else if (argv.e2e) { - let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); - let wdioConf = path.join(__dirname, 'wdio.conf.js'); - let wdioOpts; - - if (argv.file) { - wdioOpts = [ - wdioConf, - `--spec`, - `${argv.file}` - ] - } else { - wdioOpts = [ - wdioConf - ]; - } +function testTaskMaker(options = {}) { + ['watch', 'e2e', 'file', 'browserstack', 'notest'].forEach(opt => { + options[opt] = options[opt] || argv[opt]; + }) - // run fake-server - const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); - fakeServer.stdout.on('data', (data) => { - console.log(`stdout: ${data}`); - }); - fakeServer.stderr.on('data', (data) => { - console.log(`stderr: ${data}`); - }); + return function test(done) { + if (options.notest) { + done(); + } else if (options.e2e) { + let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); + let wdioConf = path.join(__dirname, 'wdio.conf.js'); + let wdioOpts; + + if (options.file) { + wdioOpts = [ + wdioConf, + `--spec`, + `${options.file}` + ] + } else { + wdioOpts = [ + wdioConf + ]; + } - execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) - .then(stdout => { - // kill fake server - fakeServer.kill('SIGINT'); - done(); - process.exit(0); - }) - .catch(err => { - // kill fake server - fakeServer.kill('SIGINT'); - done(new Error(`Tests failed with error: ${err}`)); - process.exit(1); + // run fake-server + const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); + fakeServer.stdout.on('data', (data) => { + console.log(`stdout: ${data}`); + }); + fakeServer.stderr.on('data', (data) => { + console.log(`stderr: ${data}`); }); - } else { - var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file); - var browserOverride = helpers.parseBrowserArgs(argv); - if (browserOverride.length > 0) { - karmaConf.browsers = browserOverride; - } + execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) + .then(stdout => { + // kill fake server + fakeServer.kill('SIGINT'); + done(); + process.exit(0); + }) + .catch(err => { + // kill fake server + fakeServer.kill('SIGINT'); + done(new Error(`Tests failed with error: ${err}`)); + process.exit(1); + }); + } else { + var karmaConf = karmaConfMaker(false, options.browserstack, options.watch, options.file); + + var browserOverride = helpers.parseBrowserArgs(argv); + if (browserOverride.length > 0) { + karmaConf.browsers = browserOverride; + } - new KarmaServer(karmaConf, newKarmaCallback(done)).start(); + new KarmaServer(karmaConf, newKarmaCallback(done)).start(); + } } } +const test = testTaskMaker(); + function newKarmaCallback(done) { return function (exitCode) { if (exitCode) { @@ -384,6 +372,35 @@ function startFakeServer() { }); } +// Watch Task with Live Reload +function watchTaskMaker(options = {}) { + if (options.livereload == null) { + options.livereload = true; + } + options.alsoWatch = options.alsoWatch || []; + + return function watch(done) { + var mainWatcher = gulp.watch([ + 'src/**/*.js', + 'modules/**/*.js', + ].concat(options.alsoWatch)); + + connect.server({ + https: argv.https, + port: port, + host: FAKE_SERVER_HOST, + root: './', + livereload: options.livereload + }); + + mainWatcher.on('all', options.task()); + done(); + } +} + +const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))}); +const watchFast = watchTaskMaker({livereload: false, task: () => gulp.series('build-bundle-dev')}); + // support tasks gulp.task(lint); gulp.task(watch); @@ -393,10 +410,11 @@ gulp.task(clean); gulp.task(escapePostbidConfig); gulp.task('build-bundle-dev', gulp.series(makeDevpackPkg, gulpBundle.bind(null, true))); -gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg, gulpBundle.bind(null, false))); +gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg, addBanner, gulpBundle.bind(null, false))); // public tasks (dependencies are needed for each task since they can be ran on their own) -gulp.task('test', gulp.series(clean, lint, test)); +gulp.task('test-only', test); +gulp.task('test', gulp.series(clean, lint, 'test-only')); gulp.task('test-coverage', gulp.series(clean, testCoverage)); gulp.task(viewCoverage); @@ -407,10 +425,11 @@ gulp.task('build', gulp.series(clean, 'build-bundle-prod')); gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); -gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watch))); +gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast))); +gulp.task('serve-and-test', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast, testTaskMaker({watch: true})))); gulp.task('serve-fake', gulp.series(clean, gulp.parallel('build-bundle-dev', watch), injectFakeServerEndpointDev, test, startFakeServer)); -gulp.task('default', gulp.series(clean, makeWebpackPkg)); +gulp.task('default', gulp.series(clean, 'build-bundle-prod')); gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-prod', watch), injectFakeServerEndpoint, test)); // other tasks diff --git a/integrationExamples/gpt/adloox.html b/integrationExamples/gpt/adloox.html index e8920cf2ee1..fd61267479d 100644 --- a/integrationExamples/gpt/adloox.html +++ b/integrationExamples/gpt/adloox.html @@ -161,7 +161,11 @@ }, rubicon: { singleRequest: true - } + }, + // RTD module honors pageUrl for referrer detection and + // the analytics module uses this for the 'pageurl' macro + // N.B. set this to a non-example.com URL to see the video + //pageUrl: 'https://yourdomain.com/some/path/to/content.html' }); pbjs.enableAnalytics({ provider: 'adloox', diff --git a/integrationExamples/gpt/amp/creative.html b/integrationExamples/gpt/amp/creative.html index 86f669dd6b5..384b81107cc 100644 --- a/integrationExamples/gpt/amp/creative.html +++ b/integrationExamples/gpt/amp/creative.html @@ -1,38 +1,16 @@ + diff --git a/integrationExamples/gpt/esp_example.html b/integrationExamples/gpt/esp_example.html new file mode 100644 index 00000000000..c39a67243cc --- /dev/null +++ b/integrationExamples/gpt/esp_example.html @@ -0,0 +1,177 @@ + + + + + + + + + + + + +

Basic Prebid.js Example

+ +
Div-1
+
+ +
+ +
+ +
Div-2
+
+ +
+ + + + \ No newline at end of file diff --git a/integrationExamples/gpt/haloRtdProvider_example.html b/integrationExamples/gpt/hadronRtdProvider_example.html similarity index 87% rename from integrationExamples/gpt/haloRtdProvider_example.html rename to integrationExamples/gpt/hadronRtdProvider_example.html index 14debbd2698..065c8379956 100644 --- a/integrationExamples/gpt/haloRtdProvider_example.html +++ b/integrationExamples/gpt/hadronRtdProvider_example.html @@ -1,8 +1,8 @@ -Halo Id: -
+Hadron Id: +
-Halo Real-Time Data: +Hadron Real-Time Data:
diff --git a/integrationExamples/gpt/idImportLibrary_example.html b/integrationExamples/gpt/idImportLibrary_example.html index 07a4f0fe1c5..363e8015f53 100644 --- a/integrationExamples/gpt/idImportLibrary_example.html +++ b/integrationExamples/gpt/idImportLibrary_example.html @@ -69,10 +69,10 @@ name: "zeotapIdPlus" }, { - name: 'haloId', + name: 'hadronId', storage: { type: "html5", - name: "haloId", + name: "hadronId", expires: 28 } }, { diff --git a/integrationExamples/gpt/idward_segments_example.html b/integrationExamples/gpt/idward_segments_example.html new file mode 100644 index 00000000000..9bc06124c77 --- /dev/null +++ b/integrationExamples/gpt/idward_segments_example.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+
First Party Data (ortb2) Sent to Bidding Adapter
+
+ + diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 653dd9c59f3..e11a0b626c9 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -215,10 +215,10 @@ "name": "zeotapIdPlus" }, { - "name": "haloId", + "name": "hadronId", "storage": { "type": "cookie", - "name": "haloId", + "name": "hadronId", "expires": 28 } }, @@ -252,6 +252,9 @@ "params": { "cid": 5126 // Set your Intimate Merger Customer ID here for production } + }, + { + "name": "dacId" } ], "syncDelay": 5000, diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html index 824d7a2f0c7..b81ec52b2c4 100644 --- a/integrationExamples/gpt/weboramaRtdProvider_example.html +++ b/integrationExamples/gpt/weboramaRtdProvider_example.html @@ -1,126 +1,174 @@ - + + + - - + + - + - - -
-

-test webo ctx using prebid.js -

-
-

Basic Prebid.js Example

-
Div-1
-
- -
- + }); + } + + + // in case PBJS doesn't load + setTimeout(function () { + initAdserver(); + }, FAILSAFE_TIMEOUT); + + googletag.cmd.push(function () { + googletag.defineSlot('/1056029/webo-ctx-prebid', div_1_sizes, 'div-gpt-ad-1620653642627-0').addService(googletag.pubads()); + googletag.pubads().disableInitialLoad(); + googletag.enableServices(); + }); + + + +
+

+ test webo ctx using prebid.js +

+
+

Basic Prebid.js Example

+
Div-1
+
+ +
+ - + + \ No newline at end of file diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index fce46bb380f..bea8b70b4fe 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,37 +2,45 @@ // this script can be returned by an ad server delivering a cross domain iframe, into which the // creative will be rendered, e.g. DFP delivering a SafeFrame -let windowLocation = window.location; -var urlParser = document.createElement('a'); +const windowLocation = window.location; +const urlParser = document.createElement('a'); urlParser.href = '%%PATTERN:url%%'; -var publisherDomain = urlParser.protocol + '//' + urlParser.hostname; +const publisherDomain = urlParser.protocol + '//' + urlParser.hostname; +const adId = '%%PATTERN:hb_adid%%'; + +function receiveMessage(ev) { + const origin = ev.origin || ev.originalEvent.origin; + if (origin === publisherDomain) { + renderAd(ev); + } +} function renderAd(ev) { - var key = ev.message ? 'message' : 'data'; - var adObject = {}; - try { - adObject = JSON.parse(ev[key]); - } catch (e) { - return; - } + const key = ev.message ? 'message' : 'data'; + let adObject = {}; + try { + adObject = JSON.parse(ev[key]); + } catch (e) { + return; + } - var origin = ev.origin || ev.originalEvent.origin; - if (adObject.message && adObject.message === 'Prebid Response' && - publisherDomain === origin && - adObject.adId === '%%PATTERN:hb_adid%%' && - (adObject.ad || adObject.adUrl)) { - var body = window.document.body; - var ad = adObject.ad; - var url = adObject.adUrl; - var width = adObject.width; - var height = adObject.height; + if (adObject.message && adObject.message === 'Prebid Response' && + adObject.adId === adId) { + try { + const body = window.document.body; + const ad = adObject.ad; + const url = adObject.adUrl; + const width = adObject.width; + const height = adObject.height; if (adObject.mediaType === 'video') { + signalRenderResult(false, { + reason: 'preventWritingOnMainDocument', + message: `Cannot render video ad ${adId}` + }); console.log('Error trying to write ad.'); - } else - - if (ad) { - var frame = document.createElement('iframe'); + } else if (ad) { + const frame = document.createElement('iframe'); frame.setAttribute('FRAMEBORDER', 0); frame.setAttribute('SCROLLING', 'no'); frame.setAttribute('MARGINHEIGHT', 0); @@ -46,24 +54,50 @@ frame.contentDocument.open(); frame.contentDocument.write(ad); frame.contentDocument.close(); + signalRenderResult(true); } else if (url) { body.insertAdjacentHTML('beforeend', ''); + signalRenderResult(true); } else { - console.log('Error trying to write ad. No ad for bid response id: ' + id); + signalRenderResult(false, { + reason: 'noAd', + message: `No ad for ${adId}` + }); + console.log(`Error trying to write ad. No ad markup or adUrl for ${adId}`); } + } catch (e) { + signalRenderResult(false, {reason: 'exception', message: e.message}); + console.log(`Error in rendering ad`, e); + } + } + + function signalRenderResult(success, {reason, message} = {}) { + const payload = { + message: 'Prebid Event', + adId, + event: success ? 'adRenderSucceeded' : 'adRenderFailed', } + if (!success) { + payload.info = {reason, message}; + } + window.parent.postMessage(JSON.stringify(payload), publisherDomain); } +} + + function requestAdFromPrebid() { - var message = JSON.stringify({ + const message = JSON.stringify({ message: 'Prebid Request', - adId: '%%PATTERN:hb_adid%%' + adId }); - window.parent.postMessage(message, publisherDomain); + const channel = new MessageChannel(); + channel.port1.onmessage = renderAd; + window.parent.postMessage(message, publisherDomain, [channel.port2]); } function listenAdFromPrebid() { - window.addEventListener('message', renderAd, false); + window.addEventListener('message', receiveMessage, false); } listenAdFromPrebid(); diff --git a/karma.conf.maker.js b/karma.conf.maker.js index cf5999ba85e..b5c6b44e4fd 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -2,6 +2,7 @@ // // For more information, see http://karma-runner.github.io/1.0/config/configuration-file.html +const babelConfig = require('./babelConfig.js'); var _ = require('lodash'); var webpackConf = require('./webpack.conf.js'); var karmaConstants = require('karma').constants; @@ -10,10 +11,19 @@ function newWebpackConfig(codeCoverage) { // Make a clone here because we plan on mutating this object, and don't want parallel tasks to trample each other. var webpackConfig = _.cloneDeep(webpackConf); - // remove optimize plugin for tests - webpackConfig.plugins.pop() + Object.assign(webpackConfig, { + mode: 'development', + devtool: 'inline-source-map', + }); - webpackConfig.devtool = 'inline-source-map'; + delete webpackConfig.entry; + + webpackConfig.module.rules + .flatMap((r) => r.use) + .filter((use) => use.loader === 'babel-loader') + .forEach((use) => { + use.options = babelConfig(true); + }); if (codeCoverage) { webpackConfig.module.rules.push({ @@ -111,7 +121,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { var webpackConfig = newWebpackConfig(codeCoverage); var plugins = newPluginsArray(browserstack); - var files = file ? ['test/helpers/prebidGlobal.js', file] : ['test/test_index.js']; + var files = file ? ['test/test_deps.js', file] : ['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) { @@ -166,7 +176,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { browserNoActivityTimeout: 3e5, // default 10000 captureTimeout: 3e5, // default 60000, browserDisconnectTolerance: 3, - concurrency: 5, + concurrency: 6, plugins: plugins } diff --git a/modules.json b/modules.json index 47f41c0baca..ecd24fc7151 100644 --- a/modules.json +++ b/modules.json @@ -48,5 +48,6 @@ "yieldmoBidAdapter", "enrichmentFpdModule", "fabrickIdSystem", - "concertBidAdapter" + "concertBidAdapter", + "yahoosspBidAdapter" ] diff --git a/modules/.submodules.json b/modules/.submodules.json index ea3f556dbb4..85e4658cc61 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -7,16 +7,20 @@ "britepoolIdSystem", "connectIdSystem", "criteoIdSystem", + "dacIdSystem", "deepintentDpesIdSystem", "dmdIdSystem", "fabrickIdSystem", "flocIdSystem", + "hadronIdSystem", "haloIdSystem", "id5IdSystem", + "ftrackIdSystem", "identityLinkIdSystem", "idxIdSystem", "imuIdSystem", "intentIqIdSystem", + "justIdSystem", "kinessoIdSystem", "liveIntentIdSystem", "lotamePanoramaIdSystem", @@ -32,10 +36,12 @@ "quantcastIdSystem", "sharedIdSystem", "tapadIdSystem", + "trustpidSystem", "uid2IdSystem", "unifiedIdSystem", "verizonMediaIdSystem", - "zeotapIdPlusIdSystem" + "zeotapIdPlusIdSystem", + "adqueryIdSystem" ], "adpod": [ "freeWheelAdserverVideo", @@ -45,6 +51,7 @@ "browsiRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", + "hadronRtdProvider", "haloRtdProvider", "iasRtdProvider", "jwplayerRtdProvider", diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 2bdbdd6414b..498e6cf8634 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -1,16 +1,26 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { - deepAccess, uniques, isArray, getWindowTop, isGptPubadsDefined, isSlotMatchingAdUnitCode, logInfo, logWarn, - getWindowSelf + deepAccess, + uniques, + isArray, + getWindowTop, + isGptPubadsDefined, + isSlotMatchingAdUnitCode, + logInfo, + logWarn, + getWindowSelf, + mergeDeep, } from '../src/utils.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +// **************************** UTILS *************************** // const BIDDER_CODE = '33across'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; const SYNC_ENDPOINT = 'https://ssc-cms.33across.com/ps/?m=xch&rt=html&ru=deb'; const CURRENCY = 'USD'; +const GVLID = 58; const GUID_PATTERN = /^[a-zA-Z0-9_-]{22}$/; const PRODUCT = { @@ -42,6 +52,14 @@ const adapterState = { const NON_MEASURABLE = 'nm'; +function getTTXConfig() { + const ttxSettings = Object.assign({}, + config.getConfig('ttxSettings') + ); + + return ttxSettings; +} + // **************************** VALIDATION *************************** // function isBidRequestValid(bid) { return ( @@ -74,6 +92,7 @@ function _validateGUID(bid) { function _validateBanner(bid) { const banner = deepAccess(bid, 'mediaTypes.banner'); + // If there's no banner no need to validate against banner rules if (banner === undefined) { return true; @@ -140,91 +159,125 @@ function _validateVideo(bid) { // NOTE: With regards to gdrp consent data, the server will independently // infer the gdpr applicability therefore, setting the default value to false function buildRequests(bidRequests, bidderRequest) { + const { + ttxSettings, + gdprConsent, + uspConsent, + pageUrl + } = _buildRequestParams(bidRequests, bidderRequest); + + const groupedRequests = _buildRequestGroups(ttxSettings, bidRequests); + + const serverRequests = []; + + for (const key in groupedRequests) { + serverRequests.push( + _createServerRequest({ + bidRequests: groupedRequests[key], + gdprConsent, + uspConsent, + pageUrl, + ttxSettings + }) + ) + } + + return serverRequests; +} + +function _buildRequestParams(bidRequests, bidderRequest) { + const ttxSettings = getTTXConfig(); + const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent); const uspConsent = bidderRequest && bidderRequest.uspConsent; + const pageUrl = (bidderRequest && bidderRequest.refererInfo) ? (bidderRequest.refererInfo.referer) : (undefined); adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); - return bidRequests.map(bidRequest => _createServerRequest( - { - bidRequest, - gdprConsent, - uspConsent, - pageUrl - }) - ); + return { + ttxSettings, + gdprConsent, + uspConsent, + pageUrl + } +} + +function _buildRequestGroups(ttxSettings, bidRequests) { + const bidRequestsComplete = bidRequests.map(_inferProduct); + const enableSRAMode = ttxSettings && ttxSettings.enableSRAMode; + const keyFunc = (enableSRAMode === true) ? _getSRAKey : _getMRAKey; + + return _groupBidRequests(bidRequestsComplete, keyFunc); +} + +function _groupBidRequests(bidRequests, keyFunc) { + const groupedRequests = {}; + + bidRequests.forEach((req) => { + const key = keyFunc(req); + + groupedRequests[key] = groupedRequests[key] || []; + groupedRequests[key].push(req); + }); + + return groupedRequests; +} + +function _getSRAKey(bidRequest) { + return `${bidRequest.params.siteId}:${bidRequest.params.productId}`; +} + +function _getMRAKey(bidRequest) { + return `${bidRequest.bidId}`; } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request -// NOTE: At this point, TTX only accepts request for a single impression -function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl}) { +function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, ttxSettings }) { const ttxRequest = {}; - const params = bidRequest.params; + const { siteId, test } = bidRequests[0].params; /* * Infer data for the request payload */ - ttxRequest.imp = [{}]; + ttxRequest.imp = []; - if (deepAccess(bidRequest, 'mediaTypes.banner')) { - ttxRequest.imp[0].banner = { - ..._buildBannerORTB(bidRequest) - } - } - - if (deepAccess(bidRequest, 'mediaTypes.video')) { - ttxRequest.imp[0].video = _buildVideoORTB(bidRequest); - } - - ttxRequest.imp[0].ext = { - ttx: { - prod: _getProduct(bidRequest) - } - }; + bidRequests.forEach((req) => { + ttxRequest.imp.push(_buildImpORTB(req)); + }); - ttxRequest.site = { id: params.siteId }; + ttxRequest.site = { id: siteId }; if (pageUrl) { ttxRequest.site.page = pageUrl; } - // Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and - // therefore in ad targetting process - ttxRequest.id = bidRequest.bidId; + ttxRequest.id = bidRequests[0].auctionId; if (gdprConsent.consentString) { - ttxRequest.user = setExtension( - ttxRequest.user, - 'consent', - gdprConsent.consentString - ) + ttxRequest.user = setExtensions(ttxRequest.user, { + 'consent': gdprConsent.consentString + }); } - if (Array.isArray(bidRequest.userIdAsEids) && bidRequest.userIdAsEids.length > 0) { - ttxRequest.user = setExtension( - ttxRequest.user, - 'eids', - bidRequest.userIdAsEids - ) + if (Array.isArray(bidRequests[0].userIdAsEids) && bidRequests[0].userIdAsEids.length > 0) { + ttxRequest.user = setExtensions(ttxRequest.user, { + 'eids': bidRequests[0].userIdAsEids + }); } - ttxRequest.regs = setExtension( - ttxRequest.regs, - 'gdpr', - Number(gdprConsent.gdprApplies) - ); + ttxRequest.regs = setExtensions(ttxRequest.regs, { + 'gdpr': Number(gdprConsent.gdprApplies) + }); if (uspConsent) { - ttxRequest.regs = setExtension( - ttxRequest.regs, - 'us_privacy', - uspConsent - ) + ttxRequest.regs = setExtensions(ttxRequest.regs, { + 'us_privacy': uspConsent + }); } ttxRequest.ext = { @@ -237,16 +290,14 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } }; - if (bidRequest.schain) { - ttxRequest.source = setExtension( - ttxRequest.source, - 'schain', - bidRequest.schain - ) + if (bidRequests[0].schain) { + ttxRequest.source = setExtensions(ttxRequest.source, { + 'schain': bidRequests[0].schain + }); } // Finally, set the openRTB 'test' param if this is to be a test bid - if (params.test === 1) { + if (test === 1) { ttxRequest.test = 1; } @@ -259,8 +310,7 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl }; // Allow the ability to configure the HB endpoint for testing purposes. - const ttxSettings = config.getConfig('ttxSettings'); - const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${params.siteId}`; + const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${siteId}`; // Return the server request return { @@ -272,14 +322,36 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } // BUILD REQUESTS: SET EXTENSIONS -function setExtension(obj = {}, key, value) { - return Object.assign({}, obj, { - ext: Object.assign({}, obj.ext, { - [key]: value - }) +function setExtensions(obj = {}, extFields) { + return mergeDeep({}, obj, { + 'ext': extFields }); } +// BUILD REQUESTS: IMP +function _buildImpORTB(bidRequest) { + const imp = { + id: bidRequest.bidId, + ext: { + ttx: { + prod: deepAccess(bidRequest, 'params.productId') + } + } + }; + + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + imp.banner = { + ..._buildBannerORTB(bidRequest) + } + } + + if (deepAccess(bidRequest, 'mediaTypes.video')) { + imp.video = _buildVideoORTB(bidRequest); + } + + return imp; +} + // BUILD REQUESTS: SIZE INFERENCE function _transformSizes(sizes) { if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) { @@ -297,6 +369,14 @@ function _getSize(size) { } // BUILD REQUESTS: PRODUCT INFERENCE +function _inferProduct(bidRequest) { + return mergeDeep({}, bidRequest, { + params: { + productId: _getProduct(bidRequest) + } + }); +} + function _getProduct(bidRequest) { const { params, mediaTypes } = bidRequest; @@ -367,7 +447,7 @@ function _buildVideoORTB(bidRequest) { const video = {} - const {w, h} = _getSize(videoParams.playerSize[0]); + const { w, h } = _getSize(videoParams.playerSize[0]); video.w = w; video.h = h; @@ -388,11 +468,11 @@ function _buildVideoORTB(bidRequest) { if (product === PRODUCT.INSTREAM) { video.startdelay = video.startdelay || 0; video.placement = 1; - }; + } // bidfloors if (typeof bidRequest.getFloor === 'function') { - const bidfloors = _getBidFloors(bidRequest, {w: video.w, h: video.h}, VIDEO); + const bidfloors = _getBidFloors(bidRequest, { w: video.w, h: video.h }, VIDEO); if (bidfloors) { Object.assign(video, { @@ -404,6 +484,7 @@ function _buildVideoORTB(bidRequest) { }); } } + return video; } @@ -556,54 +637,61 @@ function _isIframe() { } // **************************** INTERPRET RESPONSE ******************************** // -// NOTE: At this point, the response from 33exchange will only ever contain one bid -// i.e. the highest bid function interpretResponse(serverResponse, bidRequest) { - const bidResponses = []; - - // If there are bids, look at the first bid of the first seatbid (see NOTE above for assumption about ttx) - if (serverResponse.body.seatbid.length > 0 && serverResponse.body.seatbid[0].bid.length > 0) { - bidResponses.push(_createBidResponse(serverResponse.body)); - } - - return bidResponses; + const { seatbid, cur = 'USD' } = serverResponse.body; + + if (!isArray(seatbid)) { + return []; + } + + // Pick seats with valid bids and convert them into an Array of responses + // in format expected by Prebid Core + return seatbid + .filter((seat) => ( + isArray(seat.bid) && + seat.bid.length > 0 + )) + .reduce((acc, seat) => { + return acc.concat( + seat.bid.map((bid) => _createBidResponse(bid, cur)) + ); + }, []); } -// All this assumes that only one bid is ever returned by ttx -function _createBidResponse(response) { +function _createBidResponse(bid, cur) { const isADomainPresent = - response.seatbid[0].bid[0].adomain && response.seatbid[0].bid[0].adomain.length; - const bid = { - requestId: response.id, + bid.adomain && bid.adomain.length; + const bidResponse = { + requestId: bid.impid, bidderCode: BIDDER_CODE, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ad: response.seatbid[0].bid[0].adm, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - mediaType: deepAccess(response.seatbid[0].bid[0], 'ext.ttx.mediaType', BANNER), - currency: response.cur, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: bid.ttl || 60, + creativeId: bid.crid, + mediaType: deepAccess(bid, 'ext.ttx.mediaType', BANNER), + currency: cur, netRevenue: true } if (isADomainPresent) { - bid.meta = { - advertiserDomains: response.seatbid[0].bid[0].adomain + bidResponse.meta = { + advertiserDomains: bid.adomain }; } - if (bid.mediaType === VIDEO) { - const vastType = deepAccess(response.seatbid[0].bid[0], 'ext.ttx.vastType', 'xml'); + if (bidResponse.mediaType === VIDEO) { + const vastType = deepAccess(bid, 'ext.ttx.vastType', 'xml'); if (vastType === 'xml') { - bid.vastXml = bid.ad; + bidResponse.vastXml = bidResponse.ad; } else { - bid.vastUrl = bid.ad; + bidResponse.vastUrl = bidResponse.ad; } } - return bid; + return bidResponse; } // **************************** USER SYNC *************************** // @@ -648,6 +736,7 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ BANNER, VIDEO ], + gvlid: GVLID, isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 264cf5f9fcb..a24bc889411 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,18 +1,37 @@ -import find from 'core-js-pure/features/array/find.js'; +import {find} from '../src/polyfill.js'; import { - isInteger, isArray, deepAccess, mergeDeep, logWarn, logInfo, logError, getWindowTop, getWindowSelf, generateUUID, _map, - getDNT, parseUrl, getUniqueIdentifierStr, isNumber, cleanObj, isFn, inIframe, deepClone, getGptSlotInfoForAdUnitCode + _map, + cleanObj, + deepAccess, + deepClone, + generateUUID, + getDNT, + getGptSlotInfoForAdUnitCode, + getUniqueIdentifierStr, + getWindowSelf, + getWindowTop, + inIframe, + isArray, + isFn, + isInteger, + isNumber, + logError, + logInfo, + logWarn, + mergeDeep, + parseUrl } from '../src/utils.js'; -import { config } from '../src/config.js'; +import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { loadExternalScript } from '../src/adloader.js'; -import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { createEidsArray } from './userId/eids.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { Renderer } from '../src/Renderer.js'; -import { OUTSTREAM } from '../src/video.js'; +import {loadExternalScript} from '../src/adloader.js'; +import {verify} from 'criteo-direct-rsa-validate/build/verify.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {createEidsArray} from './userId/eids.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {OUTSTREAM} from '../src/video.js'; + const BIDDER_CODE = 'adagio'; const LOG_PREFIX = 'Adagio:'; const FEATURES_VERSION = '1'; @@ -21,7 +40,7 @@ const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; const GVLID = 617; -export const storage = getStorageManager(GVLID, 'adagio'); +export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; const MAX_SESS_DURATION = 30 * 60 * 1000; const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp'; @@ -268,8 +287,6 @@ function getSite(bidderRequest) { } else if (refererInfo.stack && refererInfo.stack.length && refererInfo.stack[0]) { // important note check if refererInfo.stack[0] is 'thruly' because a `null` value // will be considered as "localhost" by the parseUrl function. - // As the isBidRequestValid returns false when it does not reach the referer - // this should never called. const url = parseUrl(refererInfo.stack[0]); domain = url.hostname; } @@ -308,7 +325,7 @@ function getElementFromTopWindow(element, currentWindow) { function autoDetectAdUnitElementIdFromGpt(adUnitCode) { const autoDetectedAdUnit = getGptSlotInfoForAdUnitCode(adUnitCode); - if (autoDetectedAdUnit && autoDetectedAdUnit.divId) { + if (autoDetectedAdUnit.divId) { return autoDetectedAdUnit.divId; } }; @@ -806,7 +823,7 @@ function getPrintNumber(adUnitCode, bidderRequest) { return 1; } const adagioBid = find(bidderRequest.bids, bid => bid.adUnitCode === adUnitCode); - return adagioBid.bidRequestsCount || 1; + return adagioBid.bidderRequestsCount || 1; } /** @@ -873,12 +890,6 @@ export const spec = { autoFillParams(bid); - if (!internal.getRefererInfo().reachedTop) { - logWarn(`${LOG_PREFIX} the main page url is unreachabled.`); - // internal.enqueue(debugData()); - return false; - } - if (!(bid.params.organizationId && bid.params.site && bid.params.placement)) { logWarn(`${LOG_PREFIX} at least one required param is missing.`); // internal.enqueue(debugData()); diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index 2779ced8cea..889822d9bd4 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -18,7 +18,7 @@ Below, the list of Adagio params and where they can be set. | ---------- | ------------- | ------------- | | siteId | x | | organizationId (obsolete) | | x -| site (obsolete) | | x +| site (obsolete) | | x | pagetype | x | x | environment | x | x | category | x | x diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js index ca4795c574f..de8a3598be1 100644 --- a/modules/adbookpspBidAdapter.js +++ b/modules/adbookpspBidAdapter.js @@ -1,13 +1,24 @@ -import includes from 'core-js-pure/features/array/includes.js'; -import find from 'core-js-pure/features/array/find'; -import { config } from '../src/config.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {find, includes} from '../src/polyfill.js'; +import {config} from '../src/config.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; import { - isPlainObject, deepSetValue, deepAccess, logWarn, inIframe, isNumber, logError, isArray, uniques, - flatten, triggerPixel, isStr, isEmptyStr, generateUUID + deepAccess, + deepSetValue, + flatten, + generateUUID, + inIframe, + isArray, + isEmptyStr, + isNumber, + isPlainObject, + isStr, + logError, + logWarn, + triggerPixel, + uniques } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; /** * CONSTANTS @@ -363,7 +374,7 @@ function impBidsToPrebidBids( } const impToPrebidBid = - (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid) => { + (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid, bidIndex) => { try { const bidRequest = findBidRequest(bidderRequestBody, bid); @@ -377,7 +388,7 @@ const impToPrebidBid = let prebidBid = { ad: bid.adm, adId: bid.adid, - adserverTargeting: targetingMap[bid.impid], + adserverTargeting: targetingMap[bidIndex], adUnitCode: bidRequest.tagid, bidderRequestId: bidderRequestBody.id, bidId: bid.id, @@ -408,6 +419,9 @@ const impToPrebidBid = }; } + if (deepAccess(bid, 'ext.pa_win') === true) { + prebidBid.auctionWinner = true; + } return prebidBid; } catch (error) { logError(`${BIDDER_CODE}: Error while building bid`, error); @@ -429,29 +443,43 @@ function buildTargetingMap(bids) { const values = impIds.reduce((result, id) => { result[id] = { lineItemIds: [], + orderIds: [], dealIds: [], adIds: [], + adAndOrderIndexes: [] }; return result; }, {}); - bids.forEach((bid) => { - values[bid.impid].lineItemIds.push(bid.ext.liid); - values[bid.impid].dealIds.push(bid.dealid); - values[bid.impid].adIds.push(bid.adid); + bids.forEach((bid, bidIndex) => { + let impId = bid.impid; + values[impId].lineItemIds.push(bid.ext.liid); + values[impId].dealIds.push(bid.dealid); + values[impId].adIds.push(bid.adid); + + if (deepAccess(bid, 'ext.ordid')) { + values[impId].orderIds.push(bid.ext.ordid); + bid.ext.ordid.split(TARGETING_VALUE_SEPARATOR).forEach((ordid, ordIndex) => { + let adIdIndex = values[impId].adIds.indexOf(bid.adid); + values[impId].adAndOrderIndexes.push(adIdIndex + '_' + ordIndex) + }) + } }); const targetingMap = {}; - for (const id of impIds) { - targetingMap[id] = { + bids.forEach((bid, bidIndex) => { + let id = bid.impid; + + targetingMap[bidIndex] = { hb_liid_adbookpsp: values[id].lineItemIds.join(TARGETING_VALUE_SEPARATOR), hb_deal_adbookpsp: values[id].dealIds.join(TARGETING_VALUE_SEPARATOR), + hb_ad_ord_adbookpsp: values[id].adAndOrderIndexes.join(TARGETING_VALUE_SEPARATOR), hb_adid_c_adbookpsp: values[id].adIds.join(TARGETING_VALUE_SEPARATOR), + hb_ordid_adbookpsp: values[id].orderIds.join(TARGETING_VALUE_SEPARATOR), }; - } - + }) return targetingMap; } @@ -560,7 +588,7 @@ function bannerHasSingleSize(bidRequest) { * USER SYNC */ -export const storage = getStorageManager(); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { return responses diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js index 18cafe829b5..dcc453ef35a 100644 --- a/modules/addefendBidAdapter.js +++ b/modules/addefendBidAdapter.js @@ -19,6 +19,7 @@ export const spec = { v: $$PREBID_GLOBAL$$.version, auctionId: false, pageId: false, + gdpr_applies: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : 'true', gdpr_consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : '', referer: bidderRequest.refererInfo.referer, bids: [], diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index 4dd320d3f24..e0d3a881cad 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -25,7 +25,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - const ADGENE_PREBID_VERSION = '1.2.0'; + const ADGENE_PREBID_VERSION = '1.3.0'; let serverRequests = []; for (let i = 0, len = validBidRequests.length; i < len; i++) { const validReq = validBidRequests[i]; @@ -50,6 +50,12 @@ export const spec = { data = tryAppendQueryString(data, 'imark', '1'); } data = tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.referer); + if (isIos()) { + const hyperId = getHyperId(validReq); + if (hyperId != null) { + data = tryAppendQueryString(data, 'hyper_id', hyperId); + } + } // remove the trailing "&" if (data.lastIndexOf('&') === data.length - 1) { data = data.substring(0, data.length - 1); @@ -263,4 +269,20 @@ function getCurrencyType() { return 'JPY'; } +/** + * + * @param validReq request + * @return {null|string} + */ +function getHyperId(validReq) { + if (validReq.userId && validReq.userId.novatiq && validReq.userId.novatiq.snowflake.syncResponse === 1) { + return validReq.userId.novatiq.snowflake.id; + } + return null; +} + +function isIos() { + return (/(ios|ipod|ipad|iphone)/i).test(window.navigator.userAgent); +} + registerBidder(spec); diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js index c94a4e35efd..7f5af047993 100644 --- a/modules/adhashBidAdapter.js +++ b/modules/adhashBidAdapter.js @@ -1,12 +1,85 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import { BANNER } from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {includes} from '../src/polyfill.js'; +import {BANNER} from '../src/mediaTypes.js'; const VERSION = '1.0'; +const BAD_WORD_STEP = 0.1; +const BAD_WORD_MIN = 0.2; + +/** + * Function that checks the page where the ads are being served for brand safety. + * If unsafe words are found the scoring of that page increases. + * If it becomes greater than the maximum allowed score false is returned. + * The rules may vary based on the website language or the publisher. + * The AdHash bidder will not bid on unsafe pages (according to 4A's). + * @param badWords list of scoring rules to chech against + * @param maxScore maximum allowed score for that bidding + * @returns boolean flag is the page safe + */ +function brandSafety(badWords, maxScore) { + /** + * Performs the ROT13 encoding on the string argument and returns the resulting string. + * The Adhash bidder uses ROT13 so that the response is not blocked by: + * - ad blocking software + * - parental control software + * - corporate firewalls + * due to the bad words contained in the response. + * @param value The input string. + * @returns string Returns the ROT13 version of the given string. + */ + const rot13 = value => { + const input = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + const output = 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'; + const index = x => input.indexOf(x); + const translate = x => index(x) > -1 ? output[index(x)] : x; + return value.split('').map(translate).join(''); + }; + + /** + * Calculates the scoring for each bad word with dimishing returns + * @param {integer} points points that this word costs + * @param {integer} occurances number of occurances + * @returns {float} final score + */ + const scoreCalculator = (points, occurances) => { + let positive = true; + if (points < 0) { + points *= -1; + positive = false; + } + let result = 0; + for (let i = 0; i < occurances; i++) { + result += Math.max(points - i * BAD_WORD_STEP, BAD_WORD_MIN); + } + return positive ? result : -result; + }; + + // Default parameters if the bidder is unable to send some of them + badWords = badWords || []; + maxScore = parseInt(maxScore) || 10; + + try { + let score = 0; + const content = window.top.document.body.innerText.toLowerCase(); + const words = content.trim().split(/\s+/).length; + 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); + } + } + return score < maxScore * words / 500; + } catch (e) { + return true; + } +} export const spec = { code: 'adhash', - url: 'https://bidder.adhash.org/rtb?version=' + VERSION + '&prebid=true', + url: 'https://bidder.adhash.com/rtb?version=' + VERSION + '&prebid=true', supportedMediaTypes: [ BANNER ], isBidRequestValid: (bid) => { @@ -37,7 +110,7 @@ export const spec = { var size = validBidRequests[i].sizes[index].join('x'); bidRequests.push({ method: 'POST', - url: url, + url: url + '&publisher=' + validBidRequests[i].params.publisherId, bidRequest: validBidRequests[i], data: { timezone: new Date().getTimezoneOffset() / 60, @@ -59,7 +132,8 @@ export const spec = { blockedCreatives: [], currentTimestamp: new Date().getTime(), recentAds: [], - GDPR: gdprConsent + GDPRApplies: gdprConsent ? gdprConsent.gdprApplies : null, + GDPR: gdprConsent ? gdprConsent.consentString : null }, options: { withCredentials: false, @@ -73,7 +147,11 @@ export const spec = { interpretResponse: (serverResponse, request) => { const responseBody = serverResponse ? serverResponse.body : {}; - if (!responseBody.creatives || responseBody.creatives.length === 0) { + if ( + !responseBody.creatives || + responseBody.creatives.length === 0 || + !brandSafety(responseBody.badWords, responseBody.maxScore) + ) { return []; } @@ -87,7 +165,7 @@ export const spec = { cpm: responseBody.creatives[0].costEUR, ad: `
- + `, width: request.bidRequest.sizes[0][0], height: request.bidRequest.sizes[0][1], diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 88f3e0e0e4f..145b5605bc2 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -2,6 +2,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'adhese'; const GVLID = 553; @@ -20,11 +21,15 @@ export const spec = { if (validBidRequests.length === 0) { return null; } + const { gdprConsent, refererInfo } = bidderRequest; + const adheseConfig = config.getConfig('adhese'); const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {}; const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {}; - const commonParams = { ...gdprParams, ...refererParams }; + const globalCustomParams = (adheseConfig && adheseConfig.globalTargets) ? cleanTargets(adheseConfig.globalTargets) : {}; + const commonParams = { ...globalCustomParams, ...gdprParams, ...refererParams }; + const vastContentAsUrl = !(adheseConfig && adheseConfig.vastContentAsUrl == false); const slots = validBidRequests.map(bid => ({ slotname: bidToSlotName(bid), @@ -34,7 +39,7 @@ export const spec = { const payload = { slots: slots, parameters: commonParams, - vastContentAsUrl: true, + vastContentAsUrl: vastContentAsUrl, user: { ext: { eids: getEids(validBidRequests), diff --git a/modules/adkernelAdnAnalyticsAdapter.js b/modules/adkernelAdnAnalyticsAdapter.js index 2b4e67736f3..de5d59ca6f8 100644 --- a/modules/adkernelAdnAnalyticsAdapter.js +++ b/modules/adkernelAdnAnalyticsAdapter.js @@ -10,7 +10,7 @@ const GVLID = 14; const ANALYTICS_VERSION = '1.0.2'; const DEFAULT_QUEUE_TIMEOUT = 4000; const DEFAULT_HOST = 'tag.adkernel.com'; -const storageObj = getStorageManager(GVLID); +const storageObj = getStorageManager({gvlid: GVLID}); const ADK_HB_EVENTS = { AUCTION_INIT: 'auctionInit', diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 11c8b464a7e..c2d6ca4d4dd 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -1,11 +1,26 @@ import { - isStr, isArray, isPlainObject, deepSetValue, isNumber, deepAccess, getAdUnitSizes, parseGPTSingleSizeArrayToRtbSize, - cleanObj, contains, getDNT, parseUrl, createTrackPixelHtml, _each, isArrayOfNums + _each, + cleanObj, + contains, + createTrackPixelHtml, + deepAccess, + deepSetValue, + getAdUnitSizes, + getDNT, + inIframe, + isArray, + isArrayOfNums, + isEmpty, + isNumber, + isPlainObject, + isStr, + mergeDeep, + parseGPTSingleSizeArrayToRtbSize, + parseUrl } from '../src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {find, includes} from '../src/polyfill.js'; import {config} from '../src/config.js'; /* @@ -75,8 +90,11 @@ export const spec = { {code: 'denakop'}, {code: 'rtbanalytica'}, {code: 'unibots'}, + {code: 'catapultx'}, {code: 'ergadx'}, - {code: 'turktelekom'} + {code: 'turktelekom'}, + {code: 'felixads'}, + {code: 'motionspots'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -102,17 +120,16 @@ export const spec = { * @returns {ServerRequest[]} */ buildRequests: function (bidRequests, bidderRequest) { - let impDispatch = dispatchImps(bidRequests, bidderRequest.refererInfo); + let impGroups = groupImpressionsByHostZone(bidRequests, bidderRequest.refererInfo); let requests = []; let schain = bidRequests[0].schain; - Object.keys(impDispatch).forEach(host => { - Object.keys(impDispatch[host]).forEach(zoneId => { - const request = buildRtbRequest(impDispatch[host][zoneId], bidderRequest, schain); - requests.push({ - method: 'POST', - url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`, - data: JSON.stringify(request) - }); + _each(impGroups, impGroup => { + let {host, zoneId, imps} = impGroup; + const request = buildRtbRequest(imps, bidderRequest, schain); + requests.push({ + method: 'POST', + url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`, + data: JSON.stringify(request) }); }); return requests; @@ -208,17 +225,19 @@ registerBidder(spec); * @param bidRequests {BidRequest[]} * @param refererInfo {refererInfo} */ -function dispatchImps(bidRequests, refererInfo) { +function groupImpressionsByHostZone(bidRequests, refererInfo) { let secure = (refererInfo && refererInfo.referer.indexOf('https:') === 0); - return bidRequests.map(bidRequest => buildImp(bidRequest, secure)) - .reduce((acc, curr, index) => { - let bidRequest = bidRequests[index]; - let {zoneId, host} = bidRequest.params; - acc[host] = acc[host] || {}; - acc[host][zoneId] = acc[host][zoneId] || []; - acc[host][zoneId].push(curr); - return acc; - }, {}); + return Object.values( + bidRequests.map(bidRequest => buildImp(bidRequest, secure)) + .reduce((acc, curr, index) => { + let bidRequest = bidRequests[index]; + let {zoneId, host} = bidRequest.params; + let key = `${host}_${zoneId}`; + acc[key] = acc[key] || {host: host, zoneId: zoneId, imps: []}; + acc[key].imps.push(curr); + return acc; + }, {}) + ); } function getBidFloor(bid, mediaType, sizes) { @@ -364,57 +383,142 @@ function getAllowedSyncMethod(bidderCode) { } /** - * Builds complete rtb request - * @param imps {Object} Collection of rtb impressions - * @param bidderRequest {BidderRequest} - * @param schain {Object=} Supply chain config - * @return {Object} Complete rtb request + * Create device object from fpd and host-collected data + * @param fpd {Object} + * @returns {{device: Object}} */ -function buildRtbRequest(imps, bidderRequest, schain) { - let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest; - let coppa = config.getConfig('coppa'); - let req = { - 'id': auctionId, - 'imp': imps, - 'site': createSite(refererInfo), - 'at': 1, - 'device': { - 'ip': 'caller', - 'ipv6': 'caller', - 'ua': 'caller', - 'js': 1, - 'language': getLanguage() - }, - 'tmax': parseInt(timeout) - }; +function makeDevice(fpd) { + let device = mergeDeep({ + 'ip': 'caller', + 'ipv6': 'caller', + 'ua': 'caller', + 'js': 1, + 'language': getLanguage() + }, fpd.device || {}); if (getDNT()) { - req.device.dnt = 1; + device.dnt = 1; + } + return {device: device}; +} + +/** + * Create site or app description object + * @param bidderRequest {BidderRequest} + * @param fpd {Object} + * @returns {{site: Object}|{app: Object}} + */ +function makeSiteOrApp(bidderRequest, fpd) { + let {refererInfo} = bidderRequest; + let appConfig = config.getConfig('app'); + if (isEmpty(appConfig)) { + return {site: createSite(refererInfo, fpd)} + } else { + return {app: appConfig}; } +} + +/** + * Create user description object + * @param bidderRequest {BidderRequest} + * @param fpd {Object} + * @returns {{user: Object} | undefined} + */ +function makeUser(bidderRequest, fpd) { + let {gdprConsent} = bidderRequest; + let user = fpd.user || {}; + if (gdprConsent && gdprConsent.consentString !== undefined) { + deepSetValue(user, 'ext.consent', gdprConsent.consentString); + } + let eids = getExtendedUserIds(bidderRequest); + if (eids) { + deepSetValue(user, 'ext.eids', eids); + } + if (!isEmpty(user)) { return {user: user}; } +} + +/** + * Create privacy regulations object + * @param bidderRequest {BidderRequest} + * @returns {{regs: Object} | undefined} + */ +function makeRegulations(bidderRequest) { + let {gdprConsent, uspConsent} = bidderRequest; + let regs = {}; if (gdprConsent) { if (gdprConsent.gdprApplies !== undefined) { - deepSetValue(req, 'regs.ext.gdpr', ~~gdprConsent.gdprApplies); - } - if (gdprConsent.consentString !== undefined) { - deepSetValue(req, 'user.ext.consent', gdprConsent.consentString); + deepSetValue(regs, 'regs.ext.gdpr', ~~gdprConsent.gdprApplies); } } if (uspConsent) { - deepSetValue(req, 'regs.ext.us_privacy', uspConsent); + deepSetValue(regs, 'regs.ext.us_privacy', uspConsent); + } + if (config.getConfig('coppa')) { + deepSetValue(regs, 'regs.coppa', 1); } - if (coppa) { - deepSetValue(req, 'regs.coppa', 1); + if (!isEmpty(regs)) { + return regs; } +} + +/** + * Create top-level request object + * @param bidderRequest {BidderRequest} + * @param imps {Object} Impressions + * @param fpd {Object} First party data + * @returns + */ +function makeBaseRequest(bidderRequest, imps, fpd) { + let {auctionId, timeout} = bidderRequest; + let request = { + 'id': auctionId, + 'imp': imps, + 'at': 1, + 'tmax': parseInt(timeout) + }; + if (!isEmpty(fpd.bcat)) { + request.bcat = fpd.bcat; + } + if (!isEmpty(fpd.badv)) { + request.badv = fpd.badv; + } + return request; +} + +/** + * Initialize sync capabilities + * @param bidderRequest {BidderRequest} + */ +function makeSyncInfo(bidderRequest) { + let {bidderCode} = bidderRequest; let syncMethod = getAllowedSyncMethod(bidderCode); if (syncMethod) { - deepSetValue(req, 'ext.adk_usersync', syncMethod); + let res = {}; + deepSetValue(res, 'ext.adk_usersync', syncMethod); + return res; } +} + +/** + * Builds complete rtb request + * @param imps {Object} Collection of rtb impressions + * @param bidderRequest {BidderRequest} + * @param schain {Object=} Supply chain config + * @return {Object} Complete rtb request + */ +function buildRtbRequest(imps, bidderRequest, schain) { + let fpd = config.getConfig('ortb2') || {}; + + let req = mergeDeep( + makeBaseRequest(bidderRequest, imps, fpd), + makeDevice(fpd), + makeSiteOrApp(bidderRequest, fpd), + makeUser(bidderRequest, fpd), + makeRegulations(bidderRequest), + makeSyncInfo(bidderRequest) + ); if (schain) { deepSetValue(req, 'source.ext.schain', schain); } - let eids = getExtendedUserIds(bidderRequest); - if (eids) { - deepSetValue(req, 'user.ext.eids', eids); - } return req; } @@ -430,18 +534,17 @@ function getLanguage() { /** * Creates site description object */ -function createSite(refInfo) { +function createSite(refInfo, fpd) { let url = parseUrl(refInfo.referer); let site = { 'domain': url.hostname, 'page': `${url.protocol}://${url.hostname}${url.pathname}` }; - if (self === top && document.referrer) { + mergeDeep(site, fpd.site); + if (!inIframe() && document.referrer) { site.ref = document.referrer; - } - let keywords = document.getElementsByTagName('meta')['keywords']; - if (keywords && keywords.content) { - site.keywords = keywords.content; + } else { + delete site.ref; } return site; } diff --git a/modules/adlivetechBidAdapter.md b/modules/adlivetechBidAdapter.md new file mode 100644 index 00000000000..612e669ea1a --- /dev/null +++ b/modules/adlivetechBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +Module Name: Adlivetech Bidder Adapter +Module Type: Bidder Adapter +Maintainer: grid-tech@themediagrid.com + +# Description + +Module that connects to Grid demand source to fetch bids. +The adapter is GDPR compliant and supports banner and video (instream and outstream). + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "adlivetech", + params: { + uid: '1', + bidFloor: 0.5 + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "adlivetech", + params: { + uid: 2, + keywords: { + brandsafety: ['disaster'], + topic: ['stress', 'fear'] + } + } + } + ] + }, + { + code: 'test-div', + sizes: [[728, 90]], + mediaTypes: { video: { + context: 'instream', + playerSize: [728, 90], + mimes: ['video/mp4'] + }, + bids: [ + { + bidder: "adlivetech", + params: { + uid: 11 + } + } + ] + } + ]; +``` diff --git a/modules/adlooxAdServerVideo.js b/modules/adlooxAdServerVideo.js index 7305283039c..bd715cb34f3 100644 --- a/modules/adlooxAdServerVideo.js +++ b/modules/adlooxAdServerVideo.js @@ -9,7 +9,7 @@ import { registerVideoSupport } from '../src/adServerManager.js'; import { command as analyticsCommand, COMMAND } from './adlooxAnalyticsAdapter.js'; import { ajax } from '../src/ajax.js'; -import { EVENTS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; import { targeting } from '../src/targeting.js'; import { logInfo, isFn, logError, isPlainObject, isStr, isBoolean, deepSetValue, deepClone, timestamp, logWarn } from '../src/utils.js'; @@ -74,7 +74,7 @@ function track(options, callback) { bid.ext.adloox.video.adserver = false; analyticsCommand(COMMAND.TRACK, { - eventType: EVENTS.BID_WON, + eventType: CONSTANTS.EVENTS.BID_WON, args: bid }); } diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 6ea1df1b72c..1091b87a22d 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -6,14 +6,27 @@ import adapterManager from '../src/adapterManager.js'; import adapter from '../src/AnalyticsAdapter.js'; -import { loadExternalScript } from '../src/adloader.js'; -import { auctionManager } from '../src/auctionManager.js'; -import { AUCTION_COMPLETED } from '../src/auction.js'; -import { EVENTS } from '../src/constants.json'; -import find from 'core-js-pure/features/array/find.js'; +import {loadExternalScript} from '../src/adloader.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {AUCTION_COMPLETED} from '../src/auction.js'; +import CONSTANTS from '../src/constants.json'; +import {find} from '../src/polyfill.js'; +import {getRefererInfo} from '../src/refererDetection.js'; import { - deepAccess, logInfo, isPlainObject, logError, isStr, isNumber, getGptSlotInfoForAdUnitCode, - isFn, mergeDeep, logMessage, insertElement, logWarn, getUniqueIdentifierStr, parseUrl + deepAccess, + getGptSlotInfoForAdUnitCode, + getUniqueIdentifierStr, + insertElement, + isFn, + isNumber, + isPlainObject, + isStr, + logError, + logInfo, + logMessage, + logWarn, + mergeDeep, + parseUrl } from '../src/utils.js'; const MODULE = 'adlooxAnalyticsAdapter'; @@ -46,6 +59,10 @@ MACRO['targetelt'] = function(b, c) { MACRO['creatype'] = function(b, c) { return b.mediaType == 'video' ? ADLOOX_MEDIATYPE.VIDEO : ADLOOX_MEDIATYPE.DISPLAY; }; +MACRO['pageurl'] = function(b, c) { + const refererInfo = getRefererInfo(); + return (refererInfo.canonicalUrl || refererInfo.referer || '').substr(0, 300).split(/[?#]/)[0]; +}; MACRO['pbadslot'] = function(b, c) { const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); return deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; @@ -199,9 +216,9 @@ analyticsAdapter.url = function(url, args, bid) { return url + a2qs(args); } -analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { +analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = function(auctionDetails) { if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; - analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; + analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = NOOP; logMessage(MODULE, 'preloading verification JS'); @@ -214,7 +231,7 @@ analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { insertElement(link); } -analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { +analyticsAdapter[`handle_${CONSTANTS.EVENTS.BID_WON}`] = function(bid) { if (deepAccess(bid, 'ext.adloox.video.adserver')) { logMessage(MODULE, `measuring '${bid.mediaType}' ad unit code '${bid.adUnitCode}' via Ad Server module`); return; diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md index c4618a2e3aa..e21261d0b8d 100644 --- a/modules/adlooxAnalyticsAdapter.md +++ b/modules/adlooxAnalyticsAdapter.md @@ -127,6 +127,7 @@ The following macros are available * `%%pbadslot%%`: [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit) * it is recommended you read the [Prebid Ad Slot section in the Adloox RTD Provider documentation](./adlooxRtdProvider.md#prebid-ad-slot) + * `%%pageurl%%`: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer` ### Functions diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index 34d1428ea1d..bb8334ec8fe 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -11,16 +11,29 @@ /* eslint standard/no-callback-literal: "off" */ /* eslint prebid/validate-imports: "off" */ -import { command as analyticsCommand, COMMAND } from './adlooxAnalyticsAdapter.js'; -import { config as _config } from '../src/config.js'; -import { submodule } from '../src/hook.js'; -import { ajax } from '../src/ajax.js'; -import { getGlobal } from '../src/prebidGlobal.js'; +import {command as analyticsCommand, COMMAND} from './adlooxAnalyticsAdapter.js'; +import {config as _config} from '../src/config.js'; +import {submodule} from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {getRefererInfo} from '../src/refererDetection.js'; import { - getAdUnitSizes, logInfo, isPlainObject, logError, isStr, isInteger, isArray, isBoolean, mergeDeep, deepAccess, - _each, deepSetValue, logWarn, getGptSlotInfoForAdUnitCode + _each, + deepAccess, + deepSetValue, + getAdUnitSizes, + getGptSlotInfoForAdUnitCode, + isArray, + isBoolean, + isInteger, + isPlainObject, + isStr, + logError, + logInfo, + logWarn, + mergeDeep } from '../src/utils.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'adloox'; const MODULE = `${MODULE_NAME}RtdProvider`; @@ -285,6 +298,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { } } + const refererInfo = getRefererInfo(); const args = [ [ 'v', `pbjs-${getGlobal().version}` ], [ 'c', config.params.clientid ], @@ -293,7 +307,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { [ 'imp', config.params.imps ], [ 'fc_ip', config.params.freqcap_ip ], [ 'fc_ipua', config.params.freqcap_ipua ], - [ 'pn', document.location.pathname ] + [ 'pn', (refererInfo.canonicalUrl || refererInfo.referer || '').substr(0, 300).split(/[?#]/)[0] ] ]; if (!adUnits.length) { diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index e02a1a9df04..241864c50fc 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -1,10 +1,11 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'adman'; const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi'; -const URL_SYNC = 'https://pub.admanmedia.com/?c=o&m=sync'; +const URL_SYNC = 'https://sync.admanmedia.com'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -108,6 +109,7 @@ export const spec = { } if (bid.userId) { getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); + getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com'); } if (traff === VIDEO) { placement.playerSize = bid.mediaTypes[VIDEO].playerSize; @@ -151,19 +153,24 @@ export const spec = { }, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncUrl = URL_SYNC + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = URL_SYNC + `/${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}`; + 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: 'image', + type: syncType, url: syncUrl }]; } diff --git a/modules/admaruBidAdapter.js b/modules/admaruBidAdapter.js new file mode 100644 index 00000000000..65f62c77e26 --- /dev/null +++ b/modules/admaruBidAdapter.js @@ -0,0 +1,81 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const ADMARU_ENDPOINT = 'https://p1.admaru.net/AdCall'; +const BIDDER_CODE = 'admaru'; + +const DEFAULT_BID_TTL = 360; + +function parseBid(rawBid, currency) { + const bid = {}; + + bid.cpm = rawBid.price; + bid.impid = rawBid.impid; + bid.requestId = rawBid.impid; + bid.netRevenue = true; + bid.dealId = ''; + bid.creativeId = rawBid.crid; + bid.currency = currency; + bid.ad = rawBid.adm; + bid.width = rawBid.w; + bid.height = rawBid.h; + bid.mediaType = BANNER; + bid.ttl = DEFAULT_BID_TTL; + + return bid; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!(bid && bid.params && bid.params.pub_id && bid.params.adspace_id); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + const payload = { + pub_id: bid.params.pub_id, + adspace_id: bid.params.adspace_id, + bidderRequestId: bid.bidderRequestId, + bidId: bid.bidId + }; + + return { + method: 'GET', + url: ADMARU_ENDPOINT, + data: payload, + } + }) + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + let bid = null; + + if (!serverResponse.hasOwnProperty('body') || !serverResponse.body.hasOwnProperty('seatbid')) { + return bidResponses; + } + + const serverBody = serverResponse.body; + const seatbid = serverBody.seatbid; + + for (let i = 0; i < seatbid.length; i++) { + if (!seatbid[i].hasOwnProperty('bid')) { + continue; + } + + const innerBids = seatbid[i].bid; + for (let j = 0; j < innerBids.length; j++) { + bid = parseBid(innerBids[j], serverBody.cur); + + bidResponses.push(bid); + } + } + + return bidResponses; + } +} + +registerBidder(spec); diff --git a/modules/admaruBidAdapter.md b/modules/admaruBidAdapter.md new file mode 100644 index 00000000000..9985a660ac6 --- /dev/null +++ b/modules/admaruBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Admaru Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@admaru.com +``` + +# Description + +Module that connects to Admaru demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "admaru", + params: { + pub_id: '1234', // string - required + adspace_id: '1234' // string - required + } + } + ] + } + ]; +``` diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index bb91ddcdfc8..dfb76a03804 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,14 +1,15 @@ 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'; const BIDDER_CODE = 'admixer'; -const ALIASES = ['go2net', 'adblender', 'adsyield']; +const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads']; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; export const spec = { code: BIDDER_CODE, aliases: ALIASES, - supportedMediaTypes: ['banner', 'video'], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. */ diff --git a/modules/adnowBidAdapter.js b/modules/adnowBidAdapter.js index badf57ed5c9..472d0fdb2e1 100644 --- a/modules/adnowBidAdapter.js +++ b/modules/adnowBidAdapter.js @@ -1,7 +1,7 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE, BANNER } from '../src/mediaTypes.js'; -import { parseSizesInput, deepAccess, parseQueryStringParameters } from '../src/utils.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import {deepAccess, parseQueryStringParameters, parseSizesInput} from '../src/utils.js'; +import {includes} from '../src/polyfill.js'; const BIDDER_CODE = 'adnow'; const ENDPOINT = 'https://n.ads3-adnow.com/a'; diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index a1dff3d258d..9e05ea664d8 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -27,7 +27,7 @@ const getSegmentsFromOrtb = function (ortb2) { } const handleMeta = function () { - const storage = getStorageManager(GVLID, 'adnuntius') + const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}) let adnMeta = null if (storage.localStorageIsEnabled()) { adnMeta = JSON.parse(storage.getDataFromLocalStorage('adn.metaData')) @@ -37,7 +37,8 @@ const handleMeta = function () { } const getUsi = function (meta, ortb2, bidderRequest) { - const usi = (meta !== null) ? meta.usi : false; + let usi = (meta !== null && meta.usi) ? meta.usi : false; + if (ortb2 && ortb2.user && ortb2.user.id) { usi = ortb2.user.id } return usi } @@ -55,6 +56,8 @@ export const spec = { const requests = []; const request = []; const ortb2 = config.getConfig('ortb2'); + const bidderConfig = config.getConfig(); + const adnMeta = handleMeta() const usi = getUsi(adnMeta, ortb2, bidderRequest) const segments = getSegmentsFromOrtb(ortb2); @@ -67,7 +70,7 @@ export const spec = { if (gdprApplies !== undefined) request.push('consentString=' + consentString); if (segments.length > 0) request.push('segments=' + segments.join(',')); if (usi) request.push('userId=' + usi); - + if (bidderConfig.useCookie === false) request.push('noCookies=true') for (var i = 0; i < validBidRequests.length; i++) { const bid = validBidRequests[i] const network = bid.params.network || 'network'; diff --git a/modules/adnuntiusRtdProvider.js b/modules/adnuntiusRtdProvider.js new file mode 100644 index 00000000000..9234a30aa33 --- /dev/null +++ b/modules/adnuntiusRtdProvider.js @@ -0,0 +1,96 @@ + +import { submodule } from '../src/hook.js' +import { logError, logInfo } from '../src/utils.js' +import { ajax } from '../src/ajax.js'; + +import { config as sourceConfig } from '../src/config.js'; + +const GVLID = 855; + +function init(config, userConsent) { + if (!config.params || !config.params.providers) return false + logInfo(userConsent) + return true; +} + +// Make sure that ajax has a function as callback +function prepProvider(provider) { + // Map parameter to something that adnuntius endpoint understands. + const mappedParameters = { + siteId: 's', + userId: 'browserId', + browserId: 'browserId', + folderId: 'folderId' + } + + const tzo = new Date().getTimezoneOffset(); + const URL = ['https://data.adnuntius.com/usr?tzo=' + tzo] + Object.keys(provider).forEach(key => { + URL.push(`${mappedParameters[key]}=${provider[key]}`) + }) + + return new Promise((resolve, reject) => { + ajax(URL.join('&'), { + success: function (res) { + const response = JSON.parse(res) + resolve(response) + }, + error: function (err) { reject(err) } + }); + }); +} + +function setGlobalConfig(config, segments) { + const ortbSegments = { + ortb2: { + user: { + data: [{ + name: 'adnuntius', + segment: segments + }] + } + } + } + if (config.params && config.params.bidders) { + sourceConfig.mergeBidderConfig({ + bidders: config.params.bidders, + config: ortbSegments + }) + } else { + sourceConfig.mergeConfig(ortbSegments) + } +} + +function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { + const gdpr = userConsent && userConsent.gdpr; + let allowedToRun = true + if (gdpr) { + if (userConsent.gdpr.gdprApplies) { + if (gdpr.gdprApplies && !gdpr.vendorData.vendorConsents[GVLID]) allowedToRun = false; + } + } + if (allowedToRun) { + const providerRequests = config.params.providers.map(provider => prepProvider(provider)) + + Promise.allSettled(providerRequests).then((values) => { + const segments = values.reduce((segments, array) => (array.status === 'fulfilled') ? segments.concat(array.value.segments) : [], []).map(segmentId => ({ id: segmentId })) + setGlobalConfig(config, segments) + callback(); + }) + .catch(err => logError('ADN: err', err)); + } else callback(); +} + +/** @type {RtdSubmodule} */ +export const adnuntiusSubmodule = { + name: 'adnuntius', + init: init, + getBidRequestData: alterBidRequests, + setGlobalConfig: setGlobalConfig, +}; + +export function beforeInit() { + submodule('realTimeData', adnuntiusSubmodule); +} + +beforeInit(); diff --git a/modules/adnuntiusRtdProvider.md b/modules/adnuntiusRtdProvider.md new file mode 100644 index 00000000000..e62eba13e2c --- /dev/null +++ b/modules/adnuntiusRtdProvider.md @@ -0,0 +1,41 @@ +### Overview + +The Adnuntius Real Time Data Provider will request segments from adnuntius data, based on what is defined in the realTimeData object. It uses the siteId and userId that a publisher provides. These will have to correspond to a previously uploaded user to Adnuntius Data. + +### Integration + +1. Build the adnuntiusRTD module into the Prebid.js package with: + +``` +gulp build --modules=adnuntiusRtdProvider,... +``` + +2. Use `setConfig` to instruct Prebid.js to initilaize the adnuntiusRtdProvider module, as specified below. + +### Configuration + +``` +var pbjs = pbjs || { que: [] } +pbjs.que.push(function () { + pbjs.setConfig({ + realTimeData: { + auctionDelay: 300, + dataProviders: [ + { + name: 'adnuntius', + waitForIt: true, + params: { + bidders: ['adnuntius'], + providers: [{ + siteId: 'site123', + userId: 'user123' + }] + } + } + ] + }, + }); +}); +``` + +Please reach out to Adnuntius if you need more info about this: prebid@adnuntius.com diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js index 5bbee86df54..44c0c6868cf 100644 --- a/modules/adomikAnalyticsAdapter.js +++ b/modules/adomikAnalyticsAdapter.js @@ -1,9 +1,8 @@ import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import { logInfo } from '../src/utils.js'; -import find from 'core-js-pure/features/array/find.js'; -import findIndex from 'core-js-pure/features/array/find-index.js'; +import {logInfo} from '../src/utils.js'; +import {find, findIndex} from '../src/polyfill.js'; // Events used in adomik analytics adapter const auctionInit = CONSTANTS.EVENTS.AUCTION_INIT; @@ -12,6 +11,9 @@ const bidRequested = CONSTANTS.EVENTS.BID_REQUESTED; const bidResponse = CONSTANTS.EVENTS.BID_RESPONSE; const bidWon = CONSTANTS.EVENTS.BID_WON; const bidTimeout = CONSTANTS.EVENTS.BID_TIMEOUT; +const ua = navigator.userAgent; + +var _sampled = true; let adomikAdapter = Object.assign(adapter({}), { @@ -47,7 +49,7 @@ let adomikAdapter = Object.assign(adapter({}), type: 'request', event: { bidder: bid.bidder.toUpperCase(), - placementCode: bid.placementCode + placementCode: bid.adUnitCode } }); }); @@ -67,13 +69,20 @@ adomikAdapter.initializeBucketEvents = function() { adomikAdapter.bucketEvents = []; } +adomikAdapter.maxPartLength = function () { + return (ua.includes(' MSIE ')) ? 1600 : 60000; +}; + adomikAdapter.sendTypedEvent = function() { const groupedTypedEvents = adomikAdapter.buildTypedEvents(); const bulkEvents = { + testId: adomikAdapter.currentContext.testId, + testValue: adomikAdapter.currentContext.testValue, uid: adomikAdapter.currentContext.uid, ahbaid: adomikAdapter.currentContext.id, hostname: window.location.hostname, + sampling: adomikAdapter.currentContext.sampling, eventsByPlacementCode: groupedTypedEvents.map(function(typedEventsByType) { let sizes = []; const eventKeys = ['request', 'response', 'winner']; @@ -108,9 +117,10 @@ adomikAdapter.sendTypedEvent = function() { // Encode object in base64 const encodedBuf = window.btoa(stringBulkEvents); - // Create final url and split it in 1600 characters max (+endpoint length) + // Create final url and split it (+endpoint length) const encodedUri = encodeURIComponent(encodedBuf); - const splittedUrl = encodedUri.match(/.{1,1600}/g); + const maxLength = adomikAdapter.maxPartLength(); + const splittedUrl = encodedUri.match(new RegExp(`.{1,${maxLength}}`, 'g')); splittedUrl.forEach((split, i) => { const partUrl = `${split}&id=${adomikAdapter.currentContext.id}&part=${i}&on=${splittedUrl.length - 1}`; @@ -120,8 +130,10 @@ adomikAdapter.sendTypedEvent = function() { }; adomikAdapter.sendWonEvent = function (wonEvent) { + let keyValues = { testId: adomikAdapter.currentContext.testId, testValue: adomikAdapter.currentContext.testValue } + wonEvent = {...wonEvent, ...keyValues} const stringWonEvent = JSON.stringify(wonEvent) - logInfo('Won event sent to adomik prebid analytic ' + wonEvent); + logInfo('Won event sent to adomik prebid analytic ' + stringWonEvent); // Encode object in base64 const encodedBuf = window.btoa(stringWonEvent); @@ -193,17 +205,28 @@ adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; adomikAdapter.enableAnalytics = function (config) { adomikAdapter.currentContext = {}; - const initOptions = config.options; - if (initOptions) { - adomikAdapter.currentContext = { - uid: initOptions.id, - url: initOptions.url, - id: '', - timeouted: false, + + _sampled = typeof config === 'undefined' || + typeof config.sampling === 'undefined' || + Math.random() < parseFloat(config.sampling); + + if (_sampled) { + if (initOptions) { + adomikAdapter.currentContext = { + uid: initOptions.id, + url: initOptions.url, + testId: initOptions.testId, + testValue: initOptions.testValue, + id: '', + timeouted: false, + sampling: config.sampling + } + logInfo('Adomik Analytics enabled with config', initOptions); + adomikAdapter.adapterEnableAnalytics(config); } - logInfo('Adomik Analytics enabled with config', initOptions); - adomikAdapter.adapterEnableAnalytics(config); + } else { + logInfo('Adomik Analytics ignored for sampling', config.sampling); } }; diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index ddd9531eb43..a28ab4257df 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -1,313 +1,238 @@ import {Renderer} from '../src/Renderer.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {isStr, isArray, isNumber, isPlainObject, isBoolean, logError, replaceAuctionPrice} from '../src/utils.js'; -import find from 'core-js-pure/features/array/find.js'; -import { config } from '../src/config.js'; +import {isArray, isBoolean, isFn, isPlainObject, isStr, logError, replaceAuctionPrice} from '../src/utils.js'; +import {find} from '../src/polyfill.js'; +import {config} from '../src/config.js'; +import { OUTSTREAM } from '../src/video.js'; -const ADAPTER_VERSION = 'v1.0.0'; +const BIDDER_CODE = 'adot'; +const ADAPTER_VERSION = 'v2.0.0'; const BID_METHOD = 'POST'; const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidrequest'; -const FIRST_PRICE = 1; -const NET_REVENUE = true; -// eslint-disable-next-line no-template-curly-in-string -const AUCTION_PRICE = '${AUCTION_PRICE}'; -const TTL = 10; - -const SUPPORTED_VIDEO_CONTEXTS = ['instream', 'outstream']; -const SUPPORTED_INSTREAM_CONTEXTS = ['pre-roll', 'mid-roll', 'post-roll']; -const SUPPORTED_VIDEO_MIMES = ['video/mp4']; -const BID_SUPPORTED_MEDIA_TYPES = ['banner', 'video', 'native']; - +const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols']; const DOMAIN_REGEX = new RegExp('//([^/]*)'); -const OUTSTREAM_VIDEO_PLAYER_URL = 'https://adserver.adotmob.com/video/player.min.js'; - +const FIRST_PRICE = 1; +const IMP_BUILDER = { banner: buildBanner, video: buildVideo, native: buildNative }; const NATIVE_PLACEMENTS = { - title: {id: 1, name: 'title'}, - icon: {id: 2, type: 1, name: 'img'}, - image: {id: 3, type: 3, name: 'img'}, - sponsoredBy: {id: 4, name: 'data', type: 1}, - body: {id: 5, name: 'data', type: 2}, - cta: {id: 6, type: 12, name: 'data'} + title: { id: 1, name: 'title' }, + icon: { id: 2, type: 1, name: 'img' }, + image: { id: 3, type: 3, name: 'img' }, + sponsoredBy: { id: 4, name: 'data', type: 1 }, + body: { id: 5, name: 'data', type: 2 }, + cta: { id: 6, type: 12, name: 'data' } }; -const NATIVE_ID_MAPPING = {1: 'title', 2: 'icon', 3: 'image', 4: 'sponsoredBy', 5: 'body', 6: 'cta'}; -const NATIVE_PRESET_FORMATTERS = { - image: formatNativePresetImage -} - -function isNone(value) { - return (value === null) || (value === undefined); -} - -function groupBy(values, key) { - const groups = values.reduce((acc, value) => { - const groupId = value[key]; - - if (!acc[groupId]) acc[groupId] = []; - acc[groupId].push(value); - - return acc; - }, {}); - - return Object - .keys(groups) - .map(id => ({id, key, values: groups[id]})); -} - -function validateMediaTypes(mediaTypes, allowedMediaTypes) { - if (!isPlainObject(mediaTypes)) return false; - if (!allowedMediaTypes.some(mediaType => mediaType in mediaTypes)) return false; - - if (isBanner(mediaTypes)) { - if (!validateBanner(mediaTypes.banner)) return false; - } - - if (isVideo(mediaTypes)) { - if (!validateVideo(mediaTypes.video)) return false; +const NATIVE_ID_MAPPING = { 1: 'title', 2: 'icon', 3: 'image', 4: 'sponsoredBy', 5: 'body', 6: 'cta' }; +const OUTSTREAM_VIDEO_PLAYER_URL = 'https://adserver.adotmob.com/video/player.min.js'; +const BID_RESPONSE_NET_REVENUE = true; +const BID_RESPONSE_TTL = 10; +const DEFAULT_CURRENCY = 'USD'; + +/** + * Parse string in plain object + * + * @param {string} data + * @returns {object|null} Parsed object or null + */ +function tryParse(data) { + try { + return JSON.parse(data); + } catch (err) { + logError(err); + return null; } - - return true; -} - -function isBanner(mediaTypes) { - return isPlainObject(mediaTypes) && isPlainObject(mediaTypes.banner); -} - -function isVideo(mediaTypes) { - return isPlainObject(mediaTypes) && 'video' in mediaTypes; -} - -function validateBanner(banner) { - return isPlainObject(banner) && - isArray(banner.sizes) && - (banner.sizes.length > 0) && - banner.sizes.every(validateMediaSizes); } -function validateVideo(video) { - if (!isPlainObject(video)) return false; - if (!isStr(video.context)) return false; - if (SUPPORTED_VIDEO_CONTEXTS.indexOf(video.context) === -1) return false; - - if (!video.playerSize) return true; - if (!isArray(video.playerSize)) return false; - - return video.playerSize.every(validateMediaSizes); -} - -function validateMediaSizes(mediaSize) { - return isArray(mediaSize) && - (mediaSize.length === 2) && - mediaSize.every(size => (isNumber(size) && size >= 0)); -} - -function validateParameters(parameters, adUnit) { - if (isVideo(adUnit.mediaTypes)) { - if (!isPlainObject(parameters)) return false; - if (!isPlainObject(adUnit.mediaTypes.video)) return false; - if (!validateVideoParameters(parameters.video, adUnit)) return false; - } - - return true; +/** + * Extract domain from given url + * + * @param {string} url + * @returns {string|null} Extracted domain + */ +function extractDomainFromURL(url) { + if (!url || !isStr(url)) return null; + const domain = url.match(DOMAIN_REGEX); + if (isArray(domain) && domain.length === 2) return domain[1]; + return null; } -function validateVideoParameters(videoParams, adUnit) { - const video = adUnit.mediaTypes.video; - - if (!video) return false; +/** + * Create and return site OpenRtb object from given bidderRequest + * + * @param {BidderRequest} bidderRequest + * @returns {Site|null} Formatted Site OpenRtb object or null + */ +function getOpenRTBSiteObject(bidderRequest) { + if (!bidderRequest || !bidderRequest.refererInfo) return null; - if (!isArray(video.mimes)) return false; - if (video.mimes.length === 0) return false; - if (!video.mimes.every(isStr)) return false; - - if (video.minDuration && !isNumber(video.minDuration)) return false; - if (video.maxDuration && !isNumber(video.maxDuration)) return false; - - if (!isArray(video.protocols)) return false; - if (video.protocols.length === 0) return false; - if (!video.protocols.every(isNumber)) return false; - - if (isInstream(video)) { - if (!videoParams.instreamContext) return false; - if (SUPPORTED_INSTREAM_CONTEXTS.indexOf(videoParams.instreamContext) === -1) return false; - } - - return true; -} + const domain = extractDomainFromURL(bidderRequest.refererInfo.referer); + const publisherId = config.getConfig('adot.publisherId'); -function validateServerRequest(serverRequest) { - return isPlainObject(serverRequest) && - isPlainObject(serverRequest.data) && - isArray(serverRequest.data.imp) && - isPlainObject(serverRequest._adot_internal) && - isArray(serverRequest._adot_internal.impressions) -} + if (!domain) return null; -function createServerRequestFromAdUnits(adUnits, bidRequestId, adUnitContext) { - const publisherPath = config.getConfig('adot.publisherPath') === undefined ? '' : '/' + config.getConfig('adot.publisherPath'); return { - method: BID_METHOD, - url: BIDDER_URL.replace('{PUBLISHER_PATH}', publisherPath), - data: generateBidRequestsFromAdUnits(adUnits, bidRequestId, adUnitContext), - _adot_internal: generateAdotInternal(adUnits) - } -} - -function generateAdotInternal(adUnits) { - const impressions = adUnits.reduce((acc, adUnit) => { - const {bidId, mediaTypes, adUnitCode, params} = adUnit; - const base = {bidId, adUnitCode, container: params.video && params.video.container}; - - const imps = Object - .keys(mediaTypes) - .reduce((acc, mediaType, index) => { - const data = mediaTypes[mediaType]; - const impressionId = `${bidId}_${index}`; - - if (mediaType !== 'banner') return acc.concat({...base, impressionId}); - - const bannerImps = data.sizes.map((item, i) => ({...base, impressionId: `${impressionId}_${i}`})); - - return acc.concat(bannerImps); - }, []); - - return acc.concat(imps); - }, []); - - return {impressions}; + page: bidderRequest.refererInfo.referer, + domain: domain, + name: domain, + publisher: { + id: publisherId + } + }; } -function generateBidRequestsFromAdUnits(adUnits, bidRequestId, adUnitContext) { +/** + * Create and return Device OpenRtb object + * + * @returns {Device} Formatted Device OpenRtb object or null + */ +function getOpenRTBDeviceObject() { + return { ua: navigator.userAgent, language: navigator.language }; +} + +/** + * Create and return User OpenRtb object + * + * @param {BidderRequest} bidderRequest + * @returns {User|null} Formatted User OpenRtb object or null + */ +function getOpenRTBUserObject(bidderRequest) { + if (!bidderRequest || !bidderRequest.gdprConsent || !isStr(bidderRequest.gdprConsent.consentString)) return null; + return { ext: { consent: bidderRequest.gdprConsent.consentString } }; +} + +/** + * Create and return Regs OpenRtb object + * + * @param {BidderRequest} bidderRequest + * @returns {Regs|null} Formatted Regs OpenRtb object or null + */ +function getOpenRTBRegsObject(bidderRequest) { + if (!bidderRequest || !bidderRequest.gdprConsent || !isBoolean(bidderRequest.gdprConsent.gdprApplies)) return null; + return { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies } }; +} + +/** + * Create and return Ext OpenRtb object + * + * @param {BidderRequest} bidderRequest + * @returns {Ext|null} Formatted Ext OpenRtb object or null + */ +function getOpenRTBExtObject() { return { - id: bidRequestId, - imp: adUnits.reduce(generateImpressionsFromAdUnit, []), - site: generateSiteFromAdUnitContext(adUnitContext), - device: getDeviceInfo(), - user: getUserInfoFromAdUnitContext(adUnitContext), - regs: getRegulationFromAdUnitContext(adUnitContext), - at: FIRST_PRICE, - ext: generateBidRequestExtension() + adot: { adapter_version: ADAPTER_VERSION }, + should_use_gzip: true }; } -function generateImpressionsFromAdUnit(acc, adUnit) { - const {bidId, mediaTypes, params} = adUnit; - const {placementId} = params; - const pmp = {}; - const ext = {placementId}; - - if (placementId) pmp.deals = [{id: placementId}] - - const imps = Object - .keys(mediaTypes) - .reduce((acc, mediaType, index) => { - const data = mediaTypes[mediaType]; - const impId = `${bidId}_${index}`; - - if (mediaType === 'banner') return acc.concat(generateBannerFromAdUnit(impId, data, params)); - if (mediaType === 'video') return acc.concat({id: impId, video: generateVideoFromAdUnit(data, params), pmp, ext}); - if (mediaType === 'native') return acc.concat({id: impId, native: generateNativeFromAdUnit(data), pmp, ext}); - }, []); - - return acc.concat(imps); -} - -function isImpressionAVideo(impression) { - return isPlainObject(impression) && isPlainObject(impression.video); +/** + * Return MediaType from MediaTypes object + * + * @param {MediaType} mediaTypes Prebid MediaTypes + * @returns {string|null} Mediatype or null if not found + */ +function getMediaType(mediaTypes) { + if (mediaTypes.banner) return 'banner'; + if (mediaTypes.video) return 'video'; + if (mediaTypes.native) return 'native'; + return null; } -function generateBannerFromAdUnit(impId, data, params) { - const {position, placementId} = params; - const pos = position || 0; - const pmp = {}; - const ext = {placementId}; - - if (placementId) pmp.deals = [{id: placementId}] +/** + * Build OpenRtb imp banner from given bidderRequest and media + * + * @param {Banner} banner MediaType Banner Object + * @param {BidderRequest} bidderRequest + * @returns {OpenRtbBanner} OpenRtb banner object + */ +function buildBanner(banner, bidderRequest) { + const pos = bidderRequest.position || 0; + const format = (banner.sizes || []).map(([w, h]) => ({ w, h })); + return { format, pos }; +} + +/** + * Build object with w and h value depending on given video media + * + * @param {Video} video MediaType Video Object + * @returns {Object} Size as { w: number; h: number } + */ +function getVideoSize(video) { + const sizes = video.playerSize || []; + const format = sizes.length > 0 ? sizes[0] : []; - return data.sizes.map(([w, h], index) => ({id: `${impId}_${index}`, banner: {format: [{w, h}], w, h, pos}, pmp, ext})); + return { + w: format[0] || null, + h: format[1] || null + }; } -function generateVideoFromAdUnit(data, params) { - const {playerSize} = data; - const video = data - - const hasPlayerSize = isArray(playerSize) && playerSize.length > 0; - const {minDuration, maxDuration, protocols} = video; - - const size = {width: hasPlayerSize ? playerSize[0][0] : null, height: hasPlayerSize ? playerSize[0][1] : null}; - const duration = {min: isNumber(minDuration) ? minDuration : null, max: isNumber(maxDuration) ? maxDuration : null}; - const startdelay = computeStartDelay(data, params); +/** + * Build OpenRtb imp video from given bidderRequest and media + * + * @param {Video} video MediaType Video Object + * @returns {OpenRtbVideo} OpenRtb video object + */ +function buildVideo(video) { + const { w, h } = getVideoSize(video); return { - mimes: SUPPORTED_VIDEO_MIMES, - skip: video.skippable || 0, - w: size.width, - h: size.height, - startdelay: startdelay, + api: video.api, + w, + h, linearity: video.linearity || null, - minduration: duration.min, - maxduration: duration.max, - protocols, - api: getApi(protocols), - format: hasPlayerSize ? playerSize.map(s => { - return {w: s[0], h: s[1]}; - }) : null, - pos: video.position || 0 + mimes: video.mimes, + minduration: video.minduration, + maxduration: video.maxduration, + placement: video.placement, + playbackmethod: video.playbackmethod, + pos: video.position || 0, + protocols: video.protocols, + skip: video.skip || 0, + startdelay: video.startdelay }; } -function getApi(protocols) { - let defaultValue = [2]; - let listProtocols = [ - {key: 'VPAID_1_0', value: 1}, - {key: 'VPAID_2_0', value: 2}, - {key: 'MRAID_1', value: 3}, - {key: 'ORMMA', value: 4}, - {key: 'MRAID_2', value: 5}, - {key: 'MRAID_3', value: 6}, - ]; - if (protocols) { - return listProtocols.filter(p => { - return protocols.indexOf(p.key) !== -1; - }).map(p => p.value) - } else { - return defaultValue; - } -} - -function isInstream(video) { - return isPlainObject(video) && (video.context === 'instream'); -} +/** + * Check if given Native Media is an asset of type Image. + * + * Return default native assets if given media is an asset + * Return given native assets if given media is not an asset + * + * @param {NativeMedia} native Native Mediatype + * @returns {OpenRtbNativeAssets} + */ +function cleanNativeMedia(native) { + if (native.type !== 'image') return native; -function isOutstream(video) { - return isPlainObject(video) && (video.startdelay === null) + return { + image: { required: true, sizes: native.sizes }, + title: { required: true }, + sponsoredBy: { required: true }, + body: { required: false }, + cta: { required: false }, + icon: { required: false } + }; } -function computeStartDelay(data, params) { - if (isInstream(data)) { - if (params.video.instreamContext === 'pre-roll') return 0; - if (params.video.instreamContext === 'mid-roll') return -1; - if (params.video.instreamContext === 'post-roll') return -2; - } - - return null; -} +/** + * Build Native OpenRtb Imp from Native Mediatype + * + * @param {NativeMedia} native Native Mediatype + * @returns {OpenRtbNative} + */ +function buildNative(native) { + native = cleanNativeMedia(native); -function generateNativeFromAdUnit(data) { - const {type} = data; - const presetFormatter = type && NATIVE_PRESET_FORMATTERS[data.type]; - const nativeFields = presetFormatter ? presetFormatter(data) : data; + const assets = Object.keys(native) + .reduce((nativeAssets, assetKey) => { + const asset = native[assetKey]; + const assetInfo = NATIVE_PLACEMENTS[assetKey]; - const assets = Object - .keys(nativeFields) - .reduce((acc, placement) => { - const placementData = nativeFields[placement]; - const assetInfo = NATIVE_PLACEMENTS[placement]; + if (!assetInfo) return nativeAssets; - if (!assetInfo) return acc; + const { id, name, type } = assetInfo; + const { required, len, sizes = [] } = asset; - const {id, name, type} = assetInfo; - const {required, len, sizes = []} = placementData; let wmin; let hmin; @@ -319,249 +244,165 @@ function generateNativeFromAdUnit(data) { hmin = sizes[1]; } - const content = {}; + const newAsset = {}; - if (type) content.type = type; - if (len) content.len = len; - if (wmin) content.wmin = wmin; - if (hmin) content.hmin = hmin; + if (type) newAsset.type = type; + if (len) newAsset.len = len; + if (wmin) newAsset.wmin = wmin; + if (hmin) newAsset.hmin = hmin; - acc.push({id, required, [name]: content}); + nativeAssets.push({ id, required, [name]: newAsset }); - return acc; + return nativeAssets; }, []); - return { - request: JSON.stringify({assets}) - }; + return { request: JSON.stringify({ assets }) }; } -function formatNativePresetImage(data) { - const sizes = data.sizes; +/** + * Build OpenRtb Imp object from given Adunit and Context + * + * @param {AdUnit} adUnit PrebidJS Adunit + * @param {BidderRequest} bidderRequest PrebidJS Bidder Request + * @returns {Imp} OpenRtb Impression + */ +function buildImpFromAdUnit(adUnit, bidderRequest) { + const { bidId, mediaTypes, params, adUnitCode } = adUnit; + const mediaType = getMediaType(mediaTypes); - return { - image: { - required: true, - sizes - }, - title: { - required: true - }, - sponsoredBy: { - required: true - }, - body: { - required: false - }, - cta: { - required: false - }, - icon: { - required: false - } - }; -} + if (!mediaType) return null; -function generateSiteFromAdUnitContext(adUnitContext) { - if (!adUnitContext || !adUnitContext.refererInfo) return null; - - const domain = extractSiteDomainFromURL(adUnitContext.refererInfo.referer); - const publisherId = config.getConfig('adot.publisherId'); - - if (!domain) return null; - - return { - page: adUnitContext.refererInfo.referer, - domain: domain, - name: domain, - publisher: { - id: publisherId - } - }; -} - -function extractSiteDomainFromURL(url) { - if (!url || !isStr(url)) return null; - - const domain = url.match(DOMAIN_REGEX); - - if (isArray(domain) && domain.length === 2) return domain[1]; - - return null; -} - -function getDeviceInfo() { - return {ua: navigator.userAgent, language: navigator.language}; -} - -function getUserInfoFromAdUnitContext(adUnitContext) { - if (!adUnitContext || !adUnitContext.gdprConsent) return null; - if (!isStr(adUnitContext.gdprConsent.consentString)) return null; - - return { - ext: { - consent: adUnitContext.gdprConsent.consentString - } - }; -} - -function getRegulationFromAdUnitContext(adUnitContext) { - if (!adUnitContext || !adUnitContext.gdprConsent) return null; - if (!isBoolean(adUnitContext.gdprConsent.gdprApplies)) return null; + const media = IMP_BUILDER[mediaType](mediaTypes[mediaType], bidderRequest, adUnit) + const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; + const bidfloor = getMainFloor(adUnit, media.format, mediaType, currency); return { + id: bidId, ext: { - gdpr: adUnitContext.gdprConsent.gdprApplies - } - }; -} - -function generateBidRequestExtension() { - return { - adot: {adapter_version: ADAPTER_VERSION}, - should_use_gzip: true + placementId: params.placementId, + adUnitCode, + container: params.video && params.video.container + }, + [mediaType]: media, + bidfloorcur: currency, + bidfloor }; } -function validateServerResponse(serverResponse) { - return isPlainObject(serverResponse) && - isPlainObject(serverResponse.body) && - isStr(serverResponse.body.cur) && - isArray(serverResponse.body.seatbid); -} - -function seatBidsToAds(seatBid, bidResponse, serverRequest) { - return seatBid.bid - .filter(bid => validateBids(bid, serverRequest)) - .map(bid => generateAdFromBid(bid, bidResponse, serverRequest)); -} - -function validateBids(bid, serverRequest) { - if (!isPlainObject(bid)) return false; - if (!isStr(bid.impid)) return false; - if (!isStr(bid.crid)) return false; - if (!isNumber(bid.price)) return false; - - if (!isPlainObject(bid.ext)) return false; - if (!isPlainObject(bid.ext.adot)) return false; - if (!isStr(bid.ext.adot.media_type)) return false; - if (BID_SUPPORTED_MEDIA_TYPES.indexOf(bid.ext.adot.media_type) === -1) return false; - - if (!bid.adm && !bid.nurl) return false; - if (bid.adm) { - if (!isStr(bid.adm)) return false; - if (bid.adm.indexOf(AUCTION_PRICE) === -1) return false; - } - if (bid.nurl) { - if (!isStr(bid.nurl)) return false; - if (bid.nurl.indexOf(AUCTION_PRICE) === -1) return false; - } - - if (isBidABanner(bid)) { - if (!isNumber(bid.h)) return false; - if (!isNumber(bid.w)) return false; - } - if (isBidAVideo(bid)) { - if (!(isNone(bid.h) || isNumber(bid.h))) return false; - if (!(isNone(bid.w) || isNumber(bid.w))) return false; - } - - const impression = getImpressionData(serverRequest, bid.impid); - - if (!isPlainObject(impression.openRTB)) return false; - if (!isPlainObject(impression.internal)) return false; - if (!isStr(impression.internal.adUnitCode)) return false; - - if (isBidABanner(bid)) { - if (!isPlainObject(impression.openRTB.banner)) return false; - } - if (isBidAVideo(bid)) { - if (!isPlainObject(impression.openRTB.video)) return false; - } - if (isBidANative(bid)) { - if (!isPlainObject(impression.openRTB.native) || !tryParse(bid.adm)) return false; - } - +/** + * Return if given video is Valid. + * A video is defined as valid if it contains all required fields + * + * @param {VideoMedia} video + * @returns {boolean} + */ +function isValidVideo(video) { + if (REQUIRED_VIDEO_PARAMS.some((param) => video[param] === undefined)) return false; return true; } -function isBidABanner(bid) { - return isPlainObject(bid) && - isPlainObject(bid.ext) && - isPlainObject(bid.ext.adot) && - bid.ext.adot.media_type === 'banner'; -} - -function isBidAVideo(bid) { - return isPlainObject(bid) && - isPlainObject(bid.ext) && - isPlainObject(bid.ext.adot) && - bid.ext.adot.media_type === 'video'; -} - -function isBidANative(bid) { - return isPlainObject(bid) && - isPlainObject(bid.ext) && - isPlainObject(bid.ext.adot) && - bid.ext.adot.media_type === 'native'; -} - -function getImpressionData(serverRequest, impressionId) { - const openRTBImpression = find(serverRequest.data.imp, imp => imp.id === impressionId); - const internalImpression = find(serverRequest._adot_internal.impressions, imp => imp.impressionId === impressionId); - +/** + * Return if given bid is Valid. + * A bid is defined as valid if it media is a valid video or other media + * + * @param {Bid} bid + * @returns {boolean} + */ +function isBidRequestValid(bid) { + const video = bid.mediaTypes.video; + return !video || isValidVideo(video); +} + +/** + * Build OpenRtb request from Prebid AdUnits and Bidder request + * + * @param {Array} adUnits Array of PrebidJS Adunit + * @param {BidderRequest} bidderRequest PrebidJS BidderRequest + * @param {string} requestId Request ID + * + * @returns {OpenRTBBidRequest} OpenRTB bid request + */ +function buildBidRequest(adUnits, bidderRequest, requestId) { + const data = { + id: requestId, + imp: adUnits.map((adUnit) => buildImpFromAdUnit(adUnit, bidderRequest)).filter((item) => !!item), + site: getOpenRTBSiteObject(bidderRequest), + device: getOpenRTBDeviceObject(), + user: getOpenRTBUserObject(bidderRequest), + regs: getOpenRTBRegsObject(bidderRequest), + ext: getOpenRTBExtObject(), + at: FIRST_PRICE + }; + return data; +} + +/** + * Build PrebidJS Ajax request + * + * @param {Array} adUnits Array of PrebidJS Adunit + * @param {BidderRequest} bidderRequest PrebidJS BidderRequest + * @param {string} bidderUrl Adot Bidder URL + * @param {string} requestId Request ID + * @returns + */ +function buildAjaxRequest(adUnits, bidderRequest, bidderUrl, requestId) { return { - id: impressionId, - openRTB: openRTBImpression || null, - internal: internalImpression || null + method: BID_METHOD, + url: bidderUrl, + data: buildBidRequest(adUnits, bidderRequest, requestId) }; } -function generateAdFromBid(bid, bidResponse, serverRequest) { - const impressionData = getImpressionData(serverRequest, bid.impid); - const isVideo = isBidAVideo(bid); - const base = { - requestId: impressionData.internal.bidId, - cpm: bid.price, - currency: bidResponse.cur, - ttl: TTL, - creativeId: bid.crid, - netRevenue: NET_REVENUE, - mediaType: bid.ext.adot.media_type, - }; - - if (bid.adomain) { - base.meta = { advertiserDomains: bid.adomain }; - } - - if (isBidANative(bid)) return {...base, native: formatNativeData(bid)}; - - const size = getSizeFromBid(bid, impressionData); - const creative = getCreativeFromBid(bid, impressionData); - - return { - ...base, - height: size.height, - width: size.width, - ad: creative.markup, - adUrl: creative.markupUrl, - vastXml: isVideo && !isStr(creative.markupUrl) ? creative.markup : null, - vastUrl: isVideo && isStr(creative.markupUrl) ? creative.markupUrl : null, - renderer: creative.renderer - }; +/** + * Split given PrebidJS Request in Dictionnary + * + * @param {Array} validBidRequests + * @returns {Dictionnary} + */ +function splitAdUnits(validBidRequests) { + return validBidRequests.reduce((adUnits, adUnit) => { + const bidderRequestId = adUnit.bidderRequestId; + if (!adUnits[bidderRequestId]) { + adUnits[bidderRequestId] = []; + } + adUnits[bidderRequestId].push(adUnit); + return adUnits; + }, {}); } -function formatNativeData({adm, price}) { +/** + * Build Ajax request Array + * + * @param {Array} validBidRequests + * @param {BidderRequest} bidderRequest + * @returns {Array} + */ +function buildRequests(validBidRequests, bidderRequest) { + const adUnits = splitAdUnits(validBidRequests); + const publisherPathConfig = config.getConfig('adot.publisherPath'); + const publisherPath = publisherPathConfig === undefined ? '' : '/' + publisherPathConfig; + const bidderUrl = BIDDER_URL.replace('{PUBLISHER_PATH}', publisherPath); + + return Object.keys(adUnits).map((requestId) => buildAjaxRequest(adUnits[requestId], bidderRequest, bidderUrl, requestId)); +} + +/** + * Build Native PrebidJS Response grom OpenRtb Response + * + * @param {OpenRtbBid} bid + * + * @returns {NativeAssets} Native PrebidJS + */ +function buildNativeBidData(bid) { + const { adm, price } = bid; const parsedAdm = tryParse(adm); - const {assets, link: {url, clicktrackers}, imptrackers, jstracker} = parsedAdm.native; - const placements = NATIVE_PLACEMENTS; - const placementIds = NATIVE_ID_MAPPING; + const { assets, link: { url, clicktrackers }, imptrackers, jstracker } = parsedAdm.native; return assets.reduce((acc, asset) => { - const placementName = placementIds[asset.id]; - const content = placementName && asset[placements[placementName].name]; + const placementName = NATIVE_ID_MAPPING[asset.id]; + const content = placementName && asset[NATIVE_PLACEMENTS[placementName].name]; if (!content) return acc; - acc[placementName] = content.text || content.value || {url: content.url, width: content.w, height: content.h}; + acc[placementName] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; return acc; }, { clickUrl: url, @@ -571,57 +412,38 @@ function formatNativeData({adm, price}) { }); } -function getSizeFromBid(bid, impressionData) { - if (isNumber(bid.w) && isNumber(bid.h)) { - return { width: bid.w, height: bid.h }; - } - - if (isImpressionAVideo(impressionData.openRTB)) { - const { video } = impressionData.openRTB; - - if (isNumber(video.w) && isNumber(video.h)) { - return { width: video.w, height: video.h }; - } - } - - return { width: null, height: null }; -} - -function getCreativeFromBid(bid, impressionData) { - const shouldUseAdMarkup = !!bid.adm; - const price = bid.price; +/** + * Return Adot Renderer if given Bid is a video one + * + * @param {OpenRtbBid} bid + * @param {string} mediaType + * @returns {any|null} + */ +function buildRenderer(bid, mediaType) { + if (!(mediaType === VIDEO && + bid.ext && + bid.ext.adot && + bid.ext.adot.container && + bid.ext.adot.adUnitCode && + bid.ext.adot.video && + bid.ext.adot.video.type === OUTSTREAM)) return null; + + const container = bid.ext.adot.container + const adUnitCode = bid.ext.adot.adUnitCode - return { - markup: shouldUseAdMarkup ? replaceAuctionPrice(bid.adm, price) : null, - markupUrl: !shouldUseAdMarkup ? replaceAuctionPrice(bid.nurl, price) : null, - renderer: getRendererFromBid(bid, impressionData) - }; -} - -function getRendererFromBid(bid, impressionData) { - const isOutstreamImpression = isBidAVideo(bid) && - isImpressionAVideo(impressionData.openRTB) && - isOutstream(impressionData.openRTB.video); - - return isOutstreamImpression - ? buildOutstreamRenderer(impressionData) - : null; -} - -function buildOutstreamRenderer(impressionData) { const renderer = Renderer.install({ url: OUTSTREAM_VIDEO_PLAYER_URL, loaded: false, - adUnitCode: impressionData.internal.adUnitCode + adUnitCode: adUnitCode }); renderer.setRender((ad) => { ad.renderer.push(() => { - const container = impressionData.internal.container - ? document.querySelector(impressionData.internal.container) - : document.getElementById(impressionData.internal.adUnitCode); + const domContainer = container + ? document.querySelector(container) + : document.getElementById(adUnitCode); - const player = new window.VASTPlayer(container); + const player = new window.VASTPlayer(domContainer); player.on('ready', () => { player.adVolume = 0; @@ -641,54 +463,181 @@ function buildOutstreamRenderer(impressionData) { return renderer; } -function tryParse(data) { - try { - return JSON.parse(data); - } catch (err) { - logError(err); - return null; - } +/** + * Build PrebidJS response from OpenRtbBid + * + * @param {OpenRtbBid} bid + * @param {string} mediaType + * @returns {Object} + */ +function buildCreativeBidData(bid, mediaType) { + const adm = bid.adm ? replaceAuctionPrice(bid.adm, bid.price) : null; + const nurl = (!bid.adm && bid.nurl) ? replaceAuctionPrice(bid.nurl, bid.price) : null; + + return { + width: bid.ext.adot.size && bid.ext.adot.size.w, + height: bid.ext.adot.size && bid.ext.adot.size.h, + ad: adm, + adUrl: nurl, + vastXml: mediaType === VIDEO && !isStr(nurl) ? adm : null, + vastUrl: mediaType === VIDEO && isStr(nurl) ? nurl : null, + renderer: buildRenderer(bid, mediaType) + }; } -const adotBidderSpec = { - code: 'adot', - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid(adUnit) { - const allowedBidderCodes = [this.code]; - - return isPlainObject(adUnit) && - allowedBidderCodes.indexOf(adUnit.bidder) !== -1 && - isStr(adUnit.adUnitCode) && - isStr(adUnit.bidderRequestId) && - isStr(adUnit.bidId) && - validateMediaTypes(adUnit.mediaTypes, this.supportedMediaTypes) && - validateParameters(adUnit.params, adUnit); - }, - buildRequests(adUnits, adUnitContext) { - if (!adUnits) return null; - - return groupBy(adUnits, 'bidderRequestId').map(group => { - const bidRequestId = group.id; - const adUnits = groupBy(group.values, 'bidId').map((group) => { - const length = group.values.length; - return length > 0 && group.values[length - 1] - }); +/** + * Return if given bid and imp are valid + * + * @param {OpenRtbBid} bid OpenRtb Bid + * @param {Imp} imp OpenRtb Imp + * @returns {boolean} + */ +function isBidImpInvalid(bid, imp) { + return !bid || !imp; +} + +/** + * Build PrebidJS Bid Response from given OpenRTB Bid + * + * @param {OpenRtbBid} bid + * @param {OpenRtbBidResponse} bidResponse + * @param {OpenRtbBid} imp + * @returns {PrebidJSResponse} + */ +function buildBidResponse(bid, bidResponse, imp) { + if (isBidImpInvalid(bid, imp)) return null; + const mediaType = bid.ext.adot.media_type; + const baseBid = { + requestId: bid.impid, + cpm: bid.price, + currency: bidResponse.cur, + ttl: BID_RESPONSE_TTL, + creativeId: bid.crid, + netRevenue: BID_RESPONSE_NET_REVENUE, + mediaType + }; - return createServerRequestFromAdUnits(adUnits, bidRequestId, adUnitContext) + if (bid.dealid) baseBid.dealId = bid.dealid; + if (bid.adomain) baseBid.meta = { advertiserDomains: bid.adomain }; + + if (mediaType === NATIVE) return { ...baseBid, native: buildNativeBidData(bid) }; + return { ...baseBid, ...buildCreativeBidData(bid, mediaType) }; +} + +/** + * Find OpenRtb Imp from request with same id that given bid + * + * @param {OpenRtbBid} bid + * @param {OpenRtbRequest} bidRequest + * @returns {Imp} OpenRtb Imp + */ +function getImpfromBid(bid, bidRequest) { + if (!bidRequest || !bidRequest.imp) return null; + const imps = bidRequest.imp; + return find(imps, (imp) => imp.id === bid.impid); +} + +/** + * Return if given response is valid + * + * @param {OpenRtbBidResponse} response + * @returns {boolean} + */ +function isValidResponse(response) { + return isPlainObject(response) && + isPlainObject(response.body) && + isStr(response.body.cur) && + isArray(response.body.seatbid); +} + +/** + * Return if given request is valid + * + * @param {OpenRtbRequest} request + * @returns {boolean} + */ +function isValidRequest(request) { + return isPlainObject(request) && + isPlainObject(request.data) && + isArray(request.data.imp); +} + +/** + * Interpret given OpenRtb Response to build PrebidJS Response + * + * @param {OpenRtbBidResponse} serverResponse + * @param {OpenRtbRequest} request + * @returns {PrebidJSResponse} + */ +function interpretResponse(serverResponse, request) { + if (!isValidResponse(serverResponse) || !isValidRequest(request)) return []; + + const bidsResponse = serverResponse.body; + const bidRequest = request.data; + + return bidsResponse.seatbid.reduce((pbsResponse, seatbid) => { + if (!seatbid || !isArray(seatbid.bid)) return pbsResponse; + seatbid.bid.forEach((bid) => { + const imp = getImpfromBid(bid, bidRequest); + const bidResponse = buildBidResponse(bid, bidsResponse, imp); + if (bidResponse) pbsResponse.push(bidResponse); }); - }, - interpretResponse(serverResponse, serverRequest) { - if (!validateServerRequest(serverRequest)) return []; - if (!validateServerResponse(serverResponse)) return []; - - const bidResponse = serverResponse.body; + return pbsResponse; + }, []); +} - return bidResponse.seatbid - .filter(seatBid => isPlainObject(seatBid) && isArray(seatBid.bid)) - .reduce((acc, seatBid) => acc.concat(seatBidsToAds(seatBid, bidResponse, serverRequest)), []); - } +/** + * Call Adunit getFloor function with given argument to get specific floor. + * Return 0 by default + * + * @param {AdUnit} adUnit + * @param {Array|string} size Adunit size or * + * @param {string} mediaType + * @param {string} currency USD by default + * + * @returns {number} Floor price + */ +function getFloor(adUnit, size, mediaType, currency) { + if (!isFn(adUnit.getFloor)) return 0; + + const floorResult = adUnit.getFloor({ currency, mediaType, size }); + + return floorResult.currency === currency ? floorResult.floor : 0; +} + +/** + * Call getFloor for each format and return the lower floor + * Return 0 by default + * + * interface Format { w: number; h: number } + * + * @param {AdUnit} adUnit + * @param {Array} formats Media formats + * @param {string} mediaType + * @param {string} currency USD by default + * + * @returns {number} Lower floor. + */ +function getMainFloor(adUnit, formats, mediaType, currency) { + if (!formats) return getFloor(adUnit, '*', mediaType, currency); + + return formats.reduce((bidFloor, format) => { + const floor = getFloor(adUnit, [format.w, format.h], mediaType, currency) + const maxFloor = bidFloor || Number.MAX_SAFE_INTEGER; + return floor !== 0 && floor < maxFloor ? floor : bidFloor; + }, null) || 0; +} + +/** + * Adot PrebidJS Adapter + */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getFloor }; -registerBidder(adotBidderSpec); - -export {adotBidderSpec as spec}; +registerBidder(spec); diff --git a/modules/adotBidAdapter.md b/modules/adotBidAdapter.md index 894a592ec18..d1622e5f901 100644 --- a/modules/adotBidAdapter.md +++ b/modules/adotBidAdapter.md @@ -6,7 +6,7 @@ Adot Bidder Adapter is a module that enables the communication between the Prebi - Module name: Adot Bidder Adapter - Module type: Bidder Adapter -- Maintainer: `aurelien.giudici@adotmob.com` +- Maintainer: `alexandre.lorin@adotmob.com` - Supported media types: `banner`, `video`, `native` ## Example ad units @@ -34,9 +34,9 @@ const adUnit = { ### Video ad unit -#### Outstream video ad unit +#### Video ad unit -Adot Bidder Adapter accepts outstream video ad units using the following ad unit format: +Adot Bidder Adapter accepts video ad units using the following ad unit format: ```javascript const adUnit = { @@ -51,9 +51,9 @@ const adUnit = { // Content MIME types supported by the ad unit. mimes: ['video/mp4'], // Minimum accepted video ad duration (in seconds). - minDuration: 5, + minduration: 5, // Maximum accepted video ad duration (in seconds). - maxDuration: 35, + maxduration: 35, // Video protocols supported by the ad unit (see the OpenRTB 2.5 specifications, // section 5.8). protocols: [2, 3] @@ -61,45 +61,7 @@ const adUnit = { }, bids: [{ bidder: 'adot', - params: { - video: {} - } - }] -} -``` - -#### Instream video ad unit - -Adot Bidder Adapter accepts instream video ad units using the following ad unit format: - -```javascript -const adUnit = { - code: 'test-div', - mediaTypes: { - video: { - // Video context. Must be 'instream'. - context: 'instream', - // Video dimensions supported by the video ad unit. - // Each ad unit size is formatted as follows: [width, height]. - playerSize: [[300, 250]], - // Content MIME types supported by the ad unit. - mimes: ['video/mp4'], - // Minimum accepted video ad duration (in seconds). - minDuration: 5, - // Maximum accepted video ad duration (in seconds). - maxDuration: 35, - // Video protocols supported by the ad unit (see the OpenRTB 2.5 specifications, - // section 5.8). - protocols: [2, 3] - } - }, - bids: [{ - bidder: 'adot', - params: { - video: { - instreamContext: 'pre-roll' - } - } + params: {} }] } ``` diff --git a/modules/adplusBidAdapter.js b/modules/adplusBidAdapter.js new file mode 100644 index 00000000000..4707ca2ff5a --- /dev/null +++ b/modules/adplusBidAdapter.js @@ -0,0 +1,203 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +// #region Constants +export const BIDDER_CODE = 'adplus'; +export const ADPLUS_ENDPOINT = 'https://ssp.ad-plus.com.tr/server/headerBidding'; +export const DGID_CODE = 'adplus_dg_id'; +export const SESSION_CODE = 'adplus_s_id'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); +const COOKIE_EXP = 1000 * 60 * 60 * 24; // 1 day +// #endregion + +// #region Helpers +export function isValidUuid (uuid) { + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( + uuid + ); +} + +function getSessionId() { + let sid = storage.cookiesAreEnabled() && storage.getCookie(SESSION_CODE); + + if ( + !sid || !isValidUuid(sid) + ) { + sid = utils.generateUUID(); + setSessionId(sid); + } + + return sid; +} + +function setSessionId(sid) { + if (storage.cookiesAreEnabled()) { + const expires = new Date(Date.now() + COOKIE_EXP).toISOString(); + + storage.setCookie(SESSION_CODE, sid, expires); + } +} +// #endregion + +// #region Bid request validation +function isBidRequestValid(bid) { + if (!bid) { + utils.logError(BIDDER_CODE, 'bid, can not be empty', bid); + return false; + } + + if (!bid.params) { + utils.logError(BIDDER_CODE, 'bid.params is required.'); + return false; + } + + if (!bid.params.adUnitId || typeof bid.params.adUnitId !== 'string') { + utils.logError( + BIDDER_CODE, + 'bid.params.adUnitId is missing or has wrong type.' + ); + return false; + } + + if (!bid.params.inventoryId || typeof bid.params.inventoryId !== 'string') { + utils.logError( + BIDDER_CODE, + 'bid.params.inventoryId is missing or has wrong type.' + ); + return false; + } + + if ( + !bid.mediaTypes || + !bid.mediaTypes[BANNER] || + !utils.isArray(bid.mediaTypes[BANNER].sizes) || + bid.mediaTypes[BANNER].sizes.length <= 0 || + !utils.isArrayOfNums(bid.mediaTypes[BANNER].sizes[0]) + ) { + utils.logError(BIDDER_CODE, 'Wrong or missing size parameters.'); + return false; + } + + return true; +} +// #endregion + +// #region Building the bid requests +/** + * + * @param {object} bid + * @returns + */ +function createBidRequest(bid) { + // Developer Params + const { + inventoryId, + adUnitId, + extraData, + yearOfBirth, + gender, + categories, + latitude, + longitude, + sdkVersion, + } = bid.params; + + return { + method: 'GET', + url: ADPLUS_ENDPOINT, + data: utils.cleanObj({ + bidId: bid.bidId, + inventoryId, + adUnitId, + adUnitWidth: bid.mediaTypes[BANNER].sizes[0][0], + adUnitHeight: bid.mediaTypes[BANNER].sizes[0][1], + extraData, + yearOfBirth, + gender, + categories, + latitude, + longitude, + sdkVersion: sdkVersion || '1', + session: getSessionId(), + interstitial: 0, + token: typeof window.top === 'object' && window.top[DGID_CODE] ? window.top[DGID_CODE] : undefined, + secure: window.location.protocol === 'https:' ? 1 : 0, + screenWidth: screen.width, + screenHeight: screen.height, + language: window.navigator.language || 'en-US', + pageUrl: window.location.href, + domain: window.location.hostname, + referrer: window.location.referrer, + }), + }; +} + +function buildRequests(validBidRequests, bidderRequest) { + return validBidRequests.map((req) => createBidRequest(req)); +} +// #endregion + +// #region Interpreting Responses +/** + * + * @param {HeaderBiddingResponse} responseData + * @param { object } bidParams + * @returns + */ +function createAdResponse(responseData, bidParams) { + return { + requestId: responseData.requestID, + cpm: responseData.cpm, + currency: responseData.currency, + width: responseData.width, + height: responseData.height, + creativeId: responseData.creativeID, + dealId: responseData.dealID, + netRevenue: responseData.netRevenue, + ttl: responseData.ttl, + ad: responseData.ad, + mediaType: responseData.mediaType, + meta: { + advertiserDomains: responseData.advertiserDomains, + primaryCatId: utils.isArray(responseData.categoryIDs) && responseData.categoryIDs.length > 0 + ? responseData.categoryIDs[0] : undefined, + secondaryCatIds: responseData.categoryIDs, + }, + }; +} + +function interpretResponse(response, request) { + // In case of empty response + if ( + response.body == null || + !utils.isArray(response.body) || + response.body.length === 0 + ) { + return []; + } + const bids = response.body.map((bid) => createAdResponse(bid)); + return bids; +} +// #endregion + +// #region Bidder +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onTimeout(timeoutData) { + utils.logError('Adplus adapter timed out for the auction.', timeoutData); + }, + onBidWon(bid) { + utils.logInfo( + `Adplus adapter won the auction. Bid id: ${bid.bidId}, Ad Unit Id: ${bid.adUnitId}, Inventory Id: ${bid.inventoryId}` + ); + }, +}; + +registerBidder(spec); +// #endregion diff --git a/modules/adplusBidAdapter.md b/modules/adplusBidAdapter.md new file mode 100644 index 00000000000..dce9e4a312f --- /dev/null +++ b/modules/adplusBidAdapter.md @@ -0,0 +1,39 @@ +# Overview + +Module Name: AdPlus Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: adplus.destek@yaani.com.tr + +# Description + +AdPlus Prebid.js Bidder Adapter. Only banner formats are supported. + +About us : https://ssp.ad-plus.com.tr/ + +# Test Parameters + +```javascript +var adUnits = [ + { + code: "div-adplus", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + ], + }, + }, + bids: [ + { + bidder: "adplus", + params: { + inventoryId: "-1", + adUnitId: "-3", + }, + }, + ], + }, +]; +``` diff --git a/modules/adpod.js b/modules/adpod.js index ddceed1c344..b7c459fd66f 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -13,23 +13,36 @@ */ import { - generateUUID, deepAccess, logWarn, logInfo, isArrayOfNums, isArray, isNumber, logError, groupBy, compareOn, - isPlainObject + compareOn, + deepAccess, + generateUUID, + groupBy, + isArray, + isArrayOfNums, + isNumber, + isPlainObject, + logError, + logInfo, + logWarn } from '../src/utils.js'; -import { addBidToAuction, doCallbacksIfTimedout, AUCTION_IN_PROGRESS, callPrebidCache, getPriceByGranularity, getPriceGranularity } from '../src/auction.js'; -import { checkAdUnitSetup } from '../src/prebid.js'; -import { checkVideoBidSetup } from '../src/video.js'; -import { setupBeforeHookFnOnce, module } from '../src/hook.js'; -import { store } from '../src/videoCache.js'; -import { config } from '../src/config.js'; -import { ADPOD } from '../src/mediaTypes.js'; -import Set from 'core-js-pure/features/set'; -import find from 'core-js-pure/features/array/find.js'; -import { auctionManager } from '../src/auctionManager.js'; +import { + addBidToAuction, + AUCTION_IN_PROGRESS, + callPrebidCache, + doCallbacksIfTimedout, + getPriceByGranularity, + getPriceGranularity +} from '../src/auction.js'; +import {checkAdUnitSetup} from '../src/prebid.js'; +import {checkVideoBidSetup} from '../src/video.js'; +import {module, setupBeforeHookFnOnce} from '../src/hook.js'; +import {store} from '../src/videoCache.js'; +import {config} from '../src/config.js'; +import {ADPOD} from '../src/mediaTypes.js'; +import {find, arrayFrom as from} from '../src/polyfill.js'; +import {auctionManager} from '../src/auctionManager.js'; import CONSTANTS from '../src/constants.json'; -const from = require('core-js-pure/features/array/from.js'); - const TARGETING_KEY_PB_CAT_DUR = 'hb_pb_cat_dur'; const TARGETING_KEY_CACHE_ID = 'hb_cache_id'; @@ -122,7 +135,7 @@ function getPricePartForAdpodKey(bid) { const adpodDealPrefix = config.getConfig(`adpod.dealTier.${bid.bidderCode}.prefix`); pricePart = (adpodDealPrefix) ? adpodDealPrefix + deepAccess(bid, 'video.dealTier') : deepAccess(bid, 'video.dealTier'); } else { - const granularity = getPriceGranularity(bid.mediaType); + const granularity = getPriceGranularity(bid); pricePart = getPriceByGranularity(granularity)(bid); } return pricePart @@ -223,10 +236,9 @@ function firePrebidCacheCall(auctionInstance, bidList, afterBidAdded) { * @param {*} auctionInstance running context of the auction * @param {Object} bidResponse incoming bid; if adpod, will be processed through hook function. If not adpod, returns to original function. * @param {Function} afterBidAdded callback function used when Prebid Cache responds - * @param {Object} bidderRequest copy of bid's associated bidderRequest object + * @param {Object} videoConfig mediaTypes.video from the bid's adUnit */ -export function callPrebidCacheHook(fn, auctionInstance, bidResponse, afterBidAdded, bidderRequest) { - let videoConfig = deepAccess(bidderRequest, 'mediaTypes.video'); +export function callPrebidCacheHook(fn, auctionInstance, bidResponse, afterBidAdded, videoConfig) { if (videoConfig && videoConfig.context === ADPOD) { let brandCategoryExclusion = config.getConfig('adpod.brandCategoryExclusion'); let adServerCatId = deepAccess(bidResponse, 'meta.adServerCatId'); @@ -250,7 +262,7 @@ export function callPrebidCacheHook(fn, auctionInstance, bidResponse, afterBidAd } } } else { - fn.call(this, auctionInstance, bidResponse, afterBidAdded, bidderRequest); + fn.call(this, auctionInstance, bidResponse, afterBidAdded, videoConfig); } } @@ -310,18 +322,17 @@ export function checkAdUnitSetupHook(fn, adUnits) { * (eg if range was [5, 15, 30] -> 2s is rounded to 5s; 17s is rounded back to 15s; 18s is rounded up to 30s) * - if the bid is above the range of the listed durations (and outside the buffer), reject the bid * - set the rounded duration value in the `bid.video.durationBucket` field for accepted bids - * @param {Object} bidderRequest copy of the bidderRequest object associated to bidResponse + * @param {Object} videoMediaType 'mediaTypes.video' associated to bidResponse * @param {Object} bidResponse incoming bidResponse being evaluated by bidderFactory * @returns {boolean} return false if bid duration is deemed invalid as per adUnit configuration; return true if fine */ -function checkBidDuration(bidderRequest, bidResponse) { +function checkBidDuration(videoMediaType, bidResponse) { const buffer = 2; let bidDuration = deepAccess(bidResponse, 'video.durationSeconds'); - let videoConfig = deepAccess(bidderRequest, 'mediaTypes.video'); - let adUnitRanges = videoConfig.durationRangeSec; + let adUnitRanges = videoMediaType.durationRangeSec; adUnitRanges.sort((a, b) => a - b); // ensure the ranges are sorted in numeric order - if (!videoConfig.requireExactDuration) { + if (!videoMediaType.requireExactDuration) { let max = Math.max(...adUnitRanges); if (bidDuration <= (max + buffer)) { let nextHighestRange = find(adUnitRanges, range => (range + buffer) >= bidDuration); @@ -346,12 +357,12 @@ function checkBidDuration(bidderRequest, bidResponse) { * If it's found to not be an adpod bid, it will return to original function via hook logic * @param {Function} fn reference to original function (used by hook logic) * @param {Object} bid incoming bid object - * @param {Object} bidRequest bidRequest object of associated bid + * @param {Object} adUnit adUnit object of associated bid * @param {Object} videoMediaType copy of the `bidRequest.mediaTypes.video` object; used in original function * @param {String} context value of the `bidRequest.mediaTypes.video.context` field; used in original function * @returns {boolean} this return is only used for adpod bids */ -export function checkVideoBidSetupHook(fn, bid, bidRequest, videoMediaType, context) { +export function checkVideoBidSetupHook(fn, bid, adUnit, videoMediaType, context) { if (context === ADPOD) { let result = true; let brandCategoryExclusion = config.getConfig('adpod.brandCategoryExclusion'); @@ -367,7 +378,7 @@ export function checkVideoBidSetupHook(fn, bid, bidRequest, videoMediaType, cont if (!deepAccess(bid, 'video.durationSeconds') || bid.video.durationSeconds <= 0) { result = false; } else { - let isBidGood = checkBidDuration(bidRequest, bid); + let isBidGood = checkBidDuration(videoMediaType, bid); if (!isBidGood) result = false; } } @@ -382,7 +393,7 @@ export function checkVideoBidSetupHook(fn, bid, bidRequest, videoMediaType, cont fn.bail(result); } else { - fn.call(this, bid, bidRequest, videoMediaType, context); + fn.call(this, bid, adUnit, videoMediaType, context); } } diff --git a/modules/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js index 2b5a7e15af2..d64874c393e 100644 --- a/modules/adprimeBidAdapter.js +++ b/modules/adprimeBidAdapter.js @@ -1,10 +1,11 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'adprime'; const AD_URL = 'https://delta.adprime.com/pbjs'; -const SYNC_URL = 'https://delta.adprime.com'; +const SYNC_URL = 'https://sync.adprime.com'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -150,7 +151,8 @@ export const spec = { }, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncUrl = SYNC_URL + 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}`; @@ -161,12 +163,15 @@ export const spec = { if (uspConsent && uspConsent.consentString) { syncUrl += `&ccpa_consent=${uspConsent.consentString}`; } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + return [{ - type: 'image', + type: syncType, url: syncUrl }]; } - }; registerBidder(spec); diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index ce31f64d705..348bdc90808 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -11,7 +11,7 @@ const ADQUERY_USER_SYNC_DOMAIN = ADQUERY_BIDDER_DOMAIN_PROTOCOL + '://' + ADQUER const ADQUERY_DEFAULT_CURRENCY = 'PLN'; const ADQUERY_NET_REVENUE = true; const ADQUERY_TTL = 360; -const storage = getStorageManager(ADQUERY_GVLID); +const storage = getStorageManager({gvlid: ADQUERY_GVLID, bidderCode: ADQUERY_BIDDER_CODE}); /** @type {BidderSpec} */ export const spec = { diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js new file mode 100644 index 00000000000..85421bf588d --- /dev/null +++ b/modules/adqueryIdSystem.js @@ -0,0 +1,103 @@ +/** + * This module adds Adquery QID to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/adqueryIdSystem + * @requires module:modules/userId + */ + +import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {submodule} from '../src/hook.js'; +import * as utils from '../src/utils.js'; + +const MODULE_NAME = 'qid'; +const AU_GVLID = 902; + +export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'qid'}); + +/** + * Param or default. + * @param {String} param + * @param {String} defaultVal + */ +function paramOrDefault(param, defaultVal, arg) { + if (utils.isFn(param)) { + return param(arg); + } else if (utils.isStr(param)) { + return param; + } + return defaultVal; +} + +/** @type {Submodule} */ +export const adqueryIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * IAB TCF Vendor ID + * @type {string} + */ + gvlid: AU_GVLID, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{value:string}} value + * @returns {{qid:Object}} + */ + decode(value) { + let qid = storage.getDataFromLocalStorage('qid'); + if (utils.isStr(qid)) { + return {qid: qid}; + } + return (value && typeof value['qid'] === 'string') ? { 'qid': value['qid'] } : undefined; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @returns {IdResponse|undefined} + */ + getId(config) { + if (!utils.isPlainObject(config.params)) { + config.params = {}; + } + const url = paramOrDefault(config.params.url, + `https://bidder.adquery.io/prebid/qid`, + config.params.urlArg); + + const resp = function (callback) { + let qid = storage.getDataFromLocalStorage('qid'); + if (utils.isStr(qid)) { + const responseObj = {qid: qid}; + callback(responseObj); + } else { + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + } + } + callback(responseObj); + }, + error: error => { + utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, undefined, {method: 'GET'}); + } + }; + return {callback: resp}; + } +}; + +submodule('userId', adqueryIdSubmodule); diff --git a/modules/adqueryIdSystem.md b/modules/adqueryIdSystem.md new file mode 100644 index 00000000000..3a49ffbe4da --- /dev/null +++ b/modules/adqueryIdSystem.md @@ -0,0 +1,35 @@ +# Adquery QID + +Adquery QID Module. For assistance setting up your module please contact us at [prebid@adquery.io](prebid@adquery.io). + +### Prebid Params + +Individual params may be set for the Adquery ID Submodule. At least one identifier must be set in the params. + +``` +pbjs.setConfig({ + usersync: { + userIds: [{ + name: 'qid', + storage: { + name: 'qid', + type: 'html5' + } + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the Adquery User ID Module integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Adquery ID module - `"qid"` | `"qid"` | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. | | +| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | +| storage.name | Required | String | The name of the html5 local storage where the user ID will be stored. | `"qid"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the Adquery 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 | `{"qid": "2abf9f001fcd81241b67"}` | +| params | Optional | Object | Used to store params for the id system | +| params.url | Optional | String | Set an alternate GET url for qid with this parameter | +| params.urlArg | Optional | Object | Optional url parameter for params.url | diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 649031d1e3b..3d4de7c7b9d 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -1,14 +1,26 @@ -import { Renderer } from '../src/Renderer.js'; +import {Renderer} from '../src/Renderer.js'; import { - logError, convertTypes, convertCamelToUnderscore, isArray, deepClone, logWarn, logMessage, getBidRequest, deepAccess, - isStr, createTrackPixelHtml, isEmpty, transformBidderParamKeywords, chunk, isArrayOfNums + chunk, + convertCamelToUnderscore, + convertTypes, + createTrackPixelHtml, + deepAccess, + deepClone, + getBidRequest, + isArray, + isArrayOfNums, + isEmpty, + isStr, + logError, + logMessage, + logWarn, + transformBidderParamKeywords } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import { OUTSTREAM, INSTREAM } from '../src/video.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {find, includes} from '../src/polyfill.js'; +import {INSTREAM, OUTSTREAM} from '../src/video.js'; const BIDDER_CODE = 'adrelevantis'; const URL = 'https://ssp.adrelevantis.com/prebid'; @@ -127,7 +139,7 @@ export const spec = { if (fpdcfg && fpdcfg.context) { let fdata = { keywords: fpdcfg.context.keywords || '', - category: fpdcfg.context.category || '' + category: fpdcfg.context.data.category || '' } payload.fpd = fdata; } diff --git a/modules/adrinoBidAdapter.js b/modules/adrinoBidAdapter.js new file mode 100644 index 00000000000..4520066c3e7 --- /dev/null +++ b/modules/adrinoBidAdapter.js @@ -0,0 +1,74 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {triggerPixel} from '../src/utils.js'; +import {NATIVE} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'adrino'; +const REQUEST_METHOD = 'POST'; +const BIDDER_HOST = 'https://prd-prebid-bidder.adrino.io'; +const GVLID = 1072; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [NATIVE], + + isBidRequestValid: function (bid) { + return !!(bid.bidId) && + !!(bid.params) && + !!(bid.params.hash) && + (typeof bid.params.hash === 'string') && + !!(bid.mediaTypes) && + Object.keys(bid.mediaTypes).includes(NATIVE) && + (bid.bidder === BIDDER_CODE); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const bidRequests = []; + + for (let i = 0; i < validBidRequests.length; i++) { + let requestData = { + bidId: validBidRequests[i].bidId, + nativeParams: validBidRequests[i].nativeParams, + placementHash: validBidRequests[i].params.hash, + referer: bidderRequest.refererInfo.referer, + userAgent: navigator.userAgent, + } + + if (bidderRequest && bidderRequest.gdprConsent) { + requestData.gdprConsent = { + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: bidderRequest.gdprConsent.gdprApplies + } + } + + bidRequests.push({ + method: REQUEST_METHOD, + url: BIDDER_HOST + '/bidder/bid/', + data: requestData, + options: { + contentType: 'application/json', + withCredentials: false, + } + }); + } + + return bidRequests; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const response = serverResponse.body; + const bidResponses = []; + if (!response.noAd) { + bidResponses.push(response); + } + return bidResponses; + }, + + onBidWon: function (bid) { + if (bid['requestId']) { + triggerPixel(BIDDER_HOST + '/bidder/won/' + bid['requestId']); + } + } +}; + +registerBidder(spec); diff --git a/modules/adrinoBidAdapter.md b/modules/adrinoBidAdapter.md new file mode 100644 index 00000000000..5ec63a72736 --- /dev/null +++ b/modules/adrinoBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Adrino Bidder Adapter +Module Type: Bidder Adapter +Maintainer: dev@adrino.pl +``` + +# Description + +Module connects to Adrino bidder to fetch bids. Only native format is supported. + +# Test Parameters + +``` +var adUnits = [ + code: '/12345678/prebid_native_example_1', + mediaTypes: { + native: { + image: { + required: true, + sizes: [[300, 210],[300,150],[140,100]] + }, + title: { + required: true + }, + sponsoredBy: { + required: false + }, + body: { + required: false + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'adrino', + params: { + hash: 'abcdef123456' + } + }] +]; +``` diff --git a/modules/adriverBidAdapter.js b/modules/adriverBidAdapter.js index 67e039e4692..5ab417520e9 100644 --- a/modules/adriverBidAdapter.js +++ b/modules/adriverBidAdapter.js @@ -1,13 +1,14 @@ // ADRIVER BID ADAPTER for Prebid 1.13 import { logInfo, getWindowLocation, getBidIdParameter, _each } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'adriver'; const ADRIVER_BID_URL = 'https://pb.adriver.ru/cgi-bin/bid.cgi'; const TIME_TO_LIVE = 3000; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { - code: BIDDER_CODE, /** @@ -98,6 +99,15 @@ export const spec = { }); }); + let userid = validBidRequests[0].userId; + let adrcidCookie = storage.getDataFromLocalStorage('adrcid') || validBidRequests[0].userId.adrcid; + + if (adrcidCookie) { + payload.adrcid = adrcidCookie; + payload.id5 = userid.id5id; + payload.sharedid = userid.pubcid; + payload.unifiedid = userid.tdid; + } const payloadString = JSON.stringify(payload); return { diff --git a/modules/adriverIdSystem.js b/modules/adriverIdSystem.js new file mode 100644 index 00000000000..6a492fac508 --- /dev/null +++ b/modules/adriverIdSystem.js @@ -0,0 +1,83 @@ +/** + * This module adds AdriverId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/adriverIdSubmodule + * @requires module:modules/userId + */ + +import { logError, isPlainObject } from '../src/utils.js' +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'adriverId'; + +export const storage = getStorageManager(); + +/** @type {Submodule} */ +export const adriverIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{adriverId:string}} + */ + decode(value) { + return { adrcid: value } + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @returns {IdResponse|undefined} + */ + getId(config) { + if (!isPlainObject(config.params)) { + config.params = {}; + } + const url = 'https://ad.adriver.ru/cgi-bin/json.cgi?sid=1&ad=719473&bt=55&pid=3198680&bid=7189165&bn=7189165&tuid=1'; + const resp = function (callback) { + let creationDate = storage.getDataFromLocalStorage('adrcid_cd') || storage.getCookie('adrcid_cd'); + let cookie = storage.getDataFromLocalStorage('adrcid') || storage.getCookie('adrcid'); + + if (cookie && creationDate && ((new Date().getTime() - creationDate) < 86400000)) { + const responseObj = cookie; + callback(responseObj); + } else { + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response).adrcid; + } catch (error) { + logError(error); + } + let now = new Date(); + now.setTime(now.getTime() + 86400 * 1825 * 1000); + storage.setCookie('adrcid', responseObj, now.toUTCString(), 'Lax'); + storage.setDataInLocalStorage('adrcid', responseObj); + storage.setCookie('adrcid_cd', new Date().getTime(), now.toUTCString(), 'Lax'); + storage.setDataInLocalStorage('adrcid_cd', new Date().getTime()); + } + callback(responseObj); + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, undefined, {method: 'GET'}); + } + }; + return {callback: resp}; + } +}; + +submodule('userId', adriverIdSubmodule); diff --git a/modules/adriverIdSystem.md b/modules/adriverIdSystem.md new file mode 100644 index 00000000000..797318ba977 --- /dev/null +++ b/modules/adriverIdSystem.md @@ -0,0 +1,19 @@ +# Overview + +Module Name: AdRiver Id System +Module Type: User Id System +Maintainer: support@adriver.ru + +# Description + +Adriver user identification system + +## Example configuration for publishers: + +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'adriverId' + }] + } +}); \ No newline at end of file diff --git a/modules/adtargetBidAdapter.js b/modules/adtargetBidAdapter.js index 0ad0177815a..a07b0de0f67 100644 --- a/modules/adtargetBidAdapter.js +++ b/modules/adtargetBidAdapter.js @@ -1,8 +1,8 @@ -import { deepAccess, isArray, chunk, _map, flatten, logError, parseSizesInput } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import find from 'core-js-pure/features/array/find.js'; +import {_map, chunk, deepAccess, flatten, isArray, logError, parseSizesInput} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {find} from '../src/polyfill.js'; const ENDPOINT = 'https://ghb.console.adtarget.com.tr/v2/auction/'; const BIDDER_CODE = 'adtarget'; diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 44a9c90d438..d8638c4da47 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -1,9 +1,9 @@ -import { deepAccess, isArray, chunk, _map, flatten, convertTypes, parseSizesInput } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { ADPOD, BANNER, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { Renderer } from '../src/Renderer.js'; -import find from 'core-js-pure/features/array/find.js'; +import {_map, chunk, convertTypes, deepAccess, flatten, isArray, parseSizesInput} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {Renderer} from '../src/Renderer.js'; +import {find} from '../src/polyfill.js'; const subdomainSuffixes = ['', 1, 2]; const AUCTION_PATH = '/v2/auction/'; @@ -18,9 +18,9 @@ const HOST_GETTERS = { navelix: () => 'ghb.hb.navelix.com', appaloosa: () => 'ghb.hb.appaloosa.media', onefiftytwomedia: () => 'ghb.ads.152media.com', - mediafuse: () => 'ghb.hbmp.mediafuse.com', bidsxchange: () => 'ghb.hbd.bidsxchange.com', streamkey: () => 'ghb.hb.streamkey.net', + janet: () => 'ghb.bidder.jmgads.com', } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -36,12 +36,8 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', 'streamkey', - { code: 'navelix', gvlid: 380 }, - { - code: 'mediafuse', - skipPbsAliasing: true - } + aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', 'streamkey', 'janet', + { code: 'navelix', gvlid: 380 } ], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index df848fba823..283e1273150 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -4,8 +4,8 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; -const storage = getStorageManager(); const BIDDER_CODE = 'adtrue'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); const ADTRUE_CURRENCY = 'USD'; const ENDPOINT_URL = 'https://hb.adtrue.com/prebid/auction'; const LOG_WARN_PREFIX = 'AdTrue: '; diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js index 854c65b1f22..605e19cfc66 100755 --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -1,9 +1,8 @@ -import { isEmpty, deepAccess, isFn, parseSizesInput, generateUUID, parseUrl } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {find, includes} from '../src/polyfill.js'; const ADAPTER_VERSION = '1.0'; const BIDDER_CODE = 'advangelists'; diff --git a/modules/adxcgBidAdapter.md b/modules/adxcgBidAdapter.md index 8eccdb11dee..1e4ef9cd6f9 100644 --- a/modules/adxcgBidAdapter.md +++ b/modules/adxcgBidAdapter.md @@ -34,31 +34,34 @@ Module that connects to an Adxcg.com zone to fetch bids. code: 'native-ad-div', mediaTypes: { native: { - image: { + image: { sendId: false, - required: true, - sizes: [80, 80] + required: false, + sizes: [127, 83] }, icon: { - sendId: true, - }, - brand: { + sizes: [80, 80], + required: false, sendId: true, }, title: { sendId: false, - required: true, + required: false, len: 75 }, body: { sendId: false, - required: true, + required: false, len: 200 }, - sponsoredBy: { + cta: { sendId: false, required: false, - len: 20 + len: 75 + }, + sponsoredBy: { + sendId: false, + required: false } } }, @@ -73,21 +76,19 @@ Module that connects to an Adxcg.com zone to fetch bids. code: 'video-div', mediaTypes: { video: { - playerSize: [640, 480], - context: 'instream', - mimes: ['video/mp4'], - protocols: [5, 6, 8], - playback_method: ['auto_play_sound_off'] - } + playerSize: [640, 480], + context: 'instream', + mimes: ['video/mp4'], + protocols: [2, 3, 5, 6, 8], + playback_method: ['auto_play_sound_off'], + maxduration: 100, + skip: 1 + } }, bids: [{ bidder: 'adxcg', params: { - adzoneid: '20', - video: { - maxduration: 100, - skippable: true - } + adzoneid: '20' } }] } diff --git a/modules/adxpremiumAnalyticsAdapter.js b/modules/adxpremiumAnalyticsAdapter.js index 3e30de14052..9066c26fb00 100644 --- a/modules/adxpremiumAnalyticsAdapter.js +++ b/modules/adxpremiumAnalyticsAdapter.js @@ -1,9 +1,9 @@ -import { logError, logInfo, deepClone } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; +import {deepClone, logError, logInfo} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import includes from 'core-js-pure/features/array/includes.js'; +import {includes} from '../src/polyfill.js'; const analyticsType = 'endpoint'; const defaultUrl = 'https://adxpremium.services/graphql'; diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 334309aec5c..1a0074e668b 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,13 +1,15 @@ -import { deepAccess, buildUrl, parseSizesInput } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import find from 'core-js-pure/features/array/find.js'; +import {buildUrl, deepAccess, parseSizesInput} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {createEidsArray} from './userId/eids.js'; +import {find} from '../src/polyfill.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; const VERSION = '1.0'; const BIDDER_CODE = 'adyoulike'; const DEFAULT_DC = 'hb-api'; const CURRENCY = 'USD'; +const GVLID = 259; const NATIVE_IMAGE = { image: { @@ -35,6 +37,7 @@ const NATIVE_IMAGE = { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, NATIVE, VIDEO], aliases: ['ayl'], // short code /** @@ -58,6 +61,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { + let hasVideo = false; const payload = { Version: VERSION, Bids: bidRequests.reduce((accumulator, bidReq) => { @@ -85,6 +89,7 @@ export const spec = { accumulator[bidReq.bidId].Native = nativeReq; } if (mediatype === VIDEO) { + hasVideo = true; accumulator[bidReq.bidId].Video = bidReq.mediaTypes.video; const size = bidReq.mediaTypes.video.playerSize; @@ -97,17 +102,21 @@ export const spec = { PageRefreshed: getPageRefreshed() }; - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent) { payload.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : null }; } - if (bidderRequest && bidderRequest.uspConsent) { + if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + if (deepAccess(bidderRequest, 'userId')) { + payload.userId = createEidsArray(bidderRequest.userId); + } + const data = JSON.stringify(payload); const options = { withCredentials: true @@ -115,7 +124,7 @@ export const spec = { return { method: 'POST', - url: createEndpoint(bidRequests, bidderRequest), + url: createEndpoint(bidRequests, bidderRequest, hasVideo), data, options }; @@ -175,11 +184,13 @@ function getCanonicalUrl() { /* Get mediatype from bidRequest */ function getMediatype(bidRequest) { + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + return BANNER; + } if (deepAccess(bidRequest, 'mediaTypes.video')) { return VIDEO; - } else if (deepAccess(bidRequest, 'mediaTypes.banner')) { - return BANNER; - } else if (deepAccess(bidRequest, 'mediaTypes.native')) { + } + if (deepAccess(bidRequest, 'mediaTypes.native')) { return NATIVE; } } @@ -208,12 +219,13 @@ function getPageRefreshed() { } /* Create endpoint url */ -function createEndpoint(bidRequests, bidderRequest) { +function createEndpoint(bidRequests, bidderRequest, hasVideo) { let host = getHostname(bidRequests); + const endpoint = hasVideo ? '/hb-api/prebid-video/v1' : '/hb-api/prebid/v1'; return buildUrl({ protocol: 'https', host: `${DEFAULT_DC}${host}.omnitagjs.com`, - pathname: '/hb-api/prebid/v1', + pathname: endpoint, search: createEndpointQS(bidderRequest) }); } @@ -340,7 +352,7 @@ function getTrackers(eventsArray, jsTrackers) { function getVideoAd(response) { var adJson = {}; - if (typeof response.Ad === 'string') { + if (typeof response.Ad === 'string' && response.Ad.indexOf('\/\*PREBID\*\/') > 0) { adJson = JSON.parse(response.Ad.match(/\/\*PREBID\*\/(.*)\/\*PREBID\*\//)[1]); return deepAccess(adJson, 'Content.MainVideo.Vast'); } @@ -424,7 +436,7 @@ function getNativeAssets(response, nativeConfig) { const icurl = getImageUrl(adJson, deepAccess(adJson, 'Content.Preview.Sponsor.Logo.Resource'), iconSize[0], iconSize[1]); - if (url) { + if (icurl) { native[key] = { url: icurl, width: iconSize[0], @@ -473,13 +485,15 @@ function createBid(response, bidRequests) { meta: response.Meta || { advertiserDomains: [] } }; - if (request && request.Native) { + // retreive video response if present + const vast64 = response.Vast || getVideoAd(response); + if (vast64) { + bid.vastXml = window.atob(vast64); + bid.mediaType = 'video'; + } else if (request.Native) { + // format Native response if Native was requested bid.native = getNativeAssets(response, request.Native); bid.mediaType = 'native'; - } else if (request && request.Video) { - const vast64 = response.Vast || getVideoAd(response); - bid.vastXml = vast64 ? window.atob(vast64) : ''; - bid.mediaType = 'video'; } else { bid.width = response.Width; bid.height = response.Height; diff --git a/modules/afpBidAdapter.js b/modules/afpBidAdapter.js index 68941ff17c9..6565942bcc8 100644 --- a/modules/afpBidAdapter.js +++ b/modules/afpBidAdapter.js @@ -1,7 +1,7 @@ -import includes from 'core-js-pure/features/array/includes.js' -import { registerBidder } from '../src/adapters/bidderFactory.js' -import { Renderer } from '../src/Renderer.js' -import { BANNER, VIDEO } from '../src/mediaTypes.js' +import {includes} from '../src/polyfill.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {Renderer} from '../src/Renderer.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; export const IS_DEV = location.hostname === 'localhost' export const BIDDER_CODE = 'afp' diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index f5403cca3eb..b2e78a7df78 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -16,7 +16,7 @@ const SUBMODULE_NAME = 'airgrid'; const AG_TCF_ID = 782; export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids' -export const storage = getStorageManager(AG_TCF_ID, SUBMODULE_NAME); +export const storage = getStorageManager({gvlid: AG_TCF_ID, moduleName: SUBMODULE_NAME}); /** * Attach script tag to DOM diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index d143a53fbf4..aca984d39c8 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -15,7 +15,7 @@ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments'; -export const storage = getStorageManager(null, SUBMODULE_NAME); +export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); /** * Lazy merge objects. diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index d48245e9604..d1754936d7f 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -5,7 +5,7 @@ import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'amx'; -const storage = getStorageManager(737, BIDDER_CODE); +const storage = getStorageManager({gvlid: 737, bidderCode: BIDDER_CODE}); const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; const VERSION = 'pba1.3.1'; diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index 96bdf153e3f..7760aa2b47b 100644 --- a/modules/aniviewBidAdapter.js +++ b/modules/aniviewBidAdapter.js @@ -309,7 +309,7 @@ function getUserSyncs(syncOptions, serverResponses) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo'], + aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo', 'didnavideo', 'ottadvisors'], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid, buildRequests, diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index c02eeccaea6..2758fc2d03a 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -1,13 +1,37 @@ -import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, logError, logInfo, deepAccess, logMessage, convertTypes, isStr, getParameterByName, deepClone, chunk, logWarn, getBidRequest, createTrackPixelHtml, isEmpty, transformBidderParamKeywords, getMaxValueFromArray, fill, getMinValueFromArray, isArrayOfNums, isFn } from '../src/utils.js'; -import { Renderer } from '../src/Renderer.js'; -import { config } from '../src/config.js'; -import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO, ADPOD } from '../src/mediaTypes.js'; -import { auctionManager } from '../src/auctionManager.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import { OUTSTREAM, INSTREAM } from '../src/video.js'; -import { getStorageManager } from '../src/storageManager.js'; +import { + chunk, + convertCamelToUnderscore, + convertTypes, + createTrackPixelHtml, + deepAccess, + deepClone, + fill, + getBidRequest, + getMaxValueFromArray, + getMinValueFromArray, + getParameterByName, + isArray, + isArrayOfNums, + isEmpty, + isFn, + isNumber, + isPlainObject, + isStr, + logError, + logInfo, + logMessage, + logWarn, + transformBidderParamKeywords +} from '../src/utils.js'; +import {Renderer} from '../src/Renderer.js'; +import {config} from '../src/config.js'; +import {getIabSubCategory, registerBidder} from '../src/adapters/bidderFactory.js'; +import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {find, includes} from '../src/polyfill.js'; +import {INSTREAM, OUTSTREAM} from '../src/video.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {bidderSettings} from '../src/bidderSettings.js'; const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -60,7 +84,7 @@ const SCRIPT_TAG_START = ' 0) { + aucKeywords.forEach(deleteValues); + } + + payload.keywords = aucKeywords; + } + if (config.getConfig('adpod.brandCategoryExclusion')) { payload.brand_category_uniqueness = true; } @@ -259,6 +293,13 @@ export const spec = { 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 }); + }); + }); + } if (eids.length) { payload.eids = eids; @@ -293,7 +334,8 @@ export const spec = { serverResponse.tags.forEach(serverBid => { const rtbBid = getRtbBid(serverBid); if (rtbBid) { - if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const cpmCheck = (bidderSettings.get(bidderRequest.bidderCode, 'allowZeroCpmBids') === true) ? rtbBid.cpm >= 0 : rtbBid.cpm > 0; + if (cpmCheck && includes(this.supportedMediaTypes, rtbBid.ad_type)) { const bid = newBid(serverBid, rtbBid, bidderRequest); bid.mediaType = parseMediaType(rtbBid); bids.push(bid); @@ -348,11 +390,20 @@ export const spec = { }, transformBidParams: function (params, isOpenRtb) { + let conversionFn = transformBidderParamKeywords; + if (isOpenRtb === true) { + let s2sConfig = config.getConfig('s2sConfig'); + let s2sEndpointUrl = deepAccess(s2sConfig, 'endpoint.p1Consent'); + if (s2sEndpointUrl && s2sEndpointUrl.match('/openrtb2/prebid')) { + conversionFn = convertKeywordsToString; + } + } + params = convertTypes({ 'member': 'string', 'invCode': 'string', 'placementId': 'number', - 'keywords': transformBidderParamKeywords, + 'keywords': conversionFn, 'publisherId': 'number' }, params); @@ -598,6 +649,22 @@ function newBid(serverBid, rtbBid, bidderRequest) { bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); } + // temporary function; may remove at later date if/when adserver fully supports dchain + function setupDChain(rtbBid) { + let dchain = { + ver: '1.0', + complete: 0, + nodes: [{ + bsid: rtbBid.buyer_member_id.toString() + }], + }; + + return dchain; + } + if (rtbBid.buyer_member_id) { + bid.meta = Object.assign({}, bid.meta, {dchain: setupDChain(rtbBid)}); + } + if (rtbBid.brand_id) { bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); } @@ -1129,4 +1196,31 @@ function getBidFloor(bid) { return null; } +// keywords: { 'genre': ['rock', 'pop'], 'pets': ['dog'] } goes to 'genre=rock,genre=pop,pets=dog' +function convertKeywordsToString(keywords) { + let result = ''; + Object.keys(keywords).forEach(key => { + // if 'text' or '' + if (isStr(keywords[key])) { + if (keywords[key] !== '') { + result += `${key}=${keywords[key]},` + } else { + result += `${key},`; + } + } else if (isArray(keywords[key])) { + if (keywords[key][0] === '') { + result += `${key},` + } else { + keywords[key].forEach(val => { + result += `${key}=${val},` + }); + } + } + }); + + // remove last trailing comma + result = result.substring(0, result.length - 1); + return result; +} + registerBidder(spec); diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index f2d4189f237..b69fffb8b6b 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -8,7 +8,7 @@ const CONSTANTS = { BIDDER_CODE: 'apstream', GVLID: 394 }; -const storage = getStorageManager(CONSTANTS.GVLID, CONSTANTS.BIDDER_CODE); +const storage = getStorageManager({gvlid: CONSTANTS.GVLID, bidderCode: CONSTANTS.BIDDER_CODE}); var dsuModule = (function() { 'use strict'; diff --git a/modules/asealBidAdapter.js b/modules/asealBidAdapter.js new file mode 100644 index 00000000000..559afefa94b --- /dev/null +++ b/modules/asealBidAdapter.js @@ -0,0 +1,58 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +export const BIDDER_CODE = 'aseal'; +const SUPPORTED_AD_TYPES = [BANNER]; +export const API_ENDPOINT = 'https://tkprebid.aotter.net/prebid/adapter'; +export const HEADER_AOTTER_VERSION = 'prebid_0.0.1'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['aotter', 'trek'], + supportedMediaTypes: SUPPORTED_AD_TYPES, + + isBidRequestValid: (bid) => !!bid.params.placeUid && typeof bid.params.placeUid === 'string', + + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests.length === 0) { + return []; + } + + const clientId = + config.getConfig('aseal.clientId') || ''; + + const data = { + bids: validBidRequests, + refererInfo: bidderRequest.refererInfo, + }; + + const options = { + contentType: 'application/json', + withCredentials: true, + customHeaders: { + 'x-aotter-clientid': clientId, + 'x-aotter-version': HEADER_AOTTER_VERSION, + }, + }; + + return [{ + method: 'POST', + url: API_ENDPOINT, + data, + options, + }]; + }, + + interpretResponse: (serverResponse, bidRequest) => { + if (!Array.isArray(serverResponse.body)) { + return []; + } + + const bidResponses = serverResponse.body; + + return bidResponses; + }, +}; + +registerBidder(spec); diff --git a/modules/asealBidAdapter.md b/modules/asealBidAdapter.md new file mode 100644 index 00000000000..d13b802f736 --- /dev/null +++ b/modules/asealBidAdapter.md @@ -0,0 +1,52 @@ +# Overview + +``` +Module Name: Aseal Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech-service@aotter.net +``` + +# Description + +Module that connects to Aseal server for bids. +Supported Ad Formats: + +- Banner + +# Configuration + +Following configuration is required: + +```js +pbjs.setConfig({ + aseal: { + clientId: "YOUR_CLIENT_ID" + } +}); +``` + +# Ad Unit Example + +```js +var adUnits = [ + { + code: "banner-div", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }, + bids: [ + { + bidder: "aseal", + params: { + placeUid: "f4a74f73-9a74-4a87-91c9-545c6316c23d" + } + } + ] + } +]; +``` diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index 2cfcfbe98b4..726bbef9bd6 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -5,7 +5,7 @@ import {ajax} from '../src/ajax.js' const BIDDER = 'automatad' -const ENDPOINT_URL = 'https://rtb2.automatad.com/ortb2' +const ENDPOINT_URL = 'https://bid.atmtd.com' const DEFAULT_BID_TTL = 30 const DEFAULT_CURRENCY = 'USD' @@ -57,7 +57,7 @@ export const spec = { const payloadString = JSON.stringify(openrtbRequest) return { method: 'POST', - url: ENDPOINT_URL + '/resp', + url: ENDPOINT_URL + '/request', data: payloadString, options: { contentType: 'application/json', @@ -72,6 +72,7 @@ export const spec = { const response = (serverResponse || {}).body if (response && response.seatbid && response.seatbid[0].bid && response.seatbid[0].bid.length) { + var bidid = response.bidid response.seatbid.forEach(bidObj => { bidObj.bid.forEach(bid => { bidResponses.push({ @@ -88,6 +89,7 @@ export const spec = { height: bid.h, netRevenue: DEFAULT_NET_REVENUE, nurl: bid.nurl, + bidId: bidid }) }) }) @@ -97,11 +99,9 @@ export const spec = { return bidResponses }, - getUserSyncs: function(syncOptions, serverResponse) { - return [{ - type: 'iframe', - url: 'https://rtb2.automatad.com/ortb2/async_usersync' - }] + onTimeout: function(timeoutData) { + const timeoutUrl = ENDPOINT_URL + '/timeout' + ajax(timeoutUrl, null, JSON.stringify(timeoutData)) }, onBidWon: function(bid) { if (!bid.nurl) { return } @@ -116,6 +116,9 @@ export const spec = { ).replace( /\$\{AUCTION_CURRENCY\}/, winCurr + ).replace( + /\$\{AUCTON_BID_ID\}/, + bid.bidId ).replace( /\$\{AUCTION_ID\}/, bid.auctionId diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index 7cd8f63bd2a..a790a89a0c1 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -177,7 +177,7 @@ export const spec = { const { nurl } = bid || {}; if (bid.nurl) { - triggerPixel(replaceAuctionPrice(nurl, bid.cpm)); + triggerPixel(replaceAuctionPrice(nurl, bid.originalCpm || bid.cpm)); }; } } diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index a882a796851..1c341e4dc51 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -1,12 +1,21 @@ -import { logWarn, deepAccess, isArray, parseSizesInput, isFn, parseUrl, getUniqueIdentifierStr } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; - -const ADAPTER_VERSION = '1.18'; +import { + deepAccess, + deepClone, + deepSetValue, + getUniqueIdentifierStr, + isArray, + isFn, + logWarn, + parseSizesInput, + parseUrl +} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {Renderer} from '../src/Renderer.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {find, includes} from '../src/polyfill.js'; + +const ADAPTER_VERSION = '1.19'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; const CURRENCY = 'USD'; @@ -360,6 +369,7 @@ function createVideoRequestData(bid, bidderRequest) { let tagid = getVideoBidParam(bid, 'tagid'); let topLocation = getTopWindowLocation(bidderRequest); let eids = getEids(bid); + let ortb2 = deepClone(config.getConfig('ortb2')); let payload = { isPrebid: true, appId: appId, @@ -378,6 +388,7 @@ function createVideoRequestData(bid, bidderRequest) { displaymanagerver: ADAPTER_VERSION }], site: { + ...deepAccess(ortb2, 'site', {}), page: topLocation.href, domain: topLocation.hostname }, @@ -389,39 +400,32 @@ function createVideoRequestData(bid, bidderRequest) { js: 1, geo: {} }, - regs: { - ext: {} - }, - source: { - ext: {} - }, - user: { - ext: {} - }, + app: deepAccess(ortb2, 'app'), + user: deepAccess(ortb2, 'user'), cur: [CURRENCY] }; if (bidderRequest && bidderRequest.uspConsent) { - payload.regs.ext.us_privacy = bidderRequest.uspConsent; + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } if (bidderRequest && bidderRequest.gdprConsent) { let { gdprApplies, consentString } = bidderRequest.gdprConsent; - payload.regs.ext.gdpr = gdprApplies ? 1 : 0; - payload.user.ext.consent = consentString; + deepSetValue(payload, 'regs.ext.gdpr', gdprApplies ? 1 : 0); + deepSetValue(payload, 'user.ext.consent', consentString); } if (bid.schain) { - payload.source.ext.schain = bid.schain; + deepSetValue(payload, 'source.ext.schain', bid.schain); } if (eids.length > 0) { - payload.user.ext.eids = eids; + deepSetValue(payload, 'user.ext.eids', eids); } let connection = navigator.connection || navigator.webkitConnection; if (connection && connection.effectiveType) { - payload.device.connectiontype = connection.effectiveType; + deepSetValue(payload, 'device.connectiontype', connection.effectiveType); } return payload; @@ -439,8 +443,10 @@ function createBannerRequestData(bids, bidderRequest) { sizes: getBannerSizes(bid) }; }); + let ortb2 = deepClone(config.getConfig('ortb2')); let payload = { slots: slots, + ortb2: ortb2, page: topLocation.href, domain: topLocation.hostname, search: topLocation.search, diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index a6bc8a5687d..2e74170fcaf 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -36,7 +36,7 @@ export const spec = { */ buildRequests: function(validBidRequests, bidderRequest) { const slots = validBidRequests.map(beOpRequestSlotsMaker); - let pageUrl = deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + let pageUrl = deepAccess(window, 'location.href') || deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl'); let fpd = config.getLegacyFpd(config.getConfig('ortb2')); let gdpr = bidderRequest.gdprConsent; let firstSlot = slots[0]; @@ -99,16 +99,18 @@ export const spec = { } function buildTrackingParams(data, info, value) { + const accountId = data.params.accountId; return { - pid: data.params.accountId, + pid: accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : accountId, nid: data.params.networkId, nptnid: data.params.networkPartnerId, - bid: data.bidId, + bid: data.bidId || data.requestId, sl_n: data.adUnitCode, aid: data.auctionId, se_ca: 'bid', se_ac: info, - se_va: value + se_va: value, + url: window.location.href }; } diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index b2f63488e12..07d2e15df2b 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -1,6 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getAdUnitSizes, parseSizesInput } from '../src/utils.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import {includes} from '../src/polyfill.js' const BIDDER_CODE = 'between'; let ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid'; @@ -118,7 +119,7 @@ export const spec = { mediaType: serverResponse.body[i].mediaType, ttl: serverResponse.body[i].ttl, creativeId: serverResponse.body[i].creativeid, - currency: serverResponse.body[i].currency || 'RUB', + currency: serverResponse.body[i].currency || 'USD', netRevenue: serverResponse.body[i].netRevenue || true, ad: serverResponse.body[i].ad, meta: { @@ -158,10 +159,16 @@ export const spec = { // type: 'iframe', // url: 'https://acdn.adnxs.com/dmp/async_usersync.html' // }); - syncs.push({ - type: 'iframe', - url: 'https://ads.betweendigital.com/sspmatch-iframe' - }); + syncs.push( + { + type: 'iframe', + url: 'https://ads.betweendigital.com/sspmatch-iframe' + }, + { + type: 'image', + url: 'https://ads.betweendigital.com/sspmatch' + } + ); return syncs; } } diff --git a/modules/bidViewability.js b/modules/bidViewability.js index c3b72cda8d4..837eccd00c1 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -2,13 +2,13 @@ // GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent // Does not work with other than GPT integration -import { config } from '../src/config.js'; +import {config} from '../src/config.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.json'; -import { logWarn, isFn, triggerPixel } from '../src/utils.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import adapterManager, { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; -import find from 'core-js-pure/features/array/find.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 {find} from '../src/polyfill.js'; const MODULE_NAME = 'bidViewability'; const CONFIG_ENABLED = 'enabled'; @@ -70,12 +70,12 @@ export let impressionViewableHandler = (globalModuleConfig, slot, event) => { // trigger respective bidder's onBidViewable handler adapterManager.callBidViewableBidder(respectiveBid.bidder, respectiveBid); // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels - events.emit(EVENTS.BID_VIEWABLE, respectiveBid); + events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, respectiveBid); } }; export let init = () => { - events.on(EVENTS.AUCTION_INIT, () => { + events.on(CONSTANTS.EVENTS.AUCTION_INIT, () => { // read the config for the module const globalModuleConfig = config.getConfig(MODULE_NAME) || {}; // do nothing if module-config.enabled is not set to true diff --git a/modules/bidViewabilityIO.js b/modules/bidViewabilityIO.js index d936fb4aeec..ff7ec70e32c 100644 --- a/modules/bidViewabilityIO.js +++ b/modules/bidViewabilityIO.js @@ -1,7 +1,7 @@ import { logMessage } from '../src/utils.js'; import { config } from '../src/config.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; const MODULE_NAME = 'bidViewabilityIO'; const CONFIG_ENABLED = 'enabled'; @@ -42,7 +42,7 @@ export let getViewableOptions = (bid) => { export let markViewed = (bid, entry, observer) => { return () => { observer.unobserve(entry.target); - events.emit(EVENTS.BID_VIEWABLE, bid); + events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, bid); _logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} was viewed`); } } @@ -77,7 +77,7 @@ export let init = () => { if (conf[MODULE_NAME][CONFIG_ENABLED] && CLIENT_SUPPORTS_IO) { // if the module is enabled and the browser supports Intersection Observer, // then listen to AD_RENDER_SUCCEEDED to setup IO's for supported mediaTypes - events.on(EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => { + events.on(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => { if (isSupportedMediaType(bid)) { let viewable = new IntersectionObserver(viewCallbackFactory(bid), getViewableOptions(bid)); let element = document.getElementById(bid.adUnitCode); diff --git a/modules/biddoBidAdapter.js b/modules/biddoBidAdapter.js new file mode 100644 index 00000000000..5512ca60f8e --- /dev/null +++ b/modules/biddoBidAdapter.js @@ -0,0 +1,92 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'biddo'; +const ENDPOINT_URL = 'https://ad.adopx.net/delivery/impress'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bidRequest The bid request params to validate. + * @return boolean True if this is a valid bid request, and false otherwise. + */ + isBidRequestValid: function(bidRequest) { + return !!bidRequest.params.zoneId; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {Array} validBidRequests an array of bid requests + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests) { + let serverRequests = []; + + validBidRequests.forEach(bidRequest => { + const sizes = bidRequest.mediaTypes.banner.sizes; + + sizes.forEach(([width, height]) => { + bidRequest.params.requestedSizes = [width, height]; + + const payload = { + ctype: 'div', + pzoneid: bidRequest.params.zoneId, + width, + height, + }; + + const payloadString = Object.keys(payload).map(k => k + '=' + encodeURIComponent(payload[k])).join('&'); + + serverRequests.push({ + method: 'GET', + url: ENDPOINT_URL, + data: payloadString, + bidderRequest: bidRequest, + }); + }); + }); + + return serverRequests; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidderRequest A matched bid request for this response. + * @return Array An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, {bidderRequest}) { + const response = serverResponse.body; + const bidResponses = []; + + if (response && response.template && response.template.html) { + const {bidId} = bidderRequest; + const [width, height] = bidderRequest.params.requestedSizes; + + const bidResponse = { + requestId: bidId, + cpm: response.hb.cpm, + creativeId: response.banner.hash, + currency: 'USD', + netRevenue: response.hb.netRevenue, + ttl: 600, + ad: response.template.html, + mediaType: 'banner', + meta: { + advertiserDomains: response.hb.adomains || [], + }, + width, + height, + }; + + bidResponses.push(bidResponse); + } + + return bidResponses; + }, +} + +registerBidder(spec); diff --git a/modules/biddoBidAdapter.md b/modules/biddoBidAdapter.md new file mode 100644 index 00000000000..baea44b22f2 --- /dev/null +++ b/modules/biddoBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: Biddo Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@biddo.net +``` + +# Description + +Module that connects to Invamia demand sources. + +# Test Parameters + +``` + const adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [{ + bidder: 'biddo', + params: { + zoneId: 7254, + }, + }], + }]; +``` diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js new file mode 100644 index 00000000000..cd8b2462eb8 --- /dev/null +++ b/modules/big-richmediaBidAdapter.js @@ -0,0 +1,117 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {spec as baseAdapter} from './appnexusBidAdapter.js'; // eslint-disable-line prebid/validate-imports + +const BIDDER_CODE = 'big-richmedia'; + +const metadataByRequestId = {}; + +export const spec = { + version: '1.4.0', + code: BIDDER_CODE, + gvlid: baseAdapter.GVLID, // use base adapter gvlid + supportedMediaTypes: [ BANNER, VIDEO ], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!baseAdapter.isBidRequestValid) { return true; } + return baseAdapter.isBidRequestValid(bid); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + if (!baseAdapter.buildRequests) { return []; } + + const publisherId = config.getConfig('bigRichmedia.publisherId'); + if (typeof publisherId !== 'string') { return []; } + + bidRequests.forEach(bidRequest => { + if (bidRequest.params.format === 'skin' && bidRequest.mediaTypes.banner) { + bidRequest.mediaTypes.banner.sizes.push([1800, 1000]); + } + metadataByRequestId[bidRequest.bidId] = { placementId: bidRequest.adUnitCode, bidder: bidRequest.bidder }; + }); + return baseAdapter.buildRequests(bidRequests, bidderRequest); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, params) { + const publisherId = config.getConfig('bigRichmedia.publisherId'); + if (typeof publisherId !== 'string') { return []; } + + const bids = baseAdapter.interpretResponse(serverResponse, params); + bids.forEach(bid => { + const { placementId, bidder } = metadataByRequestId[bid.requestId] || {}; + const { width = 1, height = 1, ad, creativeId = '', cpm, vastXml, vastUrl } = bid; + const bidRequest = params.bidderRequest.bids.find(({ bidId }) => bidId === bid.requestId); + const format = (bidRequest && bidRequest.params && bidRequest.params.format) || 'video-sticky-footer'; + const isReplayable = bidRequest && bidRequest.params && bidRequest.params.isReplayable; + const customSelector = bidRequest && bidRequest.params && bidRequest.params.customSelector; + const renderParams = { + adm: ad, + vastXml, + vastUrl, + width, + height, + placementId, + bidId: bid.requestId, + creativeId: `${creativeId}`, + bidder, + cpm, + format, + customSelector, + isReplayable + }; + const encoded = window.btoa(JSON.stringify(renderParams)); + bid.ad = ` + `; + + if (bid.mediaType !== 'banner') { // in case this is a video + bid.mediaType = 'banner'; + delete bid.renderer; + delete bid.vastUrl; + delete bid.vastXml; + bid.width = 1; + bid.height = 1; + } + }); + return bids; + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (!baseAdapter.getUserSyncs) { return []; } + return baseAdapter.getUserSyncs(syncOptions, responses, gdprConsent); + }, + + transformBidParams: function (params, isOpenRtb) { + if (!baseAdapter.transformBidParams) { return params; } + return baseAdapter.transformBidParams(params, isOpenRtb); + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function (bid) { + if (!baseAdapter.onBidWon) { return; } + baseAdapter.onBidWon(bid); + } +} + +registerBidder(spec); diff --git a/modules/big-richmediaBidAdapter.md b/modules/big-richmediaBidAdapter.md new file mode 100644 index 00000000000..26f77e527fb --- /dev/null +++ b/modules/big-richmediaBidAdapter.md @@ -0,0 +1,82 @@ +# Overview + +``` +Module Name: BI.Garage Rich Media +Module Type: Bidder Adapter +Maintainer: mediaconsortium-develop@bi.garage.co.jp +``` + +# Description + +Module which renders richmedia demand from a Xandr seat + +### Global configuration + +```javascript +pbjs.setConfig({ + debug: false, + // …, + bigRichmedia: { + publisherId: 'A7FN99NZ98F5ZD4G', // Required + }, +}); +``` + +# AdUnit Configuration +```javascript +var adUnits = [ + // Skin adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'big-richmedia', + params: { + placementId: 12345, + format: 'skin' // This will automatically add 1800x1000 size to banner mediaType + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. + // To note - appnexus supports additional values for our system that are not part of the ORTB spec. If you want + // to use these values, they will have to be declared in the bids[].params.video object instead using the appnexus syntax. + // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will + // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. + minduration: 1, + maxduration: 60, + skip: 0, // 1 - true, 0 - false + skipafter: 5, + playbackmethod: [2], // note - we only support options 1-4 at this time + api: [1,2,3] // note - option 6 is not supported at this time + } + }, + bids: [ + { + bidder: 'big-richmedia', + params: { + placementId: 12345, + video: { + skippable: true, + playback_method: 'auto_play_sound_off' + }, + format: 'video-sticky-footer', // or 'video-sticky-top' + isReplayable: true // Default to false - choose if the video should be replayable or not. + customSelector: '#nav-bar' // custom selector for navbar + } + } + ] + } +]; +``` diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 38195f8f9d9..6223626834d 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -88,11 +88,13 @@ export const spec = { host: location.host }, source: { - tid: bidRequest.transactionId + tid: bidRequest.transactionId, + ext: { + schain: {} + } }, regs: { coppa: config.getConfig('coppa') === true ? 1 : 0, - ext: {} }, user: { ext: {} @@ -115,7 +117,7 @@ export const spec = { } if (bidRequest.schain) { - data.source.ext.schain = bidRequest.schain; + deepSetValue(data, 'source.ext.schain', bidRequest.schain); } let connection = navigator.connection || navigator.webkitConnection; diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 70349f95cde..45b6c46c2df 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -1,7 +1,6 @@ // eslint-disable-next-line prebid/validate-imports // eslint-disable-next-line prebid/validate-imports -import {registerBidder} from 'src/adapters/bidderFactory.js' - +import {registerBidder} from '../src/adapters/bidderFactory.js' export const BIDDER_CODE = 'bliink' export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/delivery' export const BLIINK_ENDPOINT_ENGINE_VAST = 'https://engine.bliink.io/vast' @@ -174,6 +173,8 @@ export const buildRequests = (_, bidderRequest) => { pageUrl: bidderRequest.refererInfo.referer, pageDescription: getMetaValue(META_DESCRIPTION), keywords: getKeywords().join(','), + gdpr: false, + gdpr_consent: '', pageTitle: document.title, } diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index 03fb0b92c8f..d362dfa5fdb 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -1,10 +1,10 @@ -import { deepAccess, deepSetValue, deepClone, logWarn, logError } from '../src/utils.js'; -import find from 'core-js-pure/features/array/find.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { Renderer } from '../src/Renderer.js'; -import { createEidsArray } from './userId/eids.js'; +import {deepAccess, deepClone, deepSetValue, logError, logWarn} from '../src/utils.js'; +import {find} from '../src/polyfill.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {Renderer} from '../src/Renderer.js'; +import {createEidsArray} from './userId/eids.js'; const DEV_MODE = window.location.search.match(/bbpbs_debug=true/); diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js new file mode 100644 index 00000000000..60d3c98f15e --- /dev/null +++ b/modules/brandmetricsRtdProvider.js @@ -0,0 +1,168 @@ +/** + * This module adds brandmetrics provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will load load the brandmetrics script and set survey- targeting to ad units of specific bidders. + * @module modules/brandmetricsRtdProvider + * @requires module:modules/realTimeData + */ +import { config } from '../src/config.js' +import { submodule } from '../src/hook.js' +import { deepSetValue, mergeDeep, logError, deepAccess } from '../src/utils.js' +import {loadExternalScript} from '../src/adloader.js' +const MODULE_NAME = 'brandmetrics' +const MODULE_CODE = MODULE_NAME +const RECEIVED_EVENTS = [] +const GVL_ID = 422 +const TCF_PURPOSES = [1, 7] + +function init (config, userConsent) { + const hasConsent = checkConsent(userConsent) + + if (hasConsent) { + const moduleConfig = getMergedConfig(config) + initializeBrandmetrics(moduleConfig.params.scriptId) + } + return hasConsent +} + +/** + * Checks TCF and USP consents + * @param {Object} userConsent + * @returns {boolean} + */ +function checkConsent (userConsent) { + let consent = false + + if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { + const gdpr = userConsent.gdpr + + if (gdpr.vendorData) { + const vendor = gdpr.vendorData.vendor + const purpose = gdpr.vendorData.purpose + + let vendorConsent = false + if (vendor.consents) { + vendorConsent = vendor.consents[GVL_ID] + } + + if (vendor.legitimateInterests) { + vendorConsent = vendorConsent || vendor.legitimateInterests[GVL_ID] + } + + const purposes = TCF_PURPOSES.map(id => { + return (purpose.consents && purpose.consents[id]) || (purpose.legitimateInterests && purpose.legitimateInterests[id]) + }) + const purposesValid = purposes.filter(p => p === true).length === TCF_PURPOSES.length + consent = vendorConsent && purposesValid + } + } else if (userConsent.usp) { + const usp = userConsent.usp + consent = usp[1] !== 'N' && usp[2] !== 'Y' + } + + return consent +} + +/** +* Add event- listeners to hook in to brandmetrics events +* @param {Object} reqBidsConfigObj +* @param {function} callback +*/ +function processBrandmetricsEvents (reqBidsConfigObj, moduleConfig, callback) { + const callBidTargeting = (event) => { + if (event.available && event.conf) { + const targetingConf = event.conf.displayOption || {} + if (targetingConf.type === 'pbjs') { + setBidderTargeting(reqBidsConfigObj, moduleConfig, targetingConf.targetKey || 'brandmetrics_survey', event.survey.measurementId) + } + } + callback() + } + + if (RECEIVED_EVENTS.length > 0) { + callBidTargeting(RECEIVED_EVENTS[RECEIVED_EVENTS.length - 1]) + } else { + window._brandmetrics = window._brandmetrics || [] + window._brandmetrics.push({ + cmd: '_addeventlistener', + val: { + event: 'surveyloaded', + reEmitLast: true, + handler: (ev) => { + RECEIVED_EVENTS.push(ev) + if (RECEIVED_EVENTS.length === 1) { + // Call bid targeting only for the first received event, if called subsequently, last event from the RECEIVED_EVENTS array is used + callBidTargeting(ev) + } + }, + } + }) + } +} + +/** + * Sets bid targeting of specific bidders + * @param {Object} reqBidsConfigObj + * @param {string} key Targeting key + * @param {string} val Targeting value + */ +function setBidderTargeting (reqBidsConfigObj, moduleConfig, key, val) { + const bidders = deepAccess(moduleConfig, 'params.bidders') + if (bidders && bidders.length > 0) { + const ortb2 = {} + deepSetValue(ortb2, 'ortb2.user.ext.data.' + key, val) + config.setBidderConfig({ + bidders: bidders, + config: ortb2 + }) + } +} + +/** + * Add the brandmetrics script to the page. + * @param {string} scriptId - The script- id provided by brandmetrics or brandmetrics partner + */ +function initializeBrandmetrics(scriptId) { + if (scriptId) { + const path = 'https://cdn.brandmetrics.com/survey/script/' + const file = scriptId + '.js' + const url = path + file + + loadExternalScript(url, MODULE_CODE) + } +} + +/** + * Merges a provided config with default values + * @param {Object} customConfig + * @returns + */ +function getMergedConfig(customConfig) { + return mergeDeep({ + waitForIt: false, + params: { + bidders: [], + scriptId: undefined, + } + }, customConfig) +} + +/** @type {RtdSubmodule} */ +export const brandmetricsSubmodule = { + name: MODULE_NAME, + getBidRequestData: function (reqBidsConfigObj, callback, customConfig) { + try { + const moduleConfig = getMergedConfig(customConfig) + if (moduleConfig.waitForIt) { + processBrandmetricsEvents(reqBidsConfigObj, moduleConfig, callback) + } else { + callback() + } + } catch (e) { + logError(e) + } + }, + init: init +} + +submodule('realTimeData', brandmetricsSubmodule) diff --git a/modules/brandmetricsRtdProvider.md b/modules/brandmetricsRtdProvider.md new file mode 100644 index 00000000000..89ee6bb75cf --- /dev/null +++ b/modules/brandmetricsRtdProvider.md @@ -0,0 +1,40 @@ +# Brandmetrics Real-time Data Submodule +This module is intended to be used by brandmetrics (https://brandmetrics.com) partners and sets targeting keywords to bids if the browser is eligeble to see a brandmetrics survey. +The module hooks in to brandmetrics events and requires a brandmetrics script to be running. The module can optionally load and initialize brandmetrics by providing the 'scriptId'- parameter. + +## Usage +Compile the Brandmetrics RTD module into your Prebid build: +``` +gulp build --modules=rtdModule,brandmetricsRtdProvider +``` + +> Note that the global RTD module, `rtdModule`, is a prerequisite of the Brandmetrics RTD module. + +Enable the Brandmetrics RTD in your Prebid configuration, using the below format: + +```javascript +pbjs.setConfig({ + ..., + realTimeData: { + auctionDelay: 500, // auction delay + dataProviders: [{ + name: 'brandmetrics', + waitForIt: true // should be true if there's an `auctionDelay`, + params: { + scriptId: '00000000-0000-0000-0000-000000000000', + bidders: ['ozone'] + } + }] + }, + ... +}) +``` + +## Parameters +| Name | Type | Description | Default | +| ----------------- | -------------------- | ------------------ | ------------------ | +| name | String | This should always be `brandmetrics` | - | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (recommended) | `false` | +| params | Object | | - | +| params.bidders | String[] | An array of bidders which should receive targeting keys. | `[]` | +| params.scriptId | String | A script- id GUID if the brandmetrics- script should be initialized. | `undefined` | diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js index 5d545b6f722..b141763af8e 100644 --- a/modules/bridgewellBidAdapter.js +++ b/modules/bridgewellBidAdapter.js @@ -1,7 +1,7 @@ -import { _each, inIframe, deepSetValue } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; +import {_each, deepSetValue, inIframe} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import {find} from '../src/polyfill.js'; const BIDDER_CODE = 'bridgewell'; const REQUEST_ENDPOINT = 'https://prebid.scupio.com/recweb/prebid.aspx?cb='; diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index a1943afda8d..15f2d58010d 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -15,14 +15,15 @@ * @property {?string} keyName */ -import { deepClone, logError, isGptPubadsDefined, isNumber, isFn, deepSetValue } from '../src/utils.js'; +import {deepClone, deepSetValue, isFn, isGptPubadsDefined, isNumber, logError, logInfo, generateUUID} from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajaxBuilder} from '../src/ajax.js'; import {loadExternalScript} from '../src/adloader.js'; import {getStorageManager} from '../src/storageManager.js'; -import find from 'core-js-pure/features/array/find.js'; +import {find, includes} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; const storage = getStorageManager(); @@ -107,6 +108,7 @@ export function setData(data) { } function getRTD(auc) { + logInfo(`Browsi RTD provider is fetching data for ${auc}`); try { const _bp = (_browsiData && _browsiData.p) || {}; return auc.reduce((rp, uc) => { @@ -332,13 +334,23 @@ export const browsiSubmodule = { getBidRequestData: setBidRequestsData }; -function getTargetingData(uc) { +function getTargetingData(uc, c, us, a) { const targetingData = getRTD(uc); + const auctionId = a.auctionId uc.forEach(auc => { if (isNumber(_ic[auc])) { _ic[auc] = _ic[auc] + 1; } + const transactionId = a.adUnits.find(adUnit => adUnit.code === auc).transactionId; + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + vendor: 'browsi', + type: 'adRequest', + billingId: generateUUID(), + transactionId: transactionId, + auctionId: auctionId + }) }); + logInfo('Browsi RTD provider returned targeting data', targetingData, 'for', uc) return targetingData; } diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 38bc99f1d83..65d1ced30e2 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -3,8 +3,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js' import { config } from '../src/config.js' import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); const BIDDER_CODE = 'ccx' +const storage = getStorageManager({bidderCode: BIDDER_CODE}); const BID_URL = 'https://delivery.clickonometrics.pl/ortb/prebid/bid' const SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6] const SUPPORTED_VIDEO_MIMES = ['video/mp4', 'video/x-flv'] diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index 3c2d3c51bf5..3fda9917715 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -1,9 +1,9 @@ -import { getDNT, inIframe, isArray, isNumber, logError, deepAccess, logWarn } from '../src/utils.js'; +import {deepAccess, getDNT, inIframe, isArray, isNumber, logError, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {includes} from '../src/polyfill.js'; export const helper = { getTopWindowDomain: function (url) { diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js index ecb56c00d29..b9da86ac24e 100644 --- a/modules/codefuelBidAdapter.js +++ b/modules/codefuelBidAdapter.js @@ -1,5 +1,4 @@ import { deepAccess, isArray } from '../src/utils.js'; -import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'codefuel'; @@ -34,8 +33,7 @@ export const spec = { const devicetype = getDeviceType() const publisher = setOnAny(validBidRequests, 'params.publisher'); const cur = CURRENCY; - // const endpointUrl = 'http://localhost:5000/prebid' - const endpointUrl = config.getConfig('codefuel.bidderUrl'); + const endpointUrl = 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid' const timeout = bidderRequest.timeout; validBidRequests.forEach(bid => bid.netRevenue = 'net'); diff --git a/modules/codefuelBidAdapter.md b/modules/codefuelBidAdapter.md index 321ae3b6644..4e1a0dd6409 100644 --- a/modules/codefuelBidAdapter.md +++ b/modules/codefuelBidAdapter.md @@ -21,7 +21,7 @@ You will receive the URLs when contacting us. ``` pbjs.setConfig({ codefuel: { - bidderUrl: 'https://ai-i-codefuel-ds-rtb-us-east-1-k8s-internal.seccint.com/prebid', + bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid', usersyncUrl: 'https://usersync-url.com' } }); @@ -74,7 +74,7 @@ pbjs.setConfig({ pbjs.setConfig({ codefuel: { - bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid' } }); ``` @@ -105,7 +105,7 @@ pbjs.setConfig({ pbjs.setConfig({ codefuel: { - bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid' } }); ``` diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 5e7c58f28ad..c1b6e31ff2e 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -1,10 +1,12 @@ import { getWindowTop, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'colossusssp'; const G_URL = 'https://colossusssp.com/?c=o&m=multi'; -const G_URL_SYNC = 'https://colossusssp.com/?c=o&m=cookie'; +const G_URL_SYNC = 'https://sync.colossusssp.com'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { @@ -46,7 +48,10 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placement_id)); + const validPlacamentId = bid.params && !isNaN(bid.params.placement_id); + const validGroupId = bid.params && !isNaN(bid.params.group_id); + + return Boolean(bid.bidId && (validPlacamentId || validGroupId)); }, /** @@ -56,17 +61,38 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { - const winTop = getWindowTop(); - const location = winTop.location; + let deviceWidth = 0; + let deviceHeight = 0; + let winLocation; + + try { + const winTop = getWindowTop(); + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + const location = refferLocation || winLocation; let placements = []; let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - 'secure': location.protocol === 'https:' ? 1 : 0, - 'host': location.host, - 'page': location.pathname, - 'placements': placements, + deviceWidth, + deviceHeight, + language: (navigator && navigator.language) ? navigator.language : '', + secure: location.protocol === 'https:' ? 1 : 0, + host: location.host, + page: location.pathname, + placements: placements, }; if (bidderRequest) { @@ -84,6 +110,7 @@ export const spec = { let traff = bid.params.traffic || BANNER let placement = { placementId: bid.params.placement_id, + groupId: bid.params.group_id, bidId: bid.bidId, sizes: bid.mediaTypes[traff].sizes, traffic: traff, @@ -170,11 +197,33 @@ export const spec = { return response; }, - getUserSyncs: () => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'html' : 'hms.gif'; + let syncUrl = G_URL_SYNC + `/${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: 'image', - url: G_URL_SYNC + type: syncType, + url: syncUrl }]; + }, + + onBidWon: (bid) => { + if (bid.nurl) { + ajax(bid.nurl, null); + } } }; diff --git a/modules/colossussspBidAdapter.md b/modules/colossussspBidAdapter.md index 8797c648c95..4187dfbf36e 100644 --- a/modules/colossussspBidAdapter.md +++ b/modules/colossussspBidAdapter.md @@ -26,5 +26,19 @@ Module that connects to Colossus SSP demand sources traffic: 'banner' } }] - ]; + }, { + code: 'placementid_1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'colossusssp', + params: { + group_id: 0, + traffic: 'banner' + } + }] + }]; ``` diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js new file mode 100644 index 00000000000..77f918276bc --- /dev/null +++ b/modules/compassBidAdapter.js @@ -0,0 +1,208 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.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 = 'compass'; +const AD_URL = 'https://sa-lb.deliverimp.com/pbjs'; +const SYNC_URL = 'https://sa-cs.deliverimp.com'; + +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 = {}) => { + 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.referer; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + 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/compassBidAdapter.md b/modules/compassBidAdapter.md new file mode 100644 index 00000000000..18d52c12384 --- /dev/null +++ b/modules/compassBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Compass Bidder Adapter +Module Type: Compass Bidder Adapter +Maintainer: sa-support@brightcom.com +``` + +# Description + +Connects to Compass exchange for bids. +Compass 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: 'compass', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'compass', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'compass', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 9a55e9cef1d..99e2492fb94 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -166,7 +166,7 @@ export const spec = { registerBidder(spec); -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * Check or generate a UID for the current user. diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index ac44a8b5a2d..2da2eda4c77 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -7,8 +7,8 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; -import {logError, formatQS} from '../src/utils.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {formatQS, logError} from '../src/utils.js'; +import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'connectId'; const VENDOR_ID = 25; diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 65ffc9a4def..f0355749055 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -1,15 +1,13 @@ - /** * This module adds GDPR 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 GDPR supported adapters to read/pass this information to * their system. */ -import { logInfo, isFn, getAdUnitSizes, logWarn, isStr, isPlainObject, logError, isNumber } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { gdprDataHandler } from '../src/adapterManager.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import strIncludes from 'core-js-pure/features/string/includes.js'; +import {getAdUnitSizes, isFn, 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'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -36,23 +34,22 @@ const cmpCallMap = { /** * This function reads the consent string from the config to obtain the consent information of the user. - * @param {function(string)} cmpSuccess acts as a success callback when the value is read from config; pass along consentObject (string) from CMP - * @param {function(string)} cmpError acts as an error callback while interacting with the config string; pass along an error message (string) - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP */ -function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) { - cmpSuccess(staticConsentData, hookConfig); +function lookupStaticConsentData({onSuccess}) { + onSuccess(staticConsentData); } /** * 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(string)} cmpSuccess acts as a success callback when CMP returns a value; pass along consentObject (string) from CMP - * @param {function(string)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * @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) + * @param width + * @param height size info passed to the SafeFrame API (used only for TCFv1 when Prebid is running within a safeframe) */ -function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { +function lookupIabConsent({onSuccess, onError, width, height}) { function findCMP() { let f = window; let cmpFrame; @@ -102,10 +99,10 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { logInfo('Received a response from CMP', tcfData); if (success) { if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { - cmpSuccess(tcfData, hookConfig); + processCmpData(tcfData, {onSuccess, onError}); } } else { - cmpError('CMP unable to register callback function. Please check CMP setup.', hookConfig); + onError('CMP unable to register callback function. Please check CMP setup.'); } } @@ -115,7 +112,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { function afterEach() { if (cmpResponse.getConsentData && cmpResponse.getVendorConsents) { logInfo('Received all requested responses from CMP', cmpResponse); - cmpSuccess(cmpResponse, hookConfig); + processCmpData(cmpResponse, {onSuccess, onError}); } } @@ -136,7 +133,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { let { cmpFrame, cmpFunction } = findCMP(); if (!cmpFrame) { - return cmpError('CMP not found.', hookConfig); + return onError('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) @@ -183,16 +180,6 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { } } - // find sizes from adUnits object - let adUnits = hookConfig.adUnits; - let width = 1; - let height = 1; - if (Array.isArray(adUnits) && adUnits.length > 0) { - let sizes = getAdUnitSizes(adUnits[0]); - width = sizes[0][0]; - height = sizes[0][1]; - } - window.$sf.ext.register(width, height, sfCallback); window.$sf.ext.cmp(commandName); } @@ -249,7 +236,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { function readPostMessageResponse(event) { let cmpDataPkgName = `${apiName}Return`; - let json = (typeof event.data === 'string' && strIncludes(event.data, cmpDataPkgName)) ? JSON.parse(event.data) : event.data; + let json = (typeof event.data === 'string' && includes(event.data, cmpDataPkgName)) ? JSON.parse(event.data) : event.data; if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) { let payload = json[cmpDataPkgName]; // TODO - clean up this logic (move listeners?); we have duplicate messages responses because 2 eventlisteners are active from the 2 cmp requests running in parallel @@ -261,6 +248,70 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { } } +/** + * 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. + * @param width if we are running in an iframe, the TCFv1 spec requires us to use the SafeFrame API to find the CMP - which + * in turn requires width and height. + * @param height see width above + */ +function loadConsentData(cb, width = 1, height = 1) { + let isDone = false; + let timer = null; + + function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { + if (timer != null) { + clearTimeout(timer); + } + isDone = true; + gdprDataHandler.setConsentData(consentData); + if (cb != null) { + cb(shouldCancelAuction, errMsg, ...extraArgs); + } + } + + if (!includes(Object.keys(cmpCallMap), userCMP)) { + done(null, false, `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) { + let consentData = null; + let shouldCancelAuction = true; + if (allowAuction.value && cmpVersion === 1) { + // still set the consentData to undefined when there is a problem as per config options + consentData = storeConsentData(undefined); + shouldCancelAuction = false; + } + done(consentData, shouldCancelAuction, msg, ...extraArgs); + } + } + cmpCallMap[userCMP]({ + width, + height, + ...callbacks + }); + + if (!isDone) { + if (consentTimeout === 0) { + processCmpData(undefined, callbacks); + } else { + timer = setTimeout(function () { + if (cmpVersion === 2) { + // for TCFv2, we allow the auction to continue on timeout + done(storeConsentData(undefined), false, `No response from CMP, continuing auction...`) + } else { + callbacks.onError('CMP workflow exceeded timeout threshold.'); + } + }, consentTimeout); + } + } +} + /** * 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 @@ -270,48 +321,60 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { * @param {function} fn required; The next function in the chain, used by hook.js */ export function requestBidsHook(fn, reqBidsConfigObj) { - // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions) - const hookConfig = { - context: this, - args: [reqBidsConfigObj], - nextFn: fn, - adUnits: reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits, - bidsBackHandler: reqBidsConfigObj.bidsBackHandler, - haveExited: false, - timer: null - }; - - // in case we already have consent (eg during bid refresh) - if (consentData) { - logInfo('User consent information already known. Pulling internally stored information...'); - return exitModule(null, hookConfig); - } + const load = (() => { + if (consentData) { + logInfo('User consent information already known. Pulling internally stored information...'); + return function (cb) { + // eslint-disable-next-line standard/no-callback-literal + cb(false); + } + } else { + // find sizes from adUnits object + let adUnits = reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits; + let width = 1; + let height = 1; + if (Array.isArray(adUnits) && adUnits.length > 0) { + let sizes = getAdUnitSizes(adUnits[0]); + width = sizes[0][0]; + height = sizes[0][1]; + } - if (!includes(Object.keys(cmpCallMap), userCMP)) { - logWarn(`CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); - return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); - } + return function (cb) { + loadConsentData(cb, width, height); + } + } + })(); - cmpCallMap[userCMP].call(this, processCmpData, cmpFailed, hookConfig); + load(function (shouldCancelAuction, errMsg, ...extraArgs) { + if (errMsg) { + let log = logWarn; + if (cmpVersion === 1 && !shouldCancelAuction) { + errMsg = `${errMsg} 'allowAuctionWithoutConsent' activated.`; + } else if (shouldCancelAuction) { + log = logError; + errMsg = `${errMsg} Canceling auction as per consentManagement config.`; + } + log(errMsg, ...extraArgs); + } - // only let this code run if module is still active (ie if the callbacks used by CMPs haven't already finished) - if (!hookConfig.haveExited) { - if (consentTimeout === 0) { - processCmpData(undefined, hookConfig); + if (shouldCancelAuction) { + if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { + reqBidsConfigObj.bidsBackHandler(); + } else { + logError('Error executing bidsBackHandler'); + } } else { - hookConfig.timer = setTimeout(cmpTimedOut.bind(null, hookConfig), consentTimeout); + 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 exit the module depending on config settings. - * If it's good, then we store the value and exits the module. - * @param {object} consentObject required; object returned by CMP that contains user's consent choices - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * If it's bad, we call `onError` + * If it's good, then we store the value and call `onSuccess` */ -function processCmpData(consentObject, hookConfig) { +function processCmpData(consentObject, {onSuccess, onError}) { function checkV1Data(consentObject) { let gdprApplies = consentObject && consentObject.getConsentData && consentObject.getConsentData.gdprApplies; return !!( @@ -347,51 +410,19 @@ function processCmpData(consentObject, hookConfig) { // determine which set of checks to run based on cmpVersion let checkFn = (cmpVersion === 1) ? checkV1Data : (cmpVersion === 2) ? checkV2Data : null; - // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2. - if (allowAuction.definedInConfig && cmpVersion === 2) { - logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`); - } else if (!allowAuction.definedInConfig && cmpVersion === 1) { - logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); - } - if (isFn(checkFn)) { if (checkFn(consentObject)) { - cmpFailed(`CMP returned unexpected value during lookup process.`, hookConfig, consentObject); + onError(`CMP returned unexpected value during lookup process.`, consentObject); } else { - clearTimeout(hookConfig.timer); - storeConsentData(consentObject); - exitModule(null, hookConfig); + onSuccess(storeConsentData(consentObject)); } } else { - cmpFailed('Unable to derive CMP version to process data. Consent object does not conform to TCF v1 or v2 specs.', hookConfig, consentObject); + onError('Unable to derive CMP version to process data. Consent object does not conform to TCF v1 or v2 specs.', consentObject); } } /** - * General timeout callback when interacting with CMP takes too long. - */ -function cmpTimedOut(hookConfig) { - cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig); -} - -/** - * This function contains the controlled steps to perform when there's a problem with CMP. - * @param {string} errMsg required; should be a short descriptive message for why the failure/issue happened. - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) - * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging -*/ -function cmpFailed(errMsg, hookConfig, extraArgs) { - clearTimeout(hookConfig.timer); - - // still set the consentData to undefined when there is a problem as per config options - if (allowAuction.value && cmpVersion === 1) { - storeConsentData(undefined); - } - exitModule(errMsg, hookConfig, extraArgs); -} - -/** - * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanager.js for later in the auction + * 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) { @@ -412,50 +443,7 @@ function storeConsentData(cmpConsentObject) { }; } consentData.apiVersion = cmpVersion; - gdprDataHandler.setConsentData(consentData); -} - -/** - * This function handles the exit logic for the module. - * While there are several paths in the module's logic to call this function, we only allow 1 of the 3 potential exits to happen before suppressing others. - * - * We prevent multiple exits to avoid conflicting messages in the console depending on certain scenarios. - * One scenario could be auction was canceled due to timeout with CMP being reached. - * While the timeout is the accepted exit and runs first, the CMP's callback still tries to process the user's data (which normally leads to a good exit). - * In this case, the good exit will be suppressed since we already decided to cancel the auction. - * - * Three exit paths are: - * 1. good exit where auction runs (CMP data is processed normally). - * 2. bad exit but auction still continues (warning message is logged, CMP data is undefined and still passed along). - * 3. bad exit with auction canceled (error message is logged). - * @param {string} errMsg optional; only to be used when there was a 'bad' exit. String is a descriptive message for the failure/issue encountered. - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) - * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging - */ -function exitModule(errMsg, hookConfig, extraArgs) { - if (hookConfig.haveExited === false) { - hookConfig.haveExited = true; - - let context = hookConfig.context; - let args = hookConfig.args; - let nextFn = hookConfig.nextFn; - - if (errMsg) { - if (allowAuction.value && cmpVersion === 1) { - logWarn(errMsg + ` 'allowAuctionWithoutConsent' activated.`, extraArgs); - nextFn.apply(context, args); - } else { - logError(errMsg + ' Canceling auction as per consentManagement config.', extraArgs); - if (typeof hookConfig.bidsBackHandler === 'function') { - hookConfig.bidsBackHandler(); - } else { - logError('Error executing bidsBackHandler'); - } - } - } else { - nextFn.apply(context, args); - } - } + return consentData; } /** @@ -465,7 +453,7 @@ export function resetConsentData() { consentData = undefined; userCMP = undefined; cmpVersion = 0; - gdprDataHandler.setConsentData(null); + gdprDataHandler.reset(); } /** @@ -516,5 +504,14 @@ export function setConsentConfig(config) { $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); } addedConsentHook = true; + gdprDataHandler.enable(); + loadConsentData(); // immediately look up consent data to make it available without requiring an auction + + // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2. + if (allowAuction.definedInConfig && cmpVersion === 2) { + logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`); + } else if (!allowAuction.definedInConfig && cmpVersion === 1) { + logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); + } } config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 4a4c4ae0a55..e98b41d5c9e 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -27,23 +27,17 @@ const uspCallMap = { /** * This function reads the consent string from the config to obtain the consent information of the user. - * @param {function(string)} cmpSuccess acts as a success callback when the value is read from config; pass along consentObject (string) from CMP - * @param {function(string)} cmpError acts as an error callback while interacting with the config string; pass along an error message (string) - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ -function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) { - cmpSuccess(staticConsentData, hookConfig); +function lookupStaticConsentData({onSuccess}) { + onSuccess(staticConsentData); } /** * This function handles interacting with an USP compliant consent manager to obtain the consent information of the user. * Given the async nature of the USP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function(string)} uspSuccess acts as a success callback when USPAPI returns a value; pass along consentObject (string) from USPAPI - * @param {function(string)} uspError acts as an error callback while interacting with USPAPI; pass along an error message (string) - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ -function lookupUspConsent(uspSuccess, uspError, hookConfig) { +function lookupUspConsent({onSuccess, onError}) { function findUsp() { let f = window; let uspapiFrame; @@ -78,9 +72,9 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { function afterEach() { if (uspResponse.usPrivacy) { - uspSuccess(uspResponse, hookConfig); + processUspData(uspResponse, {onSuccess, onError}) } else { - uspError('Unable to get USP consent string.', hookConfig); + onError('Unable to get USP consent string.'); } } @@ -100,7 +94,7 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { let { uspapiFrame, uspapiFunction } = findUsp(); if (!uspapiFrame) { - return uspError('USP CMP not found.', hookConfig); + return onError('USP CMP not found.'); } // to collect the consent information from the user, we perform a call to USPAPI @@ -165,119 +159,92 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { } /** - * If consentManagementUSP module is enabled (ie included in setConfig), this hook function will attempt to fetch the - * user's encoded consent string from the supported USPAPI. Once obtained, the module will store this - * data as part of a uspConsent object which gets transferred to adapterManager's uspDataHandler 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 + * Lookup consent data and store it in the `consentData` global as well as `adapterManager.js`' uspDataHanlder. + * + * @param cb a callback that takes an error message and extra error arguments; all args will be undefined if consent + * data was retrieved successfully. */ -export function requestBidsHook(fn, reqBidsConfigObj) { - // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions) - const hookConfig = { - context: this, - args: [reqBidsConfigObj], - nextFn: fn, - adUnits: reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits, - bidsBackHandler: reqBidsConfigObj.bidsBackHandler, - haveExited: false, - timer: null - }; +function loadConsentData(cb) { + let timer = null; + let isDone = false; + + function done(consentData, errMsg, ...extraArgs) { + if (timer != null) { + clearTimeout(timer); + } + isDone = true; + uspDataHandler.setConsentData(consentData); + if (cb != null) { + cb(errMsg, ...extraArgs) + } + } if (!uspCallMap[consentAPI]) { - logWarn(`USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); - return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); + done(null, `USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + return; + } + + const callbacks = { + onSuccess: done, + onError: function (errMsg, ...extraArgs) { + done(null, `${errMsg} Resuming auction without consent data as per consentManagement config.`, ...extraArgs); + } } - uspCallMap[consentAPI].call(this, processUspData, uspapiFailed, hookConfig); + uspCallMap[consentAPI](callbacks); - // only let this code run if module is still active (ie if the callbacks used by USPs haven't already finished) - if (!hookConfig.haveExited) { + if (!isDone) { if (consentTimeout === 0) { - processUspData(undefined, hookConfig); + processUspData(undefined, callbacks); } else { - hookConfig.timer = setTimeout(uspapiTimeout.bind(null, hookConfig), consentTimeout); + timer = setTimeout(callbacks.onError.bind(null, 'USPAPI workflow exceeded timeout threshold.'), consentTimeout) } } } +/** + * If consentManagementUSP module is enabled (ie included in setConfig), this hook function will attempt to fetch the + * user's encoded consent string from the supported USPAPI. Once obtained, the module will store this + * data as part of a uspConsent object which gets transferred to adapterManager's uspDataHandler 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 function requestBidsHook(fn, reqBidsConfigObj) { + loadConsentData((errMsg, ...extraArgs) => { + if (errMsg != null) { + logWarn(errMsg, ...extraArgs); + } + fn.call(this, reqBidsConfigObj); + }); +} + /** * This function checks the consent data provided by USPAPI to ensure it's in an expected state. * If it's bad, we exit the module depending on config settings. * If it's good, then we store the value and exits the module. * @param {object} consentObject required; object returned by USPAPI that contains user's consent choices - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * @param {function(string)} onSuccess callback accepting the resolved consent USP consent string + * @param {function(string, ...{}?)} onError callback accepting error message and any extra error arguments (used purely for logging) */ -function processUspData(consentObject, hookConfig) { +function processUspData(consentObject, {onSuccess, onError}) { const valid = !!(consentObject && consentObject.usPrivacy); if (!valid) { - uspapiFailed(`USPAPI returned unexpected value during lookup process.`, hookConfig, consentObject); + onError(`USPAPI returned unexpected value during lookup process.`, consentObject); return; } - clearTimeout(hookConfig.timer); storeUspConsentData(consentObject); - exitModule(null, hookConfig); -} - -/** - * General timeout callback when interacting with USPAPI takes too long. - */ -function uspapiTimeout(hookConfig) { - uspapiFailed('USPAPI workflow exceeded timeout threshold.', hookConfig); -} - -/** - * This function contains the controlled steps to perform when there's a problem with USPAPI. - * @param {string} errMsg required; should be a short descriptive message for why the failure/issue happened. - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) - * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging -*/ -function uspapiFailed(errMsg, hookConfig, extraArgs) { - clearTimeout(hookConfig.timer); - - exitModule(errMsg, hookConfig, extraArgs); + onSuccess(consentData); } /** * Stores USP data locally in module and then invokes uspDataHandler.setConsentData() to make information available in adaptermanger.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) + * @param {object} consentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) */ function storeUspConsentData(consentObject) { if (consentObject && consentObject.usPrivacy) { consentData = consentObject.usPrivacy; - uspDataHandler.setConsentData(consentData); - } -} - -/** - * This function handles the exit logic for the module. - * There are a couple paths in the module's logic to call this function and we only allow 1 of the 2 potential exits to happen before suppressing others. - * - * We prevent multiple exits to avoid conflicting messages in the console depending on certain scenarios. - * One scenario could be auction was canceled due to timeout with USPAPI being reached. - * While the timeout is the accepted exit and runs first, the USP's callback still tries to process the user's data (which normally leads to a good exit). - * In this case, the good exit will be suppressed since we already decided to cancel the auction. - * - * Three exit paths are: - * 1. good exit where auction runs (USPAPI data is processed normally). - * 2. bad exit but auction still continues (warning message is logged, USPAPI data is undefined and still passed along). - * @param {string} errMsg optional; only to be used when there was a 'bad' exit. String is a descriptive message for the failure/issue encountered. - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) - * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging - */ -function exitModule(errMsg, hookConfig, extraArgs) { - if (hookConfig.haveExited === false) { - hookConfig.haveExited = true; - - let context = hookConfig.context; - let args = hookConfig.args; - let nextFn = hookConfig.nextFn; - - if (errMsg) { - logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs); - } - nextFn.apply(context, args); } } @@ -287,7 +254,7 @@ function exitModule(errMsg, hookConfig, extraArgs) { export function resetConsentData() { consentData = undefined; consentAPI = undefined; - uspDataHandler.setConsentData(null); + uspDataHandler.reset(); } /** @@ -328,5 +295,7 @@ export function setConsentConfig(config) { $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); } addedConsentHook = true; + uspDataHandler.enable(); + loadConsentData(); // immediately look up consent data to make it available without requiring an auction } config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 1a2845ba85b..559abd0b8f6 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -126,6 +126,27 @@ export const spec = { bid.netRevenue = true; bid.referrer = bidRequest.bidderRequest.refererInfo.referer; + bid.meta = { + advertiserDomains: decision.adomain || [] + }; + + if (decision.cats) { + if (decision.cats.length > 0) { + bid.meta.primaryCatId = decision.cats[0]; + if (decision.cats.length > 1) { + bid.meta.secondaryCatIds = decision.cats.slice(1); + } + } + } + + if (decision.networkId) { + bid.meta.networkId = decision.networkId; + } + + if (decision.mediaType) { + bid.meta.mediaType = decision.mediaType; + } + bidResponses.push(bid); } } @@ -136,13 +157,15 @@ export const spec = { getUserSyncs: function(syncOptions, serverResponses) { if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'https://sync.serverbid.com/ss/' + siteId + '.html' - }]; + 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' + }]; + } } - if (syncOptions.pixelEnabled && serverResponses.length > 0) { + if (syncOptions.pixelEnabled && serverResponses && serverResponses.length > 0) { return serverResponses[0].body.pixels; } else { logWarn(bidder + ': Please enable iframe based user syncing.'); diff --git a/modules/consumableBidAdapter.md b/modules/consumableBidAdapter.md index 2189494ebd4..ba472899c49 100644 --- a/modules/consumableBidAdapter.md +++ b/modules/consumableBidAdapter.md @@ -4,7 +4,7 @@ Module Name: Consumable Bid Adapter Module Type: Consumable Adapter -Maintainer: naffis@consumable.com +Maintainer: prebid@consumable.com # Description diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 92b5d47277e..7ee8b1b7681 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -2,11 +2,12 @@ import { logWarn, isStr, deepAccess, isArray, getBidIdParameter, deepSetValue, i 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 = 24; -export const storage = getStorageManager(GVLID); const BIDDER_CODE = 'conversant'; +export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); const URL = 'https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25'; export const spec = { @@ -76,6 +77,9 @@ export const spec = { displaymanager: 'Prebid.js', displaymanagerver: '$prebid.version$' }; + if (bid.ortb2Imp) { + mergeDeep(imp, bid.ortb2Imp); + } copyOptProperty(bid.params.tag_id, imp, 'tagid'); @@ -132,6 +136,12 @@ export const spec = { let userExt = {}; + // pass schain object if it is present + const schain = deepAccess(validBidRequests, '0.schain'); + if (schain) { + deepSetValue(payload, 'source.ext.schain', schain); + } + if (bidderRequest) { // Add GDPR flag and consent string if (bidderRequest.gdprConsent) { @@ -167,6 +177,9 @@ export const spec = { payload.user = {ext: userExt}; } + const firstPartyData = config.getConfig('ortb2') || {}; + mergeDeep(payload, firstPartyData); + return { method: 'POST', url: bidurl, diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index b98b72b59ad..61ca4f929e7 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -1,15 +1,24 @@ -import { logError, convertTypes, convertCamelToUnderscore, isArray, deepAccess, getBidRequest, isEmpty, transformBidderParamKeywords } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { auctionManager } from '../src/auctionManager.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import { getStorageManager } from '../src/storageManager.js'; +import { + convertCamelToUnderscore, + convertTypes, + deepAccess, + getBidRequest, + isArray, + isEmpty, + logError, + transformBidderParamKeywords +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {find, includes} from '../src/polyfill.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; const TTL = 360; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, @@ -110,9 +119,10 @@ export const spec = { }, onBidWon: function(bid) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', bid._prebidWon); - xhr.send(); + ajax(bid._prebidWon, null, null, { + method: 'POST', + contentType: 'application/json' + }); } }; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index a4ec99e4fa8..75d41d970a9 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,11 +1,11 @@ -import { isArray, getUniqueIdentifierStr, parseUrl, deepAccess, logWarn, logError, logInfo } from '../src/utils.js'; +import {deepAccess, getUniqueIdentifierStr, isArray, logError, logInfo, logWarn, parseUrl} from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; -import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 -import { getStorageManager } from '../src/storageManager.js'; +import {find} from '../src/polyfill.js'; +import {verify} from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 +import {getStorageManager} from '../src/storageManager.js'; const GVLID = 91; export const ADAPTER_VERSION = 34; @@ -13,7 +13,7 @@ const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; export const PROFILE_ID_PUBLISHERTAG = 185; -const storage = getStorageManager(GVLID); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); const LOG_PREFIX = 'Criteo: '; /* @@ -24,7 +24,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 = 113; +export const FAST_BID_VERSION_CURRENT = 117; 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'; @@ -281,6 +281,7 @@ function checkNativeSendId(bidRequest) { */ function buildCdbRequest(context, bidRequests, bidderRequest) { let networkId; + let schain; const request = { publisher: { url: context.url, @@ -288,6 +289,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { }, slots: bidRequests.map(bidRequest => { networkId = bidRequest.params.networkId || networkId; + schain = bidRequest.schain || schain; const slot = { impid: bidRequest.adUnitCode, transactionid: bidRequest.transactionId, @@ -310,9 +312,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (!checkNativeSendId(bidRequest)) { logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); } - slot.sizes = parseSizes(retrieveBannerSizes(bidRequest), parseNativeSize); + slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseNativeSize); } else { - slot.sizes = parseSizes(retrieveBannerSizes(bidRequest), parseSize); + slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseSize); } if (hasVideoMediaType(bidRequest)) { const video = { @@ -344,6 +346,13 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (networkId) { request.publisher.networkid = networkId; } + if (schain) { + request.source = { + ext: { + schain: schain + } + } + }; request.user = { ext: bidderRequest.userExt }; @@ -366,11 +375,10 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { return request; } -function retrieveBannerSizes(bidRequest) { - return deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; -} - function parseSizes(sizes, parser) { + if (sizes == undefined) { + return []; + } if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) return sizes.map(size => parser(size)); } diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index c716a3c9cd6..c1a4c4398f4 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -13,7 +13,7 @@ import { getStorageManager } from '../src/storageManager.js'; const gvlid = 91; const bidderCode = 'criteo'; -export const storage = getStorageManager(gvlid, bidderCode); +export const storage = getStorageManager({gvlid: gvlid, moduleName: bidderCode}); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; @@ -33,15 +33,37 @@ function getFromAllStorages(key) { return storage.getCookie(key) || storage.getDataFromLocalStorage(key); } -function saveOnAllStorages(key, value) { +function saveOnAllStorages(key, value, hostname) { if (key && value) { - storage.setCookie(key, value, expirationString); storage.setDataInLocalStorage(key, value); + setCookieOnAllDomains(key, value, expirationString, hostname, true); } } -function deleteFromAllStorages(key) { - storage.setCookie(key, '', pastDateString); +function setCookieOnAllDomains(key, value, expiration, hostname, stopOnSuccess) { + const subDomains = hostname.split('.'); + for (let i = 0; i < subDomains.length; ++i) { + // Try to write the cookie on this subdomain (we want it to be stored only on the TLD+1) + const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.'); + + try { + storage.setCookie(key, value, expiration, null, '.' + domain); + + if (stopOnSuccess) { + // Try to read the cookie to check if we wrote it + const ck = storage.getCookie(key); + if (ck && ck === value) { + break; + } + } + } catch (error) { + + } + } +} + +function deleteFromAllStorages(key, hostname) { + setCookieOnAllDomains(key, '', pastDateString, hostname, true); storage.removeDataFromLocalStorage(key); } @@ -89,7 +111,16 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) { const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl; urlsToCall.forEach(url => triggerPixel(url)); } else if (jsonResponse.bundle) { - saveOnAllStorages(bundleStorageKey, jsonResponse.bundle); + saveOnAllStorages(bundleStorageKey, jsonResponse.bundle, domain); + } + + if (jsonResponse.bidId) { + saveOnAllStorages(bididStorageKey, jsonResponse.bidId, domain); + const criteoId = { criteoId: jsonResponse.bidId }; + callback(criteoId); + } else { + deleteFromAllStorages(bididStorageKey, domain); + callback(); } if (jsonResponse.bidId) { diff --git a/modules/currency.js b/modules/currency.js index dc77ee21430..a59a9880af1 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -1,7 +1,7 @@ import { logInfo, logWarn, logError, logMessage } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { createBid } from '../src/bidfactory.js'; -import { STATUS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; @@ -20,6 +20,25 @@ export var currencyRates = {}; var bidderCurrencyDefault = {}; var defaultRates; +export const ready = (() => { + let isDone, resolver, promise; + function reset() { + isDone = false; + resolver = null; + promise = new Promise((resolve) => { + resolver = resolve; + if (isDone) resolve(); + }) + } + function done() { + isDone = true; + if (resolver != null) { resolver() } + } + reset(); + + return {done, reset, promise: () => promise} +})(); + /** * Configuration function for currency * @param {string} [config.adServerCurrency = 'USD'] @@ -138,11 +157,15 @@ function initCurrency(url) { logInfo('currencyRates set to ' + JSON.stringify(currencyRates)); currencyRatesLoaded = true; processBidResponseQueue(); + ready.done(); } catch (e) { errorSettingsRates('Failed to parse currencyRates response: ' + response); } }, - error: errorSettingsRates + error: function (...args) { + errorSettingsRates(...args); + ready.done(); + } } ); } @@ -197,6 +220,8 @@ export function addBidResponseHook(fn, adUnitCode, bid) { bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid])); if (!currencySupportEnabled || currencyRatesLoaded) { processBidResponseQueue(); + } else { + fn.bail(ready.promise()); } } @@ -219,10 +244,7 @@ function wrapFunction(fn, context, params) { } } catch (e) { logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e); - params[1] = createBid(STATUS.NO_BID, { - bidder: bid.bidderCode || bid.bidder, - bidId: bid.requestId - }); + params[1] = createBid(CONSTANTS.STATUS.NO_BID, bid.getIdentifiers()); } } return fn.apply(context, params); diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index 9e082dffbf4..c0a24b49a3c 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -1,22 +1,22 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {getStorageManager} from '../src/storageManager.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import { OUTSTREAM } from '../src/video.js'; +import {OUTSTREAM} from '../src/video.js'; import { - isArray, - isNumber, - generateUUID, - parseSizesInput, deepAccess, + generateUUID, + getBidIdParameter, getParameterByName, getValue, - getBidIdParameter, + isArray, + isNumber, logError, logWarn, + parseSizesInput, } from '../src/utils.js'; -import { Renderer } from '../src/Renderer.js'; -import find from 'core-js-pure/features/array/find.js'; +import {Renderer} from '../src/Renderer.js'; +import {find} from '../src/polyfill.js'; // ------------------------------------ const BIDDER_CODE = 'cwire'; @@ -26,8 +26,9 @@ export const RENDERER_URL = 'https://cdn.cwi.re/prebid/renderer/LATEST/renderer. export const CW_PAGE_VIEW_ID = generateUUID(); const LS_CWID_KEY = 'cw_cwid'; const CW_GROUPS_QUERY = 'cwgroups'; +const CW_CREATIVE_QUERY = 'cwcreative'; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * ------------------------------------ @@ -90,14 +91,17 @@ 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'); + // get the pacement and page ids let placementId = getValue(bid.params, 'placementId'); let pageId = getValue(bid.params, 'pageId'); - let adUnitElementId = getValue(bid.params, 'adUnitElementId'); // get the rest of the auction/bid/transaction info bidObj.auctionId = getBidIdParameter('auctionId', bid); bidObj.adUnitCode = getBidIdParameter('adUnitCode', bid); - bidObj.adUnitElementId = adUnitElementId; bidObj.bidId = getBidIdParameter('bidId', bid); bidObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); bidObj.placementId = placementId; @@ -105,6 +109,9 @@ export const mapSlotsData = function(validBidRequests) { 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); }); @@ -123,11 +130,6 @@ export const spec = { isBidRequestValid: function(bid) { bid.params = bid.params || {}; - // if ad unit elemt id not provided - use adUnitCode by default - if (!bid.params.adUnitElementId) { - bid.params.adUnitElementId = bid.code; - } - if (!bid.params.placementId || !isNumber(bid.params.placementId)) { logError('placementId not provided or invalid'); return false; @@ -141,6 +143,21 @@ export const spec = { 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 @@ -161,8 +178,19 @@ export const spec = { let refgroups = []; + const cwCreativeId = parseInt(getQueryVariable(CW_CREATIVE_QUERY), 10) || 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(','); } @@ -171,7 +199,9 @@ export const spec = { const payload = { cwid: localStorageCWID, refgroups, + cwcreative: cwCreativeId || cwCreativeIdFromConfig, slots: slots, + cwapikey: cwApiKeyFromConfig, httpRef: referer || '', pageViewId: CW_PAGE_VIEW_ID, }; diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md index fc0889e05ad..b42c7a02489 100644 --- a/modules/cwireBidAdapter.md +++ b/modules/cwireBidAdapter.md @@ -2,7 +2,7 @@ Module Name: C-WIRE Bid Adapter Module Type: Adagio Adapter -Maintainer: dragan@cwire.ch +Maintainer: publishers@cwire.ch ## Description @@ -17,7 +17,10 @@ Below, the list of C-WIRE params and where they can be set. | ---------- | ------------- | ------------- | ---- | ---------| | pageId | | x | number | YES | | placementId | | x | number | YES | -| adUnitElementId | | x | string | NO | +| refgroups | | x | string | NO | +| cwcreative | | x | integer | NO | +| cwapikey | | x | string | NO | + ### adUnit configuration @@ -35,9 +38,11 @@ var adUnits = [ params: { pageId: 1422, // required - number placementId: 2211521, // required - number - adUnitElementId: 'other_div', // optional, div id to write to, if not set it will default to ad unit code + 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 } }] } ]; -``` \ No newline at end of file +``` diff --git a/modules/dacIdSystem.js b/modules/dacIdSystem.js new file mode 100644 index 00000000000..73b5c7420cf --- /dev/null +++ b/modules/dacIdSystem.js @@ -0,0 +1,57 @@ +/** + * This module adds dacId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/dacIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +export const cookieKey = '_a1_f'; + +export const dacIdSystemSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'dacId', + + /** + * performs action to obtain id + * @function + * @returns { {id: {dacId: string}} | undefined } + */ + getId: function() { + const newId = storage.getCookie(cookieKey); + if (!newId) { + return undefined; + } + const result = { + dacId: newId + } + return {id: result}; + }, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param { {dacId: string} } value + * @returns { {dacId: {id: string} } | undefined } + */ + decode: function(value) { + if (value && typeof value === 'object') { + const result = {}; + if (value.dacId) { + result.id = value.dacId + } + return {dacId: result}; + } + return undefined; + }, + +} + +submodule('userId', dacIdSystemSubmodule); diff --git a/modules/dacIdSystem.md b/modules/dacIdSystem.md new file mode 100644 index 00000000000..b422d0a536d --- /dev/null +++ b/modules/dacIdSystem.md @@ -0,0 +1,28 @@ +## DAC User ID Submodule + +DAC 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. + +## Building Prebid with DAC ID Support + +First, make sure to add the DAC ID submodule to your Prebid.js package with: + +``` +gulp build --modules=dacIdSystem +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'dacId' + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"dacId"` | diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js new file mode 100644 index 00000000000..ffa84ff88fd --- /dev/null +++ b/modules/dailyhuntBidAdapter.js @@ -0,0 +1,435 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as mediaTypes from '../src/mediaTypes.js'; +import {_map, deepAccess, isEmpty} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import {find} from '../src/polyfill.js'; +import {INSTREAM, OUTSTREAM} from '../src/video.js'; + +const BIDDER_CODE = 'dailyhunt'; +const BIDDER_ALIAS = 'dh'; +const SUPPORTED_MEDIA_TYPES = [mediaTypes.BANNER, mediaTypes.NATIVE, mediaTypes.VIDEO]; + +const PROD_PREBID_ENDPOINT_URL = 'https://pbs.dailyhunt.in/openrtb2/auction?partner='; +const PROD_PREBID_TEST_ENDPOINT_URL = 'https://qa-pbs-van.dailyhunt.in/openrtb2/auction?partner='; + +const ORTB_NATIVE_TYPE_MAPPING = { + img: { + '3': 'image', + '1': 'icon' + }, + data: { + '1': 'sponsoredBy', + '2': 'body', + '3': 'rating', + '4': 'likes', + '5': 'downloads', + '6': 'price', + '7': 'salePrice', + '8': 'phone', + '9': 'address', + '10': 'body2', + '11': 'displayUrl', + '12': 'cta' + } +} + +const ORTB_NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + icon: { + id: 1, + type: 1, + name: 'img' + }, + image: { + id: 2, + type: 3, + name: 'img' + }, + sponsoredBy: { + id: 3, + name: 'data', + type: 1 + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + cta: { + id: 5, + type: 12, + name: 'data' + }, + body2: { + id: 4, + name: 'data', + type: 10 + }, +}; + +// Encode URI. +const _encodeURIComponent = function (a) { + let b = window.encodeURIComponent(a); + b = b.replace(/'/g, '%27'); + return b; +} + +// Extract key from collections. +const extractKeyInfo = (collection, key) => { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i].params, key); + if (result) { + return result; + } + } + return undefined +} + +// Flattern Array. +const flatten = (arr) => { + return [].concat(...arr); +} + +const createOrtbRequest = (validBidRequests, bidderRequest) => { + let device = createOrtbDeviceObj(validBidRequests); + let user = createOrtbUserObj(validBidRequests) + let site = createOrtbSiteObj(validBidRequests, bidderRequest.refererInfo.referer) + return { + id: bidderRequest.auctionId, + imp: [], + site, + device, + user, + }; +} + +const createOrtbDeviceObj = (validBidRequests) => { + let device = { ...extractKeyInfo(validBidRequests, `device`) }; + device.ua = navigator.userAgent; + return device; +} + +const createOrtbUserObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `user`) }) + +const createOrtbSiteObj = (validBidRequests, page) => { + let site = { ...extractKeyInfo(validBidRequests, `site`), page }; + let publisher = createOrtbPublisherObj(validBidRequests); + if (!site.publisher) { + site.publisher = publisher + } + return site +} + +const createOrtbPublisherObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `publisher`) }) + +// get bidFloor Function for different creatives +function getBidFloor(bid, creative) { + let floorInfo = typeof (bid.getFloor) == 'function' ? bid.getFloor({ currency: 'USD', mediaType: creative, size: '*' }) : {}; + return Math.floor(floorInfo.floor || (bid.params.bidfloor ? bid.params.bidfloor : 0.0)); +} + +const createOrtbImpObj = (bid) => { + let params = bid.params + let testMode = !!bid.params.test_mode + + // Validate Banner Request. + let bannerObj = deepAccess(bid.mediaTypes, `banner`); + let nativeObj = deepAccess(bid.mediaTypes, `native`); + let videoObj = deepAccess(bid.mediaTypes, `video`); + + let imp = { + id: bid.bidId, + ext: { + dailyhunt: { + placement_id: params.placement_id, + publisher_id: params.publisher_id, + partner: params.partner_name + } + } + }; + + // Test Mode Campaign. + if (testMode) { + imp.ext.test_mode = testMode; + } + + if (bannerObj) { + imp.banner = { + ...createOrtbImpBannerObj(bid, bannerObj) + } + imp.bidfloor = getBidFloor(bid, 'banner'); + } else if (nativeObj) { + imp.native = { + ...createOrtbImpNativeObj(bid, nativeObj) + } + imp.bidfloor = getBidFloor(bid, 'native'); + } else if (videoObj) { + imp.video = { + ...createOrtbImpVideoObj(bid, videoObj) + } + imp.bidfloor = getBidFloor(bid, 'video'); + } + return imp; +} + +const createOrtbImpBannerObj = (bid, bannerObj) => { + let format = []; + bannerObj.sizes.forEach(size => format.push({ w: size[0], h: size[1] })) + + return { + id: 'banner-' + bid.bidId, + format + } +} + +const createOrtbImpNativeObj = (bid, nativeObj) => { + const assets = _map(bid.nativeParams, (bidParams, key) => { + const props = ORTB_NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + let h = 0; + let w = 0; + + asset.id = props.id; + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } + + asset[props.name] = { + len: bidParams.len ? bidParams.len : 20, + type: props.type, + w, + h + }; + + return asset; + } + }).filter(Boolean); + let request = { + assets, + ver: '1,0' + } + return { request: JSON.stringify(request) }; +} + +const createOrtbImpVideoObj = (bid, videoObj) => { + let obj = {}; + let params = bid.params + if (!isEmpty(bid.params.video)) { + obj = { + topframe: 1, + skip: params.video.skippable || 0, + linearity: params.video.linearity || 1, + minduration: params.video.minduration || 5, + maxduration: params.video.maxduration || 60, + mimes: params.video.mimes || ['video/mp4'], + protocols: getProtocols(params.video), + w: params.video.playerSize[0][0], + h: params.video.playerSize[0][1], + }; + } else { + obj = { + mimes: ['video/mp4'], + }; + } + obj.ext = { + ...videoObj, + } + return obj; +} + +export function getProtocols({protocols}) { + let defaultValue = [2, 3, 5, 6, 7, 8]; + let listProtocols = [ + {key: 'VAST_1_0', value: 1}, + {key: 'VAST_2_0', value: 2}, + {key: 'VAST_3_0', value: 3}, + {key: 'VAST_1_0_WRAPPER', value: 4}, + {key: 'VAST_2_0_WRAPPER', value: 5}, + {key: 'VAST_3_0_WRAPPER', value: 6}, + {key: 'VAST_4_0', value: 7}, + {key: 'VAST_4_0_WRAPPER', value: 8} + ]; + if (protocols) { + return listProtocols.filter(p => { + return protocols.indexOf(p.key) !== -1 + }).map(p => p.value); + } else { + return defaultValue; + } +} + +const createServerRequest = (ortbRequest, validBidRequests, isTestMode = 'false') => ({ + method: 'POST', + url: isTestMode === 'true' ? PROD_PREBID_TEST_ENDPOINT_URL + validBidRequests[0].params.partner_name : PROD_PREBID_ENDPOINT_URL + validBidRequests[0].params.partner_name, + data: JSON.stringify(ortbRequest), + options: { + contentType: 'application/json', + withCredentials: true + }, + bids: validBidRequests +}) + +const createPrebidBannerBid = (bid, bidResponse) => ({ + requestId: bid.bidId, + cpm: bidResponse.price.toFixed(2), + creativeId: bidResponse.crid, + width: bidResponse.w, + height: bidResponse.h, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: 'USD', + ad: bidResponse.adm, + mediaType: 'banner', + winUrl: bidResponse.nurl, + adomain: bidResponse.adomain +}) + +const createPrebidNativeBid = (bid, bidResponse) => ({ + requestId: bid.bidId, + cpm: bidResponse.price.toFixed(2), + creativeId: bidResponse.crid, + currency: 'USD', + ttl: 360, + netRevenue: bid.netRevenue === 'net', + native: parseNative(bidResponse), + mediaType: 'native', + winUrl: bidResponse.nurl, + width: bidResponse.w, + height: bidResponse.h, + adomain: bidResponse.adomain +}) + +const parseNative = (bid) => { + let adm = JSON.parse(bid.adm) + const { assets, link, imptrackers, jstracker } = adm.native; + const result = { + clickUrl: _encodeURIComponent(link.url), + clickTrackers: link.clicktrackers || [], + impressionTrackers: imptrackers || [], + javascriptTrackers: jstracker ? [ jstracker ] : [] + }; + assets.forEach(asset => { + if (!isEmpty(asset.title)) { + result.title = asset.title.text + } else if (!isEmpty(asset.img)) { + result[ORTB_NATIVE_TYPE_MAPPING.img[asset.img.type]] = { + url: asset.img.url, + height: asset.img.h, + width: asset.img.w + } + } else if (!isEmpty(asset.data)) { + result[ORTB_NATIVE_TYPE_MAPPING.data[asset.data.type]] = asset.data.value + } + }); + + return result; +} + +const createPrebidVideoBid = (bid, bidResponse) => { + let videoBid = { + requestId: bid.bidId, + cpm: bidResponse.price.toFixed(2), + creativeId: bidResponse.crid, + width: bidResponse.w, + height: bidResponse.h, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: 'USD', + mediaType: 'video', + winUrl: bidResponse.nurl, + adomain: bidResponse.adomain + }; + + let videoContext = bid.mediaTypes.video.context; + switch (videoContext) { + case OUTSTREAM: + videoBid.vastXml = bidResponse.adm; + break; + case INSTREAM: + videoBid.videoCacheKey = bidResponse.ext.bidder.cacheKey; + videoBid.vastUrl = bidResponse.ext.bidder.vastUrl; + break; + } + return videoBid; +} + +const getQueryVariable = (variable) => { + let query = window.location.search.substring(1); + let vars = query.split('&'); + for (var i = 0; i < vars.length; i++) { + let pair = vars[i].split('='); + if (decodeURIComponent(pair[0]) == variable) { + return decodeURIComponent(pair[1]); + } + } + return false; +} + +export const spec = { + code: BIDDER_CODE, + + aliases: [BIDDER_ALIAS], + + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + isBidRequestValid: bid => !!bid.params.placement_id && !!bid.params.publisher_id && !!bid.params.partner_name, + + buildRequests: function (validBidRequests, bidderRequest) { + let serverRequests = []; + + // ORTB Request. + let ortbReq = createOrtbRequest(validBidRequests, bidderRequest); + + validBidRequests.forEach((bid) => { + let imp = createOrtbImpObj(bid) + ortbReq.imp.push(imp); + }); + + serverRequests.push({ ...createServerRequest(ortbReq, validBidRequests, getQueryVariable('dh_test')) }); + + return serverRequests; + }, + + interpretResponse: function (serverResponse, request) { + const { seatbid } = serverResponse.body; + let bids = request.bids; + let prebidResponse = []; + + let seatBids = seatbid[0].bid; + + seatBids.forEach(ortbResponseBid => { + let bidId = ortbResponseBid.impid; + let actualBid = find(bids, (bid) => bid.bidId === bidId); + let bidMediaType = ortbResponseBid.ext.prebid.type + switch (bidMediaType) { + case mediaTypes.BANNER: + prebidResponse.push(createPrebidBannerBid(actualBid, ortbResponseBid)); + break; + case mediaTypes.NATIVE: + prebidResponse.push(createPrebidNativeBid(actualBid, ortbResponseBid)); + break; + case mediaTypes.VIDEO: + prebidResponse.push(createPrebidVideoBid(actualBid, ortbResponseBid)); + break; + } + }) + return prebidResponse; + }, + + onBidWon: function(bid) { + ajax(bid.winUrl, null, null, { + method: 'GET' + }) + } +} + +registerBidder(spec); diff --git a/modules/dailyhuntBidAdapter.md b/modules/dailyhuntBidAdapter.md index acfd20a4de0..a08b66fb826 100644 --- a/modules/dailyhuntBidAdapter.md +++ b/modules/dailyhuntBidAdapter.md @@ -99,3 +99,7 @@ Dailyhunt bid adapter supports Banner, Native and Video. } ]; ``` + +## latest commit has all the required support for latest version of prebid above 6.x +## Dailyhunt adapter was there till 4.x and then removed in version 5.x of prebid. +## this doc has been also submitted to https://github.com/prebid/prebid.github.io \ No newline at end of file diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 197ba19b1d6..b240db1dd25 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -4,7 +4,7 @@ import { config } from '../src/config.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { ajax } from '../src/ajax.js'; -export const storage = getStorageManager(); +export const storage = getStorageManager({bidderCode: 'datablocks'}); const NATIVE_ID_MAP = {}; const NATIVE_PARAMS = { @@ -94,7 +94,7 @@ export const spec = { code: 'datablocks', // DATABLOCKS SCOPED OBJECT - db_obj: {metrics_host: 'prebid.datablocks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0}, + db_obj: {metrics_host: 'prebid.dblks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0}, // STORE THE DATABLOCKS BUYERID IN STORAGE store_dbid: function(dbid) { @@ -388,12 +388,12 @@ export const spec = { }; let sourceId = validRequests[0].params.source_id || 0; - let host = validRequests[0].params.host || 'prebid.datablocks.net'; + let host = validRequests[0].params.host || 'prebid.dblks.net'; // RETURN WITH THE REQUEST AND PAYLOAD return { method: 'POST', - url: `https://${sourceId}.${host}/openrtb/?sid=${sourceId}`, + url: `https://${host}/openrtb/?sid=${sourceId}`, data: { id: bidderRequest.auctionId, imp: imps, diff --git a/modules/dchain.js b/modules/dchain.js new file mode 100644 index 00000000000..fbe78fc5c86 --- /dev/null +++ b/modules/dchain.js @@ -0,0 +1,149 @@ +import {includes} from '../src/polyfill.js'; +import {config} from '../src/config.js'; +import {getHook} from '../src/hook.js'; +import {_each, deepAccess, deepClone, hasOwn, isArray, isPlainObject, isStr, logError, logWarn} from '../src/utils.js'; + +const shouldBeAString = ' should be a string'; +const shouldBeAnObject = ' should be an object'; +const shouldBeAnArray = ' should be an Array'; +const shouldBeValid = ' is not a valid dchain property'; +const MODE = { + STRICT: 'strict', + RELAXED: 'relaxed', + OFF: 'off' +}; +const MODES = []; // an array of modes +_each(MODE, mode => MODES.push(mode)); + +export function checkDchainSyntax(bid, mode) { + let dchainObj = deepClone(bid.meta.dchain); + let failPrefix = 'Detected something wrong in bid.meta.dchain object for bid:'; + let failMsg = ''; + const dchainPropList = ['ver', 'complete', 'nodes', 'ext']; + + function appendFailMsg(msg) { + failMsg += '\n' + msg; + } + + function printFailMsg() { + if (mode === MODE.STRICT) { + logError(failPrefix, bid, '\n', dchainObj, failMsg); + } else { + logWarn(failPrefix, bid, `\n`, dchainObj, failMsg); + } + } + + let dchainProps = Object.keys(dchainObj); + dchainProps.forEach(prop => { + if (!includes(dchainPropList, prop)) { + appendFailMsg(`dchain.${prop}` + shouldBeValid); + } + }); + + if (dchainObj.complete !== 0 && dchainObj.complete !== 1) { + appendFailMsg(`dchain.complete should be 0 or 1`); + } + + if (!isStr(dchainObj.ver)) { + appendFailMsg(`dchain.ver` + shouldBeAString); + } + + if (hasOwn(dchainObj, 'ext')) { + if (!isPlainObject(dchainObj.ext)) { + appendFailMsg(`dchain.ext` + shouldBeAnObject); + } + } + + if (!isArray(dchainObj.nodes)) { + appendFailMsg(`dchain.nodes` + shouldBeAnArray); + printFailMsg(); + if (mode === MODE.STRICT) return false; + } else { + const nodesPropList = ['asi', 'bsid', 'rid', 'name', 'domain', 'ext']; + dchainObj.nodes.forEach((node, index) => { + if (!isPlainObject(node)) { + appendFailMsg(`dchain.nodes[${index}]` + shouldBeAnObject); + } else { + let nodeProps = Object.keys(node); + nodeProps.forEach(prop => { + if (!includes(nodesPropList, prop)) { + appendFailMsg(`dchain.nodes[${index}].${prop}` + shouldBeValid); + } + + if (prop === 'ext') { + if (!isPlainObject(node.ext)) { + appendFailMsg(`dchain.nodes[${index}].ext` + shouldBeAnObject); + } + } else { + if (!isStr(node[prop])) { + appendFailMsg(`dchain.nodes[${index}].${prop}` + shouldBeAString); + } + } + }); + } + }); + } + + if (failMsg.length > 0) { + printFailMsg(); + if (mode === MODE.STRICT) { + return false; + } + } + return true; +} + +function isValidDchain(bid) { + let mode = MODE.STRICT; + const dchainConfig = config.getConfig('dchain'); + + if (dchainConfig && isStr(dchainConfig.validation) && MODES.indexOf(dchainConfig.validation) != -1) { + mode = dchainConfig.validation; + } + + if (mode === MODE.OFF) { + return true; + } else { + return checkDchainSyntax(bid, mode); + } +} + +export function addBidResponseHook(fn, adUnitCode, bid) { + const basicDchain = { + ver: '1.0', + complete: 0, + nodes: [] + }; + + if (deepAccess(bid, 'meta.networkId') && deepAccess(bid, 'meta.networkName')) { + basicDchain.nodes.push({ name: bid.meta.networkName, bsid: bid.meta.networkId.toString() }); + } + basicDchain.nodes.push({ name: bid.bidderCode }); + + let bidDchain = deepAccess(bid, 'meta.dchain'); + if (bidDchain && isPlainObject(bidDchain)) { + let result = isValidDchain(bid); + + if (result) { + // extra check in-case mode is OFF and there is a setup issue + if (isArray(bidDchain.nodes)) { + bid.meta.dchain.nodes.push({ asi: bid.bidderCode }); + } else { + logWarn('bid.meta.dchain.nodes did not exist or was not an array; did not append prebid dchain.', bid); + } + } else { + // remove invalid dchain + delete bid.meta.dchain; + } + } else { + bid.meta.dchain = basicDchain; + } + + fn(adUnitCode, bid); +} + +export function init() { + getHook('addBidResponse').before(addBidResponseHook, 35); +} + +init(); diff --git a/modules/dchain.md b/modules/dchain.md new file mode 100644 index 00000000000..f01b3483f3c --- /dev/null +++ b/modules/dchain.md @@ -0,0 +1,45 @@ +# dchain module + +Refer: +- https://iabtechlab.com/buyers-json-demand-chain/ + +## Sample code for dchain setConfig and dchain object +``` +pbjs.setConfig({ + "dchain": { + "validation": "strict" + } +}); +``` + +``` +bid.meta.dchain: { + "complete": 0, + "ver": "1.0", + "ext": {...}, + "nodes": [{ + "asi": "abc", + "bsid": "123", + "rid": "d4e5f6", + "name": "xyz", + "domain": "mno", + "ext": {...} + }, ...] +} +``` + +## Workflow +The dchain module is not enabled by default as it may not be necessary for all publishers. +If required, dchain module can be included as following +``` + $ gulp build --modules=dchain,pubmaticBidAdapter,openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter +``` + +The dchain module will validate a bidder's dchain object (if it was defined). Bidders should assign their dchain object into `bid.meta` field. If the dchain object is valid, it will remain in the bid object for later use. + +If it was not defined, the dchain will create a default dchain object for prebid. + +## Validation modes +- ```strict```: It is the default validation mode. In this mode, dchain object will not be accpeted from adapters if it is invalid. Errors are thrown for invalid dchain object. +- ```relaxed```: In this mode, errors are thrown for an invalid dchain object but the invalid dchain object is still accepted from adapters. +- ```off```: In this mode, no validations are performed and dchain object is accepted as is from adapters. \ No newline at end of file diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js new file mode 100644 index 00000000000..2a179641424 --- /dev/null +++ b/modules/debugging/bidInterceptor.js @@ -0,0 +1,229 @@ +import { + deepAccess, + deepClone, + deepEqual, + delayExecution, + prefixLog, + mergeDeep +} from '../../src/utils.js'; +const { logMessage, logWarn, logError } = prefixLog('DEBUG:'); + +/** + * @typedef {Number|String|boolean|null|undefined} Scalar + */ + +export function BidInterceptor(opts = {}) { + ({setTimeout: this.setTimeout = window.setTimeout.bind(window)} = opts); + this.rules = []; +} + +Object.assign(BidInterceptor.prototype, { + DEFAULT_RULE_OPTIONS: { + delay: 0 + }, + serializeConfig(ruleDefs) { + function isSerializable(ruleDef, i) { + const serializable = deepEqual(ruleDef, JSON.parse(JSON.stringify(ruleDef)), {checkTypes: true}); + if (!serializable && !deepAccess(ruleDef, 'options.suppressWarnings')) { + logWarn(`Bid interceptor rule definition #${i + 1} is not serializable and will be lost after a refresh. Rule definition: `, ruleDef); + } + return serializable; + } + return ruleDefs.filter(isSerializable); + }, + updateConfig(config) { + this.rules = (config.intercept || []).map((ruleDef, i) => this.rule(ruleDef, i + 1)) + }, + /** + * @typedef {Object} RuleOptions + * @property {Number} [delay=0] delay between bid interception and mocking of response (to simulate network delay) + * @property {boolean} [suppressWarnings=false] if enabled, do not warn about unserializable rules + * + * @typedef {Object} Rule + * @property {Number} no rule number (used only as an identifier for logging) + * @property {function({}, {}): boolean} match a predicate function that tests a bid against this rule + * @property {ReplacerFn} replacer generator function for mock bid responses + * @property {RuleOptions} options + */ + + /** + * @param {{}} ruleDef + * @param {Number} ruleNo + * @returns {Rule} + */ + rule(ruleDef, ruleNo) { + return { + no: ruleNo, + match: this.matcher(ruleDef.when, ruleNo), + replace: this.replacer(ruleDef.then || {}, ruleNo), + options: Object.assign({}, this.DEFAULT_RULE_OPTIONS, ruleDef.options), + } + }, + /** + * @typedef {Function} MatchPredicate + * @param {*} candidate a bid to match, or a portion of it if used inside an ObjectMather. + * e.g. matcher((bid, bidRequest) => ....) or matcher({property: (property, bidRequest) => ...}) + * @param {BidRequest} bidRequest the request `candidate` belongs to + * @returns {boolean} + * + * @typedef {{[key]: Scalar|RegExp|MatchPredicate|ObjectMatcher}} ObjectMatcher + */ + + /** + * @param {MatchPredicate|ObjectMatcher} matchDef matcher definition + * @param {Number} ruleNo + * @returns {MatchPredicate} a predicate function that matches a bid against the given `matchDef` + */ + matcher(matchDef, ruleNo) { + if (typeof matchDef === 'function') { + return matchDef; + } + if (typeof matchDef !== 'object') { + logError(`Invalid 'when' definition for debug bid interceptor (in rule #${ruleNo})`); + return () => false; + } + function matches(candidate, {ref = matchDef, args = []}) { + return Object.entries(ref).map(([key, val]) => { + const cVal = candidate[key]; + if (val instanceof RegExp) { + return val.exec(cVal) != null; + } + if (typeof val === 'function') { + return !!val(cVal, ...args); + } + if (typeof val === 'object') { + return matches(cVal, {ref: val, args}); + } + return cVal === val; + }).every((i) => i); + } + return (candidate, ...args) => matches(candidate, {args}); + }, + /** + * @typedef {Function} ReplacerFn + * @param {*} bid a bid that was intercepted + * @param {BidRequest} bidRequest the request `bid` belongs to + * @returns {*} the response to mock for `bid`, or a portion of it if used inside an ObjectReplacer. + * e.g. replacer((bid, bidRequest) => mockResponse) or replacer({property: (bid, bidRequest) => mockProperty}) + * + * @typedef {{[key]: ReplacerFn|ObjectReplacer|*}} ObjectReplacer + */ + + /** + * @param {ReplacerFn|ObjectReplacer} replDef replacer definition + * @param ruleNo + * @return {ReplacerFn} + */ + replacer(replDef, ruleNo) { + let replFn; + if (typeof replDef === 'function') { + replFn = ({args}) => replDef(...args); + } else if (typeof replDef !== 'object') { + logError(`Invalid 'then' definition for debug bid interceptor (in rule #${ruleNo})`); + replFn = () => ({}); + } else { + replFn = ({args, ref = replDef}) => { + const result = {}; + Object.entries(ref).forEach(([key, val]) => { + if (typeof val === 'function') { + result[key] = val(...args); + } else if (typeof val === 'object') { + result[key] = replFn({args, ref: val}) + } else { + result[key] = val; + } + }); + return result; + } + } + return (bid, ...args) => { + const response = this.responseDefaults(bid); + mergeDeep(response, replFn({args: [bid, ...args]})); + if (!response.ad) { + response.ad = this.defaultAd(bid, response); + } + response.isDebug = true; + return response; + } + }, + responseDefaults(bid) { + return { + requestId: bid.bidId, + cpm: 3.5764, + currency: 'EUR', + width: 300, + height: 250, + ttl: 360, + creativeId: 'mock-creative-id', + netRevenue: false, + meta: {} + }; + }, + defaultAd(bid, bidResponse) { + return ``; + }, + /** + * Match a candidate bid against all registered rules. + * + * @param {{}} candidate + * @param args + * @returns {Rule|undefined} the first matching rule, or undefined if no match was found. + */ + match(candidate, ...args) { + return this.rules.find((rule) => rule.match(candidate, ...args)); + }, + /** + * Match a set of bids against all registered rules. + * + * @param bids + * @param bidRequest + * @returns {[{bid: *, rule: Rule}[], *[]]} a 2-tuple for matching bids (decorated with the matching rule) and + * non-matching bids. + */ + matchAll(bids, bidRequest) { + const [matches, remainder] = [[], []]; + bids.forEach((bid) => { + const rule = this.match(bid, bidRequest); + if (rule != null) { + matches.push({rule: rule, bid: bid}); + } else { + remainder.push(bid); + } + }) + return [matches, remainder]; + }, + /** + * Run a set of bids against all registered rules, filter out those that match, + * and generate mock responses for them. + * + * @param {{}[]} bids? + * @param {BidRequest} bidRequest + * @param {function(*)} addBid called once for each mock response + * @param {function()} done called once after all mock responses have been run through `addBid` + * @returns {{bids: {}[], bidRequest: {}} remaining bids that did not match any rule (this applies also to + * bidRequest.bids) + */ + intercept({bids, bidRequest, addBid, done}) { + if (bids == null) { + bids = bidRequest.bids; + } + const [matches, remainder] = this.matchAll(bids, bidRequest); + if (matches.length > 0) { + const callDone = delayExecution(done, matches.length); + matches.forEach((match) => { + const mockResponse = match.rule.replace(match.bid, bidRequest); + const delay = match.rule.options.delay; + logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse) + this.setTimeout(() => { + addBid(mockResponse, match.bid); + callDone(); + }, delay) + }); + bidRequest = deepClone(bidRequest); + bids = bidRequest.bids = remainder; + } else { + this.setTimeout(done, 0); + } + return {bids, bidRequest}; + } +}); diff --git a/modules/debugging/index.js b/modules/debugging/index.js new file mode 100644 index 00000000000..72692c3fc98 --- /dev/null +++ b/modules/debugging/index.js @@ -0,0 +1,62 @@ +import {deepClone, delayExecution} from '../../src/utils.js'; +import {processBidderRequests} from '../../src/adapters/bidderFactory.js'; +import {BidInterceptor} from './bidInterceptor.js'; +import {hook} from '../../src/hook.js'; +import {pbsBidInterceptor} from './pbsInterceptor.js'; +import { + onDisableOverrides, + onEnableOverrides, + saveDebuggingConfig +} from '../../src/debugging.js'; + +const interceptorHooks = []; +const bidInterceptor = new BidInterceptor(); + +saveDebuggingConfig.before(function (next, debugConfig, ...args) { + if (debugConfig.intercept) { + debugConfig = deepClone(debugConfig); + debugConfig.intercept = bidInterceptor.serializeConfig(debugConfig.intercept); + } + next(debugConfig, ...args); +}); + +function resetHooks(enable) { + interceptorHooks.forEach(([getHookFn, interceptor]) => { + getHookFn().getHooks({hook: interceptor}).remove(); + }); + if (enable) { + interceptorHooks.forEach(([getHookFn, interceptor]) => { + getHookFn().before(interceptor); + }) + } +} + +onEnableOverrides.push((overrides) => { + bidInterceptor.updateConfig(overrides); + resetHooks(true); +}); + +onDisableOverrides.push(() => { + bidInterceptor.updateConfig({}); + resetHooks(false); +}) + +function registerBidInterceptor(getHookFn, interceptor) { + const interceptBids = (...args) => bidInterceptor.intercept(...args); + interceptorHooks.push([getHookFn, function (next, ...args) { + interceptor(next, interceptBids, ...args) + }]); +} + +export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, cbs) { + const done = delayExecution(cbs.onCompletion, 2); + ({bids, bidRequest} = interceptBids({bids, bidRequest, addBid: cbs.onBid, done})); + if (bids.length === 0) { + done(); + } else { + next(spec, bids, bidRequest, ajax, wrapCallback, {...cbs, onCompletion: done}); + } +} + +registerBidInterceptor(() => processBidderRequests, bidderBidInterceptor); +registerBidInterceptor(() => hook.get('processPBSRequest'), pbsBidInterceptor); diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js new file mode 100644 index 00000000000..5af2384cad9 --- /dev/null +++ b/modules/debugging/pbsInterceptor.js @@ -0,0 +1,38 @@ +import {deepClone, delayExecution} from '../../src/utils.js'; +import {createBid} from '../../src/bidfactory.js'; +import * as CONSTANTS from '../../src/constants.json'; + +export function pbsBidInterceptor (next, interceptBids, s2sBidRequest, bidRequests, ajax, { + onResponse, + onError, + onBid +}) { + let responseArgs; + const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1) + function signalResponse(...args) { + responseArgs = args; + done(); + } + function addBid(bid, bidRequest) { + onBid({ + adUnit: bidRequest.adUnitCode, + bid: Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid) + }) + } + bidRequests = bidRequests + .map((req) => interceptBids({bidRequest: req, addBid, done}).bidRequest) + .filter((req) => req.bids.length > 0) + + if (bidRequests.length > 0) { + const bidIds = new Set(); + bidRequests.forEach((req) => req.bids.forEach((bid) => bidIds.add(bid.bidId))); + s2sBidRequest = deepClone(s2sBidRequest); + s2sBidRequest.ad_units.forEach((unit) => { + unit.bids = unit.bids.filter((bid) => bidIds.has(bid.bid_id)); + }) + s2sBidRequest.ad_units = s2sBidRequest.ad_units.filter((unit) => unit.bids.length > 0); + next(s2sBidRequest, bidRequests, ajax, {onResponse: signalResponse, onError, onBid}); + } else { + signalResponse(true, []); + } +} diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index d0c8eb29993..94167b92bb0 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -162,7 +162,7 @@ function buildImpression(bid) { impression = { id: bid.bidId, tagid: bid.params.tagId || '', - secure: window.location.protocol === 'https' ? 1 : 0, + secure: window.location.protocol === 'https:' ? 1 : 0, displaymanager: 'di_prebid', displaymanagerver: DI_M_V, ext: buildCustomParams(bid) diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js index 375c8c07ed1..43c7af1b3cc 100644 --- a/modules/deepintentDpesIdSystem.js +++ b/modules/deepintentDpesIdSystem.js @@ -9,7 +9,7 @@ import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; const MODULE_NAME = 'deepintentId'; -export const storage = getStorageManager(null, MODULE_NAME); +export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const deepintentDpesSubmodule = { diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 79cb03ec001..7f8ad3351fa 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -9,7 +9,7 @@ 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 events from '../src/events.js'; +import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; /** @@ -88,7 +88,14 @@ export function buildDfpVideoUrl(options) { sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'), url: encodeURIComponent(location.href), }; - const encodedCustomParams = getCustParams(bid, options); + + const urlSearchComponent = urlComponents.search; + const urlSzParam = urlSearchComponent && urlSearchComponent.sz + if (urlSzParam) { + derivedParams.sz = urlSzParam + '|' + derivedParams.sz; + } + + let encodedCustomParams = getCustParams(bid, options, urlSearchComponent && urlSearchComponent.cust_params); const queryParams = Object.assign({}, defaultParamConstants, @@ -111,12 +118,11 @@ export function buildDfpVideoUrl(options) { const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } - return buildUrl({ + return buildUrl(Object.assign({ protocol: 'https', host: 'securepubads.g.doubleclick.net', - pathname: '/gampad/ads', - search: queryParams - }); + pathname: '/gampad/ads' + }, urlComponents, { search: queryParams })); } export function notifyTranslationModule(fn) { @@ -227,9 +233,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) { const descriptionUrl = getDescriptionUrl(bid, components, 'search'); if (descriptionUrl) { components.search.description_url = descriptionUrl; } - const encodedCustomParams = getCustParams(bid, options); - components.search.cust_params = (components.search.cust_params) ? components.search.cust_params + '%26' + encodedCustomParams : encodedCustomParams; - + components.search.cust_params = getCustParams(bid, options, components.search.cust_params); return buildUrl(components); } @@ -258,7 +262,7 @@ function getDescriptionUrl(bid, components, prop) { * @param {Object} options this is the options passed in from the `buildDfpVideoUrl` function * @return {Object} Encoded key value pairs for cust_params */ -function getCustParams(bid, options) { +function getCustParams(bid, options, urlCustParams) { const adserverTargeting = (bid && bid.adserverTargeting) || {}; let allTargetingData = {}; @@ -281,7 +285,12 @@ function getCustParams(bid, options) { // merge the prebid + publisher targeting sets const publisherTargetingSet = deepAccess(options, 'params.cust_params'); const targetingSet = Object.assign({}, prebidTargetingSet, publisherTargetingSet); - return encodeURIComponent(formatQS(targetingSet)); + let encodedParams = encodeURIComponent(formatQS(targetingSet)); + if (urlCustParams) { + encodedParams = urlCustParams + '%26' + encodedParams; + } + + return encodedParams; } registerVideoSupport('dfp', { diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js new file mode 100644 index 00000000000..55a2f4a8604 --- /dev/null +++ b/modules/displayioBidAdapter.js @@ -0,0 +1,157 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; + +const BIDDER_VERSION = '1.0.0'; +const BIDDER_CODE = 'displayio'; +const GVLID = 999; +const BID_TTL = 300; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const DEFAULT_CURRENCY = 'USD'; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.placementId && bid.params.siteId && + bid.params.adsSrvDomain && bid.params.cdnDomain); + }, + buildRequests: function (bidRequests, bidderRequest) { + return bidRequests.map(bid => { + let url = '//' + bid.params.adsSrvDomain + '/srv?method=getPlacement&app=' + + bid.params.siteId + '&placement=' + bid.params.placementId; + const data = this._getPayload(bid, bidderRequest); + return { + method: 'POST', + headers: {'Content-Type': 'application/json;charset=utf-8'}, + url, + data + }; + }); + }, + interpretResponse: function (serverResponse, serverRequest) { + const ads = serverResponse.body.data.ads; + const bidResponses = []; + const { data } = serverRequest.data; + if (ads.length) { + const adData = ads[0].ad.data; + const bidResponse = { + requestId: data.id, + cpm: adData.ecpm, + width: adData.w, + height: adData.h, + netRevenue: true, + ttl: BID_TTL, + creativeId: adData.adId || 0, + currency: DEFAULT_CURRENCY, + referrer: data.data.ref, + mediaType: ads[0].ad.subtype, + ad: adData.markup, + placement: data.placement, + adData: adData + }; + if (bidResponse.vastUrl === 'videoVast') { + bidResponse.vastUrl = adData.videos[0].url + } + bidResponses.push(bidResponse); + } + return bidResponses; + }, + _getPayload: function (bid, bidderRequest) { + const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; + const userSession = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { + let r = Math.random() * 16 | 0; + let v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + const { params } = bid; + const { siteId, placementId } = params; + const { refererInfo, uspConsent, gdprConsent } = bidderRequest; + const mediation = {consent: '-1', gdpr: '-1'}; + if (gdprConsent) { + if (gdprConsent.consentString !== undefined) { + mediation.consent = gdprConsent.consentString; + } + if (gdprConsent.gdprApplies !== undefined) { + mediation.gdpr = gdprConsent.gdprApplies ? '1' : '0'; + } + } + const payload = { + userSession, + data: { + id: bid.bidId, + action: 'getPlacement', + app: siteId, + placement: placementId, + data: { + pagecat: params.pageCategory ? params.pageCategory.split(',').map(k => k.trim()) : [], + keywords: params.keywords ? params.keywords.split(',').map(k => k.trim()) : [], + lang_content: document.documentElement.lang, + lang: window.navigator.language, + domain: window.location.hostname, + page: window.location.href, + ref: refererInfo.referer, + userids: _getUserIDs(), + geo: '', + }, + complianceData: { + child: '-1', + us_privacy: uspConsent, + dnt: window.navigator.doNotTrack, + iabConsent: {}, + mediation: { + consent: mediation.consent, + gdpr: mediation.gdpr, + } + }, + integration: 'JS', + omidpn: 'Displayio', + mediationPlatform: 0, + prebidVersion: BIDDER_VERSION, + device: { + w: window.screen.width, + h: window.screen.height, + connection_type: connection ? connection.effectiveType : '', + } + } + } + if (navigator.permissions) { + navigator.permissions.query({ name: 'geolocation' }) + .then((result) => { + if (result.state === 'granted') { + payload.data.data.geo = _getGeoData(); + } + }); + } + return payload + } +}; + +function _getUserIDs () { + let ids = {}; + try { + ids = window.owpbjs.getUserIdsAsEids(); + } catch (e) {} + return ids; +} + +async function _getGeoData () { + let geoData = null; + const getCurrentPosition = () => { + return new Promise((resolve, reject) => + navigator.geolocation.getCurrentPosition(resolve, reject) + ); + } + try { + const position = await getCurrentPosition(); + let {latitude, longitude, accuracy} = position.coords; + geoData = { + 'lat': latitude, + 'lng': longitude, + 'precision': accuracy + }; + } catch (e) {} + return geoData +} + +registerBidder(spec); diff --git a/modules/displayioBidAdapter.md b/modules/displayioBidAdapter.md new file mode 100644 index 00000000000..41505ee966e --- /dev/null +++ b/modules/displayioBidAdapter.md @@ -0,0 +1,148 @@ +# Overview + +``` +Module Name: DisplayIO Bidder Adapter +Module Type: Bidder Adapter +``` + +# Description + +Module that connects to display.io's demand sources. +Web mobile (not relevant for web desktop). + + +#Features +| Feature | | Feature | | +|---------------|---------------------------------------------------------|-----------------------|-----| +| Bidder Code | displayio | Prebid member | no | +| Media Types | Banner, video.
Sizes (display 320x480 / vertical video) | GVL ID | no | +| GDPR Support | yes | Prebid.js Adapter | yes | +| USP Support | yes | Prebid Server Adapter | no | + + +#Global configuration +```javascript + + + +`; - bid.mediaType = BANNER; - } - // Common properties - bid.cpm = parseFloat(bidObject.price); - bid.creativeId = bidObject.crid; - bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; - - // Deal ID. Composite ads can have multiple line items and the ID of the first - // dealID line item will be used. - if (isNumber(bidObject.lid) && bidObject.buying_type && bidObject.buying_type !== 'rtb') { - bid.dealId = bidObject.lid; - } else if (Array.isArray(bidObject.lid) && - Array.isArray(bidObject.buying_type) && - bidObject.lid.length === bidObject.buying_type.length) { - let isDeal = false; - bidObject.buying_type.forEach((bt, i) => { - if (isDeal) return; - if (bt && bt !== 'rtb') { - isDeal = true; - bid.dealId = bidObject.lid[i]; - } - }); - } + serverResponse.body.seatbid.forEach(seatbid => { + if (!Array.isArray(seatbid.bid)) return; - bid.height = bidObject.h; - bid.netRevenue = bidObject.isNet ? bidObject.isNet : false; - bid.requestId = bidObject.id; - bid.ttl = 300; - bid.width = bidObject.w; + 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 + } - if (!bid.width || !bid.height) { - bid.width = 1; - bid.height = 1; - } + ID_RESPONSE.buildAd(bid, bidRequest, bidObject); - if (bidObject.adomain) { - bid.meta = { - advertiserDomains: bidObject.adomain - }; - } + ID_RAZR.addBidData({ + bidRequest, + bid + }); - bids.push(bid); + bids.push(bid); + }); }); + return bids; }, @@ -176,17 +206,14 @@ export const spec = { * @param {ServerResponse[]} serverResponses List of server's responses. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs(syncOptions, serverResponses) { if (syncOptions.pixelEnabled) { const syncs = []; serverResponses.forEach(response => { - response.body.bid.forEach(bidObject => { - if (isArray(bidObject.sync)) { - bidObject.sync.forEach(syncElement => { - if (syncs.indexOf(syncElement) === -1) { - syncs.push(syncElement); - } - }); + const syncArr = deepAccess(response, `body.ext.${BIDDER_CODE}.sync`, []); + syncArr.forEach(syncElement => { + if (syncs.indexOf(syncElement) === -1) { + syncs.push(syncElement); } }); }); @@ -196,519 +223,374 @@ export const spec = { } }; -function isInstreamVideo(bid) { - const mediaTypes = Object.keys(deepAccess(bid, 'mediaTypes', {})); - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - const context = deepAccess(bid, 'mediaTypes.video.context'); - return bid.mediaType === 'video' || (mediaTypes.length === 1 && videoMediaType && context !== 'outstream'); -} - -function isOutstreamVideo(bid) { - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - const context = deepAccess(bid, 'mediaTypes.video.context'); - return videoMediaType && context === 'outstream'; -} - -function getVideoTargetingParams(bid) { - const result = {}; - Object.keys(Object(bid.mediaTypes.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.mediaTypes.video[ key ]; - }); - Object.keys(Object(bid.params.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.params.video[ key ]; - }); - return result; -} +registerBidder(spec); -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - const floor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; -} - -function outstreamRender(bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [bid.width, bid.height], - targetId: bid.adUnitCode, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }, handleOutstreamRendererEvents.bind(null, bid)); - }); -} - -function handleOutstreamRendererEvents(bid, id, eventName) { - bid.renderer.handleVideoEvent({ id, eventName }); -} - -function createRenderer(bidRequest) { - const renderer = Renderer.install({ - id: bidRequest.adUnitCode, - url: RENDERER_URL, - loaded: false, - config: deepAccess(bidRequest, 'renderer.options'), - adUnitCode: bidRequest.adUnitCode - }); - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } - return renderer; -} - -function getNormalizedBidRequest(bid) { - let adUnitId = getBidIdParameter('adUnitCode', bid) || null; - let placementId = getBidIdParameter('placementId', bid.params) || null; - let publisherId = null; - let placementKey = null; - - if (placementId === null) { - publisherId = getBidIdParameter('publisherId', bid.params) || null; - placementKey = getBidIdParameter('placementKey', bid.params) || null; - } - const keyValues = getBidIdParameter('keyValues', bid.params) || null; - const singleSizeFilter = getBidIdParameter('size', bid.params) || null; - const bidId = getBidIdParameter('bidId', bid); - const transactionId = getBidIdParameter('transactionId', bid); - const currency = config.getConfig('currency.adServerCurrency'); - - let normalizedBidRequest = {}; - if (isInstreamVideo(bid)) { - normalizedBidRequest.adTypes = [ VIDEO ]; - } - if (isInstreamVideo(bid) || isOutstreamVideo(bid)) { - normalizedBidRequest.video = getVideoTargetingParams(bid); - } - if (placementId) { - normalizedBidRequest.placementId = placementId; - } else { - if (publisherId) { - normalizedBidRequest.publisherId = publisherId; - } - if (placementKey) { - normalizedBidRequest.placementKey = placementKey; +const ID_REQUEST = { + buildServerRequests(requestObject, bidRequests, bidderRequest) { + const requests = []; + if (config.getConfig('improvedigital.singleRequest') === true) { + requestObject.imp = bidRequests.map((bidRequest) => this.buildImp(bidRequest)); + requests[0] = this.formatRequest(requestObject, bidderRequest); + } else { + bidRequests.map((bidRequest) => { + const request = deepClone(requestObject); + request.id = bidRequest.bidId || getUniqueIdentifierStr(); + request.imp = [this.buildImp(bidRequest)]; + deepSetValue(request, 'source.tid', bidRequest.transactionId); + requests.push(this.formatRequest(request, bidderRequest)); + }); } - } - if (keyValues) { - normalizedBidRequest.keyValues = keyValues; - } + return requests; + }, - if (config.getConfig('improvedigital.usePrebidSizes') === true && !isInstreamVideo(bid) && !isOutstreamVideo(bid) && bid.sizes && bid.sizes.length > 0) { - normalizedBidRequest.format = bid.sizes; - } else if (singleSizeFilter && singleSizeFilter.w && singleSizeFilter.h) { - normalizedBidRequest.size = {}; - normalizedBidRequest.size.h = singleSizeFilter.h; - normalizedBidRequest.size.w = singleSizeFilter.w; - } + formatRequest(request, bidderRequest) { + return { + method: 'POST', + url: REQUEST_URL, + data: JSON.stringify(request), + bidderRequest + } + }, - if (bidId) { - normalizedBidRequest.id = bidId; - } - if (adUnitId) { - normalizedBidRequest.adUnitId = adUnitId; - } - if (transactionId) { - normalizedBidRequest.transactionId = transactionId; - } - if (currency) { - normalizedBidRequest.currency = currency; - } - // Floor - let bidFloor = getBidFloor(bid); - let bidFloorCur = null; - if (!bidFloor) { - bidFloor = getBidIdParameter('bidFloor', bid.params); - bidFloorCur = getBidIdParameter('bidFloorCur', bid.params); - } - if (bidFloor) { - normalizedBidRequest.bidFloor = bidFloor; - normalizedBidRequest.bidFloorCur = bidFloorCur ? bidFloorCur.toUpperCase() : 'USD'; - } - return normalizedBidRequest; -} + buildImp(bidRequest) { + const imp = { + id: getBidIdParameter('bidId', bidRequest) || getUniqueIdentifierStr(), + secure: ID_UTIL.toBit(window.location.protocol === 'https:'), + }; -function getNormalizedNativeAd(rawNative) { - const native = {}; - if (!rawNative || !isArray(rawNative.assets)) { - return null; - } - // Assets - rawNative.assets.forEach(asset => { - if (asset.title) { - native.title = asset.title.text; - } else if (asset.data) { - switch (asset.data.type) { - case 1: - native.sponsoredBy = asset.data.value; - break; - case 2: - native.body = asset.data.value; - break; - case 3: - native.rating = asset.data.value; - break; - case 4: - native.likes = asset.data.value; - break; - case 5: - native.downloads = asset.data.value; - break; - case 6: - native.price = asset.data.value; - break; - case 7: - native.salePrice = asset.data.value; - break; - case 8: - native.phone = asset.data.value; - break; - case 9: - native.address = asset.data.value; - break; - case 10: - native.body2 = asset.data.value; - break; - case 11: - native.displayUrl = asset.data.value; - break; - case 12: - native.cta = asset.data.value; - break; - } - } else if (asset.img) { - switch (asset.img.type) { - case 2: - native.icon = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h - }; - break; - case 3: - native.image = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h - }; - break; - } + // 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); } - }); - // Trackers - if (rawNative.eventtrackers) { - native.impressionTrackers = []; - rawNative.eventtrackers.forEach(tracker => { - // Only handle impression event. Viewability events are not supported yet. - if (tracker.event !== 1) return; - switch (tracker.method) { - case 1: // img - native.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. - native.javascriptTrackers = ``; - break; - } - }); - } else { - native.impressionTrackers = rawNative.imptrackers || []; - native.javascriptTrackers = rawNative.jstracker; - } - if (rawNative.link) { - native.clickUrl = rawNative.link.url; - native.clickTrackers = rawNative.link.clicktrackers; - } - if (rawNative.privacy) { - native.privacyLink = rawNative.privacy; - } - return native; -} -registerBidder(spec); -export function ImproveDigitalAdServerJSClient(endPoint) { - this.CONSTANTS = { - AD_SERVER_BASE_URL: 'ice.360yield.com', - END_POINT: endPoint || 'hb', - AD_SERVER_URL_PARAM: 'jsonp=', - CLIENT_VERSION: 'JS-6.4.0', - MAX_URL_LENGTH: 2083, - ERROR_CODES: { - MISSING_PLACEMENT_PARAMS: 2, - LIB_VERSION_MISSING: 3 - }, - RETURN_OBJ_TYPE: { - DEFAULT: 0, - URL_PARAMS_SPLIT: 1 + const placementId = getBidIdParameter('placementId', bidRequest.params); + if (placementId) { + deepSetValue(imp, 'ext.bidder.placementId', placementId); + } else { + deepSetValue(imp, 'ext.bidder.publisherId', getBidIdParameter('publisherId', bidRequest.params)); + deepSetValue(imp, 'ext.bidder.placementKey', getBidIdParameter('placementKey', bidRequest.params)); } - }; - this.getErrorReturn = function(errorCode) { - return { - idMappings: {}, - requests: {}, - 'errorCode': errorCode - }; - }; + deepSetValue(imp, 'ext.bidder.keyValues', getBidIdParameter('keyValues', bidRequest.params) || undefined); - this.createRequest = function(requestObject, requestParameters, extraRequestParameters) { - if (!requestParameters.libVersion) { - return this.getErrorReturn(this.CONSTANTS.ERROR_CODES.LIB_VERSION_MISSING); - } + // Adding GPID + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || + deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot') || + deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.adslot'); - requestParameters.returnObjType = requestParameters.returnObjType || this.CONSTANTS.RETURN_OBJ_TYPE.DEFAULT; - requestParameters.adServerBaseUrl = 'https://' + (requestParameters.adServerBaseUrl || this.CONSTANTS.AD_SERVER_BASE_URL); + deepSetValue(imp, 'ext.gpid', gpid); - let impressionObjects = []; - let impressionObject; - if (isArray(requestObject)) { - for (let counter = 0; counter < requestObject.length; counter++) { - impressionObject = this.createImpressionObject(requestObject[counter]); - impressionObjects.push(impressionObject); - } - } else { - impressionObject = this.createImpressionObject(requestObject); - impressionObjects.push(impressionObject); + // Adding Interstitial Signal + if (deepAccess(bidRequest, 'ortb2Imp.instl')) { + imp.instl = 1; } - let returnIdMappings = true; - if (requestParameters.returnObjType === this.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT) { - returnIdMappings = false; + 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); } - let returnObject = {}; - returnObject.requests = []; - if (returnIdMappings) { - returnObject.idMappings = []; + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + imp.banner = this.buildBannerRequest(bidRequest); } - let errors = null; - let baseUrl = `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; + if (deepAccess(bidRequest, 'mediaTypes.native')) { + imp.native = this.buildNativeRequest(bidRequest); + } - let bidRequestObject = { - bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) - }; - for (let counter = 0; counter < impressionObjects.length; counter++) { - impressionObject = impressionObjects[counter]; - - if (impressionObject.errorCode) { - errors = errors || []; - errors.push({ - errorCode: impressionObject.errorCode, - adUnitId: impressionObject.adUnitId - }); - } else { - if (returnIdMappings) { - returnObject.idMappings.push({ - adUnitId: impressionObject.adUnitId, - id: impressionObject.impressionObject.id - }); - } - bidRequestObject.bid_request.imp = bidRequestObject.bid_request.imp || []; - bidRequestObject.bid_request.imp.push(impressionObject.impressionObject); - - let writeLongRequest = false; - const outputUri = baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)); - if (outputUri.length > this.CONSTANTS.MAX_URL_LENGTH) { - writeLongRequest = true; - if (bidRequestObject.bid_request.imp.length > 1) { - // Pop the current request and process it again in the next iteration - bidRequestObject.bid_request.imp.pop(); - if (returnIdMappings) { - returnObject.idMappings.pop(); - } - counter--; - } - } + return imp; + }, - if (writeLongRequest || - !requestParameters.singleRequestMode || - counter === impressionObjects.length - 1) { - returnObject.requests.push(this.formatRequest(requestParameters, bidRequestObject)); - bidRequestObject = { - bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) - }; - } - } + 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; - if (errors) { - returnObject.errors = errors; + // Mimes is required + if (!video.mimes) { + video.mimes = VIDEO_PARAMS.DEFAULT_MIMES; } - return returnObject; - }; + // 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; + }, - this.formatRequest = function(requestParameters, bidRequestObject) { - switch (requestParameters.returnObjType) { - case this.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT: - return { - method: 'GET', - url: `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}`, - data: `${this.CONSTANTS.AD_SERVER_URL_PARAM}${encodeURIComponent(JSON.stringify(bidRequestObject))}` + 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.mediaTypes.native; + const request = { + 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: ID_UTIL.toBit(assetParams.required), }; - default: - const baseUrl = `${requestParameters.adServerBaseUrl}/` + - `${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; - return { - url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) + 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); + } } - }; + return { ver: NATIVE_DATA.VERSION, request: JSON.stringify(request) }; + }, - this.createBasicBidRequestObject = function(requestParameters, extraRequestParameters) { - let impressionBidRequestObject = {}; - impressionBidRequestObject.secure = 1; - if (requestParameters.requestId) { - impressionBidRequestObject.id = requestParameters.requestId; - } else { - impressionBidRequestObject.id = getUniqueIdentifierStr(); - } - if (requestParameters.domain) { - impressionBidRequestObject.domain = requestParameters.domain; - } - if (requestParameters.page) { - impressionBidRequestObject.page = requestParameters.page; - } - if (requestParameters.ref) { - impressionBidRequestObject.ref = requestParameters.ref; - } - if (requestParameters.callback) { - impressionBidRequestObject.callback = requestParameters.callback; - } - if (requestParameters.libVersion) { - impressionBidRequestObject.version = requestParameters.libVersion + '-' + this.CONSTANTS.CLIENT_VERSION; - } - if (requestParameters.referrer) { - impressionBidRequestObject.referrer = requestParameters.referrer; - } - if (requestParameters.gdpr || requestParameters.gdpr === 0) { - impressionBidRequestObject.gdpr = requestParameters.gdpr; - } - if (requestParameters.usPrivacy) { - impressionBidRequestObject.us_privacy = requestParameters.usPrivacy; - } - if (requestParameters.schain) { - impressionBidRequestObject.schain = requestParameters.schain; - } - if (requestParameters.user) { - impressionBidRequestObject.user = requestParameters.user; + isOutstreamVideo(bidRequest) { + return deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream'; + }, + + getBidFloor(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return null; } - if (extraRequestParameters) { - for (let prop in extraRequestParameters) { - impressionBidRequestObject[prop] = extraRequestParameters[prop]; - } + const floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; } + return null; + }, - return impressionBidRequestObject; - }; - - this.createImpressionObject = function(placementObject) { - let outputObject = {}; - let impressionObject = {}; - outputObject.impressionObject = impressionObject; + buildSiteOrApp(request, bidderRequest) { + const app = {}; + const configAppSettings = config.getConfig('app') || {}; + const fpdAppSettings = config.getConfig('ortb2.app') || {}; + mergeDeep(app, configAppSettings, fpdAppSettings); - if (placementObject.id) { - impressionObject.id = placementObject.id; + if (Object.keys(app).length !== 0) { + request.app = app; } else { - impressionObject.id = getUniqueIdentifierStr(); - } - if (placementObject.adTypes) { - impressionObject.ad_types = placementObject.adTypes; - } - if (placementObject.adUnitId) { - outputObject.adUnitId = placementObject.adUnitId; - } - if (placementObject.currency) { - impressionObject.currency = placementObject.currency.toUpperCase(); - } - if (placementObject.bidFloor) { - impressionObject.bidfloor = placementObject.bidFloor; - } - if (placementObject.bidFloorCur) { - impressionObject.bidfloorcur = placementObject.bidFloorCur.toUpperCase(); - } - if (placementObject.placementId) { - impressionObject.pid = placementObject.placementId; - } - if (placementObject.publisherId) { - impressionObject.pubid = placementObject.publisherId; - } - if (placementObject.placementKey) { - impressionObject.pkey = placementObject.placementKey; - } - if (placementObject.transactionId) { - impressionObject.tid = placementObject.transactionId; - } - if (!isEmpty(placementObject.video)) { - const video = Object.assign({}, placementObject.video); - // 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; - } - } - if (!isEmpty(video)) { - impressionObject.video = video; + const site = {}; + const url = config.getConfig('pageUrl') || deepAccess(bidderRequest, 'refererInfo.referer'); + if (url) { + site.page = url; + site.domain = parseUrl(url).hostname; } + const configSiteSettings = config.getConfig('site') || {}; + const fpdSiteSettings = config.getConfig('ortb2.site') || {}; + mergeDeep(site, configSiteSettings, fpdSiteSettings); + request.site = site; } - if (placementObject.keyValues) { - for (let key in placementObject.keyValues) { - for (let valueCounter = 0; valueCounter < placementObject.keyValues[key].length; valueCounter++) { - impressionObject.kvw = impressionObject.kvw || {}; - impressionObject.kvw[key] = impressionObject.kvw[key] || []; - impressionObject.kvw[key].push(placementObject.keyValues[key][valueCounter]); - } + }, +}; + +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 { + if (bidResponse.adm.search(/^ sizePair.length === 2 && - isInteger(sizePair[0]) && - isInteger(sizePair[1]) && - sizePair[0] >= 0 && - sizePair[1] >= 0) - .map(sizePair => { - return { w: sizePair[0], h: sizePair[1] } - }); - if (format.length > 0) { - impressionObject.banner.format = format; + buildBannerAd(bid, bidRequest, bidResponse) { + bid.mediaType = BANNER; + bid.ad = bidResponse.adm; + bid.width = bidResponse.w; + bid.height = bidResponse.h; + }, + + buildNativeAd(bid, bidRequest, bidResponse) { + bid.mediaType = NATIVE; + const nativeResponse = JSON.parse(bidResponse.adm); + const nativeAd = { + clickUrl: deepAccess(nativeResponse, 'link.url'), + clickTrackers: deepAccess(nativeResponse, 'link.clicktrackers'), + privacyLink: nativeResponse.privacy + } + // Trackers + if (nativeResponse.eventtrackers) { + nativeAd.impressionTrackers = []; + nativeResponse.eventtrackers.forEach(tracker => { + // 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 = { + RENDERER_URL: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + createRenderer(bidRequest) { + const renderer = Renderer.install({ + id: bidRequest.adUnitCode, + url: this.RENDERER_URL, + config: deepAccess(bidRequest, 'renderer.options'), + adUnitCode: bidRequest.adUnitCode + }); + try { + renderer.setRender(this.render); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); } + return renderer; + }, + + render(bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, ID_OUTSTREAM.handleRendererEvents.bind(null, bid)); + }); + }, + + handleRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); + }, +}; - if (!impressionObject.pid && - !impressionObject.pubid && - !impressionObject.pkey && - !(impressionObject.banner && impressionObject.banner.w && impressionObject.banner.h)) { - outputObject.impressionObject = null; - outputObject.errorCode = this.CONSTANTS.ERROR_CODES.MISSING_PLACEMENT_PARAMS; +const ID_RAZR = { + RENDERER_URL: 'https://razr.improvedigital.com/renderer.js', + addBidData({bid, bidRequest}) { + if (this.isValidBid(bid)) { + bid.renderer = Renderer.install({ + url: this.RENDERER_URL, + config: {bidRequest} + }); + bid.renderer.setRender(this.render); } - return outputObject; - }; -} + }, + + isValidBid(bid) { + return bid && /razr:\/\//.test(bid.ad); + }, + + render(bid) { + const {bidRequest} = bid.renderer.getConfig(); + + const payload = { + type: 'prebid', + bidRequest, + bid, + config: mergeDeep( + {}, + config.getConfig('improvedigital.rendererConfig'), + deepAccess(bidRequest, 'params.rendererConfig') + ) + }; + + const razr = window.razr = window.razr || {}; + razr.queue = razr.queue || []; + razr.queue.push(payload); + } +}; + +const ID_UTIL = { + toBit(val) { + return val ? 1 : 0; + }, +}; diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 984d5c637bc..4122166a151 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -3,7 +3,7 @@ import {BANNER} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {deepAccess, generateUUID, logError, isArray} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from '../src/polyfill.js'; const BIDDER_CODE = 'insticator'; const ENDPOINT = 'https://ex.ingage.tech/v1/openrtb'; // production endpoint @@ -12,7 +12,7 @@ const USER_ID_COOKIE_EXP = 2592000000; // 30 days const BID_TTL = 300; // 5 minutes const GVLID = 910; -export const storage = getStorageManager(GVLID, BIDDER_CODE); +export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); config.setDefaults({ insticator: { @@ -139,7 +139,7 @@ function extractSchain(bids, requestId) { function extractEids(bids) { if (!bids) return; - const bid = find(bids, bid => isArray(bid.userIdAsEids) && bid.userIdAsEids.length > 0); + const bid = bids.find(bid => isArray(bid.userIdAsEids) && bid.userIdAsEids.length > 0); return bid ? bid.userIdAsEids : bids[0].userIdAsEids; } @@ -181,13 +181,13 @@ function buildRequest(validBidRequests, bidderRequest) { const schain = extractSchain(validBidRequests, bidderRequest.bidderRequestId); if (schain) { - req.source.ext = {schain}; + req.source.ext = { schain }; } const eids = extractEids(validBidRequests); if (eids) { - req.user.ext = {eids}; + req.user.ext = { eids }; } return req; @@ -195,14 +195,14 @@ function buildRequest(validBidRequests, bidderRequest) { function buildBid(bid, bidderRequest) { const originalBid = find(bidderRequest.bids, (b) => b.bidId === bid.impid); - let meta = {}; + let meta = {} if (bid.ext && bid.ext.meta) { - meta = bid.ext.meta; + meta = bid.ext.meta } if (bid.adomain) { - meta.advertiserDomains = bid.adomain; + meta.advertiserDomains = bid.adomain } return { diff --git a/modules/instreamTracking.js b/modules/instreamTracking.js index 40a6ecb1939..ff8305c7fed 100644 --- a/modules/instreamTracking.js +++ b/modules/instreamTracking.js @@ -3,11 +3,11 @@ import { config } from '../src/config.js'; import { auctionManager } from '../src/auctionManager.js'; import { INSTREAM } from '../src/video.js'; import * as events from '../src/events.js'; -import { BID_STATUS, EVENTS, TARGETING_KEYS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json' -const {CACHE_ID, UUID} = TARGETING_KEYS; -const {BID_WON, AUCTION_END} = EVENTS; -const {RENDERED} = BID_STATUS; +const {CACHE_ID, UUID} = CONSTANTS.TARGETING_KEYS; +const {BID_WON, AUCTION_END} = CONSTANTS.EVENTS; +const {RENDERED} = CONSTANTS.BID_STATUS; const INSTREAM_TRACKING_DEFAULT_CONFIG = { enabled: false, diff --git a/modules/integr8BidAdapter.js b/modules/integr8BidAdapter.js index 321c3c4c1ab..d61fe624c59 100644 --- a/modules/integr8BidAdapter.js +++ b/modules/integr8BidAdapter.js @@ -10,7 +10,7 @@ const SIZE_SEPARATOR = ';'; const BISKO_ID = 'biskoId'; const STORAGE_ID = 'bisko-sid'; const SEGMENTS = 'biskoSegments'; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 2092f9a185a..d60ab3962ae 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -15,7 +15,7 @@ const PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; export const FIRST_PARTY_KEY = '_iiq_fdata'; -export const storage = getStorageManager(undefined, MODULE_NAME); +export const storage = getStorageManager({gvlid: undefined, moduleName: MODULE_NAME}); const INVALID_ID = 'INVALID_ID'; diff --git a/modules/interactiveOffersBidAdapter.js b/modules/interactiveOffersBidAdapter.js index c1757198eca..d8a106623fd 100644 --- a/modules/interactiveOffersBidAdapter.js +++ b/modules/interactiveOffersBidAdapter.js @@ -83,7 +83,8 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest) { let openRTBRequest = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequest'])); openRTBRequest.id = prebidRequest.auctionId; openRTBRequest.ext = { - auctionstart: Date.now() + refererInfo: prebidRequest.refererInfo, + auctionId: prebidRequest.auctionId }; openRTBRequest.site = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSite'])); @@ -111,15 +112,17 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest) { openRTBRequest.user = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestUser'])); openRTBRequest.imp = []; - prebidRequest.bids.forEach(function(bid, impId) { - impId++; + prebidRequest.bids.forEach(function(bid) { if (!ret.partnerId) { ret.partnerId = bid.params.partnerId; } let imp = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImp'])); - imp.id = impId; + imp.id = bid.bidId; imp.secure = secure; - imp.tagid = bid.bidId; + imp.tagid = bid.adUnitCode; + imp.ext = { + rawdata: bid + }; openRTBRequest.site.publisher.id = openRTBRequest.site.publisher.id || 0; openRTBRequest.tmax = openRTBRequest.tmax || bid.params.tmax || 0; @@ -152,7 +155,7 @@ function parseResponseOpenRTBToPrebidjs(openRTBResponse) { if (seatbid.bid && seatbid.bid.forEach) { seatbid.bid.forEach(function(bid) { let prebid = JSON.parse(JSON.stringify(DEFAULT['PrebidBid'])); - prebid.requestId = bid.ext.tagid; + prebid.requestId = bid.impid; prebid.ad = bid.adm; prebid.creativeId = bid.crid; prebid.cpm = bid.price; diff --git a/modules/intersectionRtdProvider.js b/modules/intersectionRtdProvider.js new file mode 100644 index 00000000000..c7d03b25b57 --- /dev/null +++ b/modules/intersectionRtdProvider.js @@ -0,0 +1,115 @@ +import {submodule} from '../src/hook.js'; +import {isFn, logError} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {includes} from '../src/polyfill.js'; +import '../src/adapterManager.js'; + +let observerAvailable = true; +function getIntersectionData(requestBidsObject, onDone, providerConfig, userConsent) { + const intersectionMap = {}; + const placeholdersMap = {}; + let done = false; + if (!observerAvailable) return complete(); + const observer = new IntersectionObserver(observerCallback, {threshold: 0.5}); + const adUnitCodes = requestBidsObject.adUnitCodes || []; + const auctionDelay = config.getConfig('realTimeData.auctionDelay') || 0; + const waitForIt = providerConfig.waitForIt; + let adUnits = requestBidsObject.adUnits || getGlobal().adUnits || []; + if (adUnitCodes.length) { + adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); + } + let checkTimeoutId; + findAndObservePlaceholders(); + if (auctionDelay > 0) { + setTimeout(complete, auctionDelay); + } + function findAndObservePlaceholders() { + const observed = adUnits.filter((unit) => { + const code = unit.code; + if (placeholdersMap[code]) return true; + const ph = document.getElementById(code); + if (ph) { + placeholdersMap[code] = ph; + observer.observe(ph); + return true; + } + }); + if ( + observed.length === adUnits.length || + !waitForIt || + auctionDelay <= 0 + ) { + return; + } + checkTimeoutId = setTimeout(findAndObservePlaceholders); + } + function observerCallback(entries) { + let entry = entries.pop(); + while (entry) { + const target = entry.target; + const id = target.getAttribute('id'); + if (id) { + const intersection = intersectionMap[id]; + if (!intersection || intersection.time < entry.time) { + intersectionMap[id] = { + 'boundingClientRect': cloneRect(entry.boundingClientRect), + 'intersectionRect': cloneRect(entry.intersectionRect), + 'rootRect': cloneRect(entry.rootRect), + 'intersectionRatio': entry.intersectionRatio, + 'isIntersecting': entry.isIntersecting, + 'time': entry.time + }; + if (adUnits.every(unit => !!intersectionMap[unit.code])) { + complete(); + } + } + } + entry = entries.pop(); + } + } + function complete() { + if (done) return; + if (checkTimeoutId) clearTimeout(checkTimeoutId); + done = true; + checkTimeoutId = null; + observer && observer.disconnect(); + adUnits && adUnits.forEach((unit) => { + const intersection = intersectionMap[unit.code]; + if (intersection && unit.bids) { + unit.bids.forEach(bid => bid.intersection = intersection); + } + }); + onDone(); + } +} +function init(moduleConfig) { + if (!isFn(window.IntersectionObserver)) { + logError('IntersectionObserver is not defined'); + observerAvailable = false; + } else { + observerAvailable = true; + } + return observerAvailable; +} +function cloneRect(rect) { + return rect ? { + 'left': rect.left, + 'top': rect.top, + 'right': rect.right, + 'bottom': rect.bottom, + 'width': rect.width, + 'height': rect.height, + 'x': rect.x, + 'y': rect.y, + } : rect; +} +export const intersectionSubmodule = { + name: 'intersection', + getBidRequestData: getIntersectionData, + init: init, +}; +function registerSubModule() { + submodule('realTimeData', intersectionSubmodule); +} +registerSubModule(); diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 75da1509f19..e83786f3857 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -4,7 +4,8 @@ import {getStorageManager} from '../src/storageManager.js'; const CONSTANTS = { BIDDER_CODE: 'invibes', - BID_ENDPOINT: 'https://bid.videostep.com/Bid/VideoAdContent', + BID_ENDPOINT: '.videostep.com/Bid/VideoAdContent', + BID_SUBDOMAIN: 'https://bid', SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', @@ -15,7 +16,7 @@ const CONSTANTS = { META_TAXONOMY: ['networkId', 'networkName', 'agencyId', 'agencyName', 'advertiserId', 'advertiserName', 'advertiserDomains', 'brandId', 'brandName', 'primaryCatId', 'secondaryCatIds', 'mediaType'] }; -const storage = getStorageManager(CONSTANTS.INVIBES_VENDOR_ID); +const storage = getStorageManager({gvlid: CONSTANTS.INVIBES_VENDOR_ID, bidderCode: CONSTANTS.BIDDER_CODE}); export const spec = { code: CONSTANTS.BIDDER_CODE, @@ -77,13 +78,15 @@ function isBidRequestValid(bid) { function buildRequest(bidRequests, bidderRequest) { bidderRequest = bidderRequest || {}; const _placementIds = []; - let _loginId, _customEndpoint, _userId; + const _adUnitCodes = []; + let _customEndpoint, _userId, _domainId; let _ivAuctionStart = bidderRequest.auctionStart || Date.now(); bidRequests.forEach(function (bidRequest) { bidRequest.startTime = new Date().getTime(); _placementIds.push(bidRequest.params.placementId); - _loginId = _loginId || bidRequest.params.loginId; + _adUnitCodes.push(bidRequest.adUnitCode); + _domainId = _domainId || bidRequest.params.domainId; _customEndpoint = _customEndpoint || bidRequest.params.customEndpoint; _customUserSync = _customUserSync || bidRequest.params.customUserSync; _userId = _userId || bidRequest.userId; @@ -99,7 +102,7 @@ function buildRequest(bidRequests, bidderRequest) { let userIdModel = getUserIds(_userId); let bidParamsJson = { placementIds: _placementIds, - loginId: _loginId, + adUnitCodes: _adUnitCodes, auctionStartTime: _ivAuctionStart, bidVersion: CONSTANTS.PREBID_VERSION }; @@ -143,9 +146,11 @@ function buildRequest(bidRequests, bidderRequest) { } } + let endpoint = createEndpoint(_customEndpoint, _domainId, _placementIds); + return { method: CONSTANTS.METHOD, - url: _customEndpoint || CONSTANTS.BID_ENDPOINT, + url: endpoint, data: data, options: {withCredentials: true}, // for POST: { contentType: 'application/json', withCredentials: true } @@ -181,9 +186,12 @@ function handleResponse(responseObj, bidRequests) { const bidResponses = []; for (let i = 0; i < bidRequests.length; i++) { let bidRequest = bidRequests[i]; + let usedPlacementId = responseObj.UseAdUnitCode === true + ? bidRequest.params.placementId + '_' + bidRequest.adUnitCode + : bidRequest.params.placementId; - if (invibes.placementBids.indexOf(bidRequest.params.placementId) > -1) { - logInfo('Invibes Adapter - Placement was previously bid on ' + bidRequest.params.placementId); + if (invibes.placementBids.indexOf(usedPlacementId) > -1) { + logInfo('Invibes Adapter - Placement was previously bid on ' + usedPlacementId); continue; } @@ -191,21 +199,21 @@ function handleResponse(responseObj, bidRequests) { if (responseObj.AdPlacements != null) { for (let j = 0; j < responseObj.AdPlacements.length; j++) { let bidModel = responseObj.AdPlacements[j].BidModel; - if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + if (bidModel != null && bidModel.PlacementId == usedPlacementId) { requestPlacement = responseObj.AdPlacements[j]; break; } } } else { let bidModel = responseObj.BidModel; - if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + if (bidModel != null && bidModel.PlacementId == usedPlacementId) { requestPlacement = responseObj; } } - let bid = createBid(bidRequest, requestPlacement, responseObj.MultipositionEnabled); + let bid = createBid(bidRequest, requestPlacement, responseObj.MultipositionEnabled, usedPlacementId); if (bid !== null) { - invibes.placementBids.push(bidRequest.params.placementId); + invibes.placementBids.push(usedPlacementId); bidResponses.push(bid); } } @@ -213,9 +221,9 @@ function handleResponse(responseObj, bidRequests) { return bidResponses; } -function createBid(bidRequest, requestPlacement, multipositionEnabled) { +function createBid(bidRequest, requestPlacement, multipositionEnabled, usedPlacementId) { if (requestPlacement === null || requestPlacement.BidModel === null) { - logInfo('Invibes Adapter - Placement not configured for bidding ' + bidRequest.params.placementId); + logInfo('Invibes Adapter - Placement not configured for bidding ' + usedPlacementId); return null; } @@ -274,6 +282,48 @@ function createBid(bidRequest, requestPlacement, multipositionEnabled) { }; } +function createEndpoint(customEndpoint, domainId, placementIds) { + if (customEndpoint != null) { + return customEndpoint; + } + + if (domainId != null) { + return extractEndpointFromId(domainId - 1000); + } + + if (placementIds.length > 0) { + for (var i = 0; i < placementIds.length; i++) { + const id = extractFromPlacement(placementIds[i]); + if (id != null) { + return extractEndpointFromId(id); + } + } + } + + return extractEndpointFromId(1); +} + +function extractEndpointFromId(domainId) { + if (domainId < 2) { + return CONSTANTS.BID_SUBDOMAIN + CONSTANTS.BID_ENDPOINT; + } + + return CONSTANTS.BID_SUBDOMAIN + domainId + CONSTANTS.BID_ENDPOINT; +} + +function extractFromPlacement(placementId) { + if (placementId == null) { return null; } + + var pattern = /_ivbs([0-9]+)/g; + + var match = pattern.exec(placementId); + if (match != null && match[1] != null) { + return parseInt(match[1]); + } + + return null; +} + function addMeta(bidModelMeta) { var meta = {}; if (bidModelMeta != null) { @@ -684,7 +734,7 @@ let keywords = (function () { return kw; }()); -// ===================== +// ====================== export function resetInvibes() { invibes.optIn = undefined; diff --git a/modules/invibesBidAdapter.md b/modules/invibesBidAdapter.md index 06afba6344d..06588b6cb28 100644 --- a/modules/invibesBidAdapter.md +++ b/modules/invibesBidAdapter.md @@ -20,7 +20,8 @@ Connect to Invibes for bids. { bidder: 'invibes', params: { - placementId: '12345' + placementId: '12345', + domainId: 1001 } } ] diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index 5268f8935be..6c0a2e5f56d 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -5,6 +5,7 @@ import { config } from '../src/config.js'; const BIDDER_CODE = 'iqzone'; const AD_URL = 'https://smartssp-us-east.iqzone.com/pbjs'; +const SYNC_URL = 'https://cs.smartssp.iqzone.com'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -27,16 +28,23 @@ function isBidResponseValid(bid) { function getPlacementReqData(bid) { const { params, bidId, mediaTypes } = bid; const schain = bid.schain || {}; - const { placementId } = params; + const { placementId, endpointId } = params; const bidfloor = getBidFloor(bid); const placement = { - placementId, 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; @@ -88,7 +96,7 @@ export const spec = { isBidRequestValid: (bid = {}) => { const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.placementId); + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); if (mediaTypes && mediaTypes[BANNER]) { valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); @@ -170,6 +178,29 @@ export const spec = { } } 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 + }]; } }; diff --git a/modules/iqzoneBidAdapter.md b/modules/iqzoneBidAdapter.md index 75e82d59b3e..3808bdbabec 100644 --- a/modules/iqzoneBidAdapter.md +++ b/modules/iqzoneBidAdapter.md @@ -75,6 +75,22 @@ IQZone bid adapter supports Banner, Video (instream and outstream) and Native. } } ] + }, + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'iqzone', + params: { + endpointId: 'testBanner', + } + } + ] } ]; ``` \ No newline at end of file diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 0a686ddace2..7cec6172ba4 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -1,11 +1,30 @@ -import { deepAccess, parseGPTSingleSizeArray, inIframe, deepClone, logError, logWarn, isFn, contains, isInteger, isArray, deepSetValue, parseQueryStringParameters, isEmpty, mergeDeep, convertTypes } from '../src/utils.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import find from 'core-js-pure/features/array/find.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { INSTREAM, OUTSTREAM } from '../src/video.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import { Renderer } from '../src/Renderer.js'; +import { + contains, + convertTypes, + deepAccess, + deepClone, + deepSetValue, + hasDeviceAccess, + inIframe, + isArray, + isEmpty, + isFn, + isInteger, + logError, + logWarn, + mergeDeep, + parseGPTSingleSizeArray, + parseQueryStringParameters +} from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import CONSTANTS from '../src/constants.json'; +import {getStorageManager, validateStorageEnforcement} from '../src/storageManager.js'; +import * as events from '../src/events.js'; +import {find, includes} 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'; @@ -20,15 +39,23 @@ const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NET_REVENUE = true; const MAX_REQUEST_SIZE = 8000; const MAX_REQUEST_LIMIT = 4; - const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; const RENDERER_URL = 'https://js-sec.indexww.com/htv/video-player.js'; const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; -// determines which eids we send and the rtiPartner field in ext - +export const ERROR_CODES = { + BID_SIZE_INVALID_FORMAT: 1, + BID_SIZE_NOT_INCLUDED: 2, + PROPERTY_NOT_INCLUDED: 3, + SITE_ID_INVALID_VALUE: 4, + BID_FLOOR_INVALID_FORMAT: 5, + IX_FPD_EXCEEDS_MAX_SIZE: 6, + EXCEEDS_MAX_SIZE: 7, + PB_FPD_EXCEEDS_MAX_SIZE: 8, + VIDEO_DURATION_INVALID: 9 +}; const FIRST_PARTY_DATA = { SITE: [ 'id', 'name', 'domain', 'cat', 'sectioncat', 'pagecat', 'page', 'ref', 'search', 'mobile', @@ -36,35 +63,33 @@ const FIRST_PARTY_DATA = { ], USER: ['id', 'buyeruid', 'yob', 'gender', 'keywords', 'customdata', 'geo', 'data', 'ext'] }; - const SOURCE_RTI_MAPPING = { 'liveramp.com': 'idl', 'netid.de': 'NETID', 'neustar.biz': 'fabrickId', 'zeotap.com': 'zeotapIdPlus', 'uidapi.com': 'UID2', - 'adserver.org': 'TDID' + 'adserver.org': 'TDID', + 'id5-sync.com': '', // ID5 Universal ID, configured as id5Id + 'crwdcntrl.net': '', // Lotame Panorama ID, lotamePanoramaId + 'epsilon.com': '', // Publisher Link, publinkId + 'audigent.com': '', // Halo ID from Audigent, haloId + 'pubcid.org': '', // SharedID, pubcid + 'trustpid.com': '' // Trustpid }; - const PROVIDERS = [ 'britepoolid', - 'id5id', 'lipbid', - 'haloId', 'criteoId', - 'lotamePanoramaId', 'merkleId', 'parrableId', 'connectid', 'tapadId', 'quantcastId', - 'pubcid', - 'TDID', - 'flocId' + 'flocId', + 'pubProvidedId' ]; - const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration']; // note: protocol/protocols is also reqd - const VIDEO_PARAMS_ALLOW_LIST = [ 'mimes', 'minduration', 'maxduration', 'protocols', 'protocol', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', @@ -73,6 +98,17 @@ const VIDEO_PARAMS_ALLOW_LIST = [ 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext', 'playerSize', 'w', 'h' ]; +const LOCAL_STORAGE_KEY = 'ixdiag'; +let hasRegisteredHandler = false; +export const storage = getStorageManager({gvlid: GLOBAL_VENDOR_ID, bidderCode: BIDDER_CODE}); + +// Possible values for bidResponse.seatBid[].bid[].mtype which indicates the type of the creative markup so that it can properly be associated with the right sub-object of the BidRequest.Imp. +const MEDIA_TYPES = { + Banner: 1, + Video: 2, + Audio: 3, + Native: 4 +} /** * Transform valid bid request config object to banner impression object that will be sent to ad server. @@ -125,7 +161,10 @@ function bidToVideoImp(bid) { } if (imp.video.minduration > imp.video.maxduration) { - logError(`IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]`); + logError( + `IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]`, + { bidder: BIDDER_CODE, code: ERROR_CODES.VIDEO_DURATION_INVALID } + ); return {}; } @@ -173,17 +212,13 @@ function bidToImp(bid) { imp.id = bid.bidId; imp.ext = {}; - imp.ext.siteID = bid.params.siteId; + imp.ext.siteID = bid.params.siteId.toString(); if (bid.params.hasOwnProperty('id') && (typeof bid.params.id === 'string' || typeof bid.params.id === 'number')) { imp.ext.sid = String(bid.params.id); } - const dfpAdUnitCode = deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot'); - if (dfpAdUnitCode) { - imp.ext.dfp_ad_unit_code = dfpAdUnitCode; - } return imp; } @@ -262,9 +297,14 @@ function parseBid(rawBid, currency, bidRequest) { bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; - // in the event of a video - if (deepAccess(rawBid, 'ext.vasturl')) { + if (rawBid.mtype == MEDIA_TYPES.Video) { + bid.vastXml = rawBid.adm + } else if (rawBid.ext && rawBid.ext.vasturl) { bid.vastUrl = rawBid.ext.vasturl + } + + // in the event of a video + if ((rawBid.ext && rawBid.ext.vasturl) || rawBid.mtype == MEDIA_TYPES.Video) { bid.width = bidRequest.video.w; bid.height = bidRequest.video.h; bid.mediaType = VIDEO; @@ -418,16 +458,19 @@ function getEidInfo(allEids, flocData) { let seenSources = {}; if (isArray(allEids)) { for (const eid of allEids) { - if (SOURCE_RTI_MAPPING[eid.source] && deepAccess(eid, 'uids.0')) { + if (SOURCE_RTI_MAPPING.hasOwnProperty(eid.source) && deepAccess(eid, 'uids.0')) { seenSources[eid.source] = true; - eid.uids[0].ext = { - rtiPartner: SOURCE_RTI_MAPPING[eid.source] - }; + if (SOURCE_RTI_MAPPING[eid.source] != '') { + eid.uids[0].ext = { + rtiPartner: SOURCE_RTI_MAPPING[eid.source] + }; + } delete eid.uids[0].atype; toSend.push(eid); } } } + const isValidFlocId = flocData && flocData.id && flocData.version; if (isValidFlocId) { const flocEid = { @@ -440,6 +483,7 @@ function getEidInfo(allEids, flocData) { return { toSend, seenSources }; } + /** * Builds a request object to be sent to the ad server based on bid requests. * @@ -456,6 +500,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Get ids from Prebid User ID Modules let eidInfo = getEidInfo(deepAccess(validBidRequests, '0.userIdAsEids'), deepAccess(validBidRequests, '0.userId.flocId')); let userEids = eidInfo.toSend; + const pageUrl = getPageUrl() || deepAccess(bidderRequest, 'refererInfo.referer'); // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist @@ -498,6 +543,13 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext.ixdiag[key] = ixdiag[key]; } + // Get cached errors stored in LocalStorage + const cachedErrors = getCachedErrors(); + + if (!isEmpty(cachedErrors)) { + r.ext.ixdiag.err = cachedErrors; + } + // if an schain is provided, send it along if (validBidRequests[0].schain) { r.source = { @@ -547,8 +599,8 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { deepSetValue(r, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - if (bidderRequest.refererInfo) { - r.site.page = bidderRequest.refererInfo.referer; + if (pageUrl) { + r.site.page = pageUrl; } } @@ -576,7 +628,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; if (baseRequestSize > MAX_REQUEST_SIZE) { - logError('ix bidder: Base request size has exceeded maximum 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; } @@ -606,7 +658,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } currentRequestSize += fpdRequestSize; } else { - logError('ix bidder: IX config FPD request size has exceeded maximum request size.'); + 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 }); } } @@ -645,8 +697,10 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; } + const gpid = impressions[transactionIds[adUnitIndex]].gpid; + const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; if (impressionObjects.length && BANNER in impressionObjects[0]) { - const { id, banner: { topframe }, ext } = impressionObjects[0]; + const { id, banner: { topframe } } = impressionObjects[0]; const _bannerImpression = { id, banner: { @@ -655,10 +709,10 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { }, } - if (ext.dfp_ad_unit_code) { - _bannerImpression.ext = { - dfp_ad_unit_code: ext.dfp_ad_unit_code - } + if (dfpAdUnitCode || gpid) { + _bannerImpression.ext = {}; + _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; + _bannerImpression.ext.gpid = gpid; } if ('bidfloor' in impressionObjects[0]) { @@ -673,6 +727,8 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext.ixdiag.msd += missingCount; r.ext.ixdiag.msi += missingBannerImpressions.length; } else { + // set imp.ext.gpid to resolved gpid for each imp + impressionObjects.forEach(imp => deepSetValue(imp, 'ext.gpid', gpid)); r.imp.push(...impressionObjects); } @@ -713,7 +769,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; currentRequestSize += fpdRequestSize; } else { - logError('ix bidder: FPD request size has exceeded maximum request size.'); + logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE }); } } @@ -849,6 +905,7 @@ function createVideoImps(validBidRequest, videoImps) { videoImps[validBidRequest.transactionId] = {}; videoImps[validBidRequest.transactionId].ixImps = []; videoImps[validBidRequest.transactionId].ixImps.push(imp); + videoImps[validBidRequest.transactionId].gpid = deepAccess(validBidRequest, 'ortb2Imp.ext.gpid'); } } @@ -869,11 +926,15 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { const bannerSizeDefined = includesSize(deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), deepAccess(validBidRequest, 'params.size')); + if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { + bannerImps[validBidRequest.transactionId] = {}; + } + + bannerImps[validBidRequest.transactionId].gpid = deepAccess(validBidRequest, 'ortb2Imp.ext.gpid'); + bannerImps[validBidRequest.transactionId].dfp_ad_unit_code = deepAccess(validBidRequest, 'ortb2Imp.ext.data.adserver.adslot'); + // Create IX imps from params.size if (bannerSizeDefined) { - if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { - bannerImps[validBidRequest.transactionId] = {}; - } if (!bannerImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { bannerImps[validBidRequest.transactionId].ixImps = [] } @@ -885,6 +946,20 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { } } +/** + * Returns the `pageUrl` set by publisher on the page if it is an valid url + */ +function getPageUrl() { + const pageUrl = config.getConfig('pageUrl'); + try { + const url = new URL(pageUrl); + return url.href; + } catch (_) { + logWarn(`IX Bid Adapter: invalid pageUrl config property value set: ${pageUrl}`); + return undefined; + } +} + /** * Determines IX configuration type based on IX params * @param {object} valid IX configured param @@ -946,10 +1021,117 @@ function createMissingBannerImp(bid, imp, newSize) { } /** + * @typedef {Array[message: string, err: Object]} ErrorData + * @property {string} message - The error message. + * @property {object} err - The error object. + * @property {string} err.bidder - The bidder of the error. + * @property {string} err.code - The error code. + */ + +/** + * Error Event handler that receives type and arguments in a data object. + * + * @param {ErrorData} data + */ +function storeErrorEventData(data) { + if (!storage.localStorageIsEnabled()) { + return; + } + + let currentStorage; + + try { + currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}'); + } catch (e) { + logWarn('ix can not read ixdiag from localStorage.'); + } + + const todayDate = new Date(); + + Object.keys(currentStorage).map((errorDate) => { + const date = new Date(errorDate); + + if (date.setDate(date.getDate() + 7) - todayDate < 0) { + delete currentStorage[errorDate]; + } + }); + + if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) { + const todayString = todayDate.toISOString().slice(0, 10); + + const errorCode = data.arguments[1].code; + + if (errorCode) { + currentStorage[todayString] = currentStorage[todayString] || {}; + + if (!Number(currentStorage[todayString][errorCode])) { + currentStorage[todayString][errorCode] = 0; + } + + currentStorage[todayString][errorCode]++; + }; + } + + storage.setDataInLocalStorage(LOCAL_STORAGE_KEY, JSON.stringify(currentStorage)); +} + +/** + * Event handler for storing data into local storage. It will only store data if + * local storage premissions are avaliable + */ +function localStorageHandler(data) { + if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) { + const DEFAULT_ENFORCEMENT_SETTINGS = { + hasEnforcementHook: false, + valid: hasDeviceAccess() + }; + validateStorageEnforcement(GLOBAL_VENDOR_ID, BIDDER_CODE, DEFAULT_ENFORCEMENT_SETTINGS, (permissions) => { + if (permissions.valid) { + storeErrorEventData(data); + } + }); + } +} + +/** + * Get ixdiag stored in LocalStorage and format to be added to request payload + * + * @returns {Object} Object with error codes and counts + */ +function getCachedErrors() { + if (!storage.localStorageIsEnabled()) { + return; + } + + const errors = {}; + let currentStorage; + + try { + currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}'); + } catch (e) { + logError('ix can not read ixdiag from localStorage.'); + return null; + } + + Object.keys(currentStorage).forEach((date) => { + Object.keys(currentStorage[date]).forEach((code) => { + if (typeof currentStorage[date][code] === 'number') { + errors[code] = errors[code] + ? errors[code] + currentStorage[date][code] + : currentStorage[date][code]; + } + }); + }); + + return errors; +} + +/** + * * Initialize Outstream Renderer * @param {Object} bid */ -function outstreamRenderer (bid) { +function outstreamRenderer(bid) { bid.renderer.push(() => { var config = { width: bid.width, @@ -957,7 +1139,13 @@ function outstreamRenderer (bid) { timeout: 3000 }; - window.IXOutstreamPlayer(bid.vastUrl, bid.adUnitCode, config); + // IXOutstreamPlayer supports both vastUrl and vastXml, so we can pass either. + // Since vastUrl is going to be deprecated from exchange response, vastXml takes priority. + if (bid.vastXml) { + window.IXOutstreamPlayer(bid.vastXml, bid.adUnitCode, config); + } else { + window.IXOutstreamPlayer(bid.vastUrl, bid.adUnitCode, config); + } }); } @@ -966,7 +1154,7 @@ function outstreamRenderer (bid) { * @param {string} id * @returns {Renderer} */ -function createRenderer (id) { +function createRenderer(id) { const renderer = Renderer.install({ id: id, url: RENDERER_URL, @@ -1000,6 +1188,12 @@ export const spec = { * @return {boolean} True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { + if (!hasRegisteredHandler) { + events.on(CONSTANTS.EVENTS.AUCTION_DEBUG, localStorageHandler); + events.on(CONSTANTS.EVENTS.AD_RENDER_FAILED, localStorageHandler); + hasRegisteredHandler = true; + } + const paramsVideoRef = deepAccess(bid, 'params.video'); const paramsSize = deepAccess(bid, 'params.size'); const mediaTypeBannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); @@ -1009,6 +1203,7 @@ export const spec = { const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); if (bid.hasOwnProperty('mediaType') && !(contains(SUPPORTED_AD_TYPES, bid.mediaType))) { + logWarn('IX Bid Adapter: media type is not supported.'); return false; } @@ -1020,26 +1215,31 @@ export const spec = { // since there is an ix bidder level size, make sure its valid const ixSize = getFirstSize(paramsSize); if (!ixSize) { - logError('ix bidder params: size has invalid format.'); + logError('IX Bid Adapter: size has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_INVALID_FORMAT }); return false; } // check if the ix bidder level size, is present in ad unit level if (!includesSize(bid.sizes, ixSize) && !(includesSize(mediaTypeVideoPlayerSize, ixSize)) && !(includesSize(mediaTypeBannerSizes, ixSize))) { - logError('ix bidder params: bid size is not included in ad unit sizes or player size.'); + logError('IX Bid Adapter: bid size is not included in ad unit sizes or player size.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_NOT_INCLUDED }); return false; } } if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { - logError('ix bidder params: siteId must be string or number value.'); + logError('IX Bid Adapter: siteId must be string or number type.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + return false; + } + + if (typeof bid.params.siteId !== 'string' && isNaN(Number(bid.params.siteId))) { + logError('IX Bid Adapter: siteId must valid value', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); return false; } if (hasBidFloor || hasBidFloorCur) { if (!(hasBidFloor && hasBidFloorCur && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur))) { - logError('ix bidder params: bidFloor / bidFloorCur parameter has invalid format.'); + logError('IX Bid Adapter: bidFloor / bidFloorCur parameter has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_FLOOR_INVALID_FORMAT }); return false; } } @@ -1048,7 +1248,7 @@ export const spec = { const errorList = checkVideoParams(mediaTypeVideoRef, paramsVideoRef); if (errorList.length) { errorList.forEach((err) => { - logError(err); + logError(err, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); }); return false; } @@ -1164,11 +1364,21 @@ export const spec = { bid = parseBid(innerBids[j], responseBody.cur, bidRequest); if (!deepAccess(bid, 'mediaTypes.video.renderer') && deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { - bid.mediaTypes.video.renderer = createRenderer(innerBids[j].bidId); + bid.renderer = createRenderer(innerBids[j].bidId); } bids.push(bid); } + + if (deepAccess(requestBid, 'ext.ixdiag.err')) { + if (storage.localStorageIsEnabled()) { + try { + storage.removeDataFromLocalStorage(LOCAL_STORAGE_KEY); + } catch (e) { + logError('ix can not clear ixdiag from localStorage.'); + } + } + } } return bids; diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 119fcdf142b..de509853fed 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -6,9 +6,9 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { Renderer } from '../src/Renderer.js'; -export const storage = getStorageManager(); const BIDDER_CODE = 'jixie'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const EVENTS_URL = 'https://hbtra.jixie.io/sync/hb?'; const JX_OUTSTREAM_RENDERER_URL = 'https://scripts.jixie.media/jxhbrenderer.1.1.min.js'; const REQUESTS_URL = 'https://hb.jixie.io/v2/hbpost'; diff --git a/modules/justIdSystem.js b/modules/justIdSystem.js new file mode 100644 index 00000000000..15b1c90da4e --- /dev/null +++ b/modules/justIdSystem.js @@ -0,0 +1,206 @@ +/** + * This module adds JustId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/justIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import { submodule } from '../src/hook.js' +import { loadExternalScript } from '../src/adloader.js' +import {includes} from '../src/polyfill.js'; + +const MODULE_NAME = 'justId'; +const EXTERNAL_SCRIPT_MODULE_CODE = 'justtag'; +const LOG_PREFIX = 'User ID - JustId submodule: '; +const GVLID = 160; +const DEFAULT_PARTNER = 'pbjs-just-id-module'; +const DEFAULT_ATM_VAR_NAME = '__atm'; + +const MODE_BASIC = 'BASIC'; +const MODE_COMBINED = 'COMBINED'; +const DEFAULT_MODE = MODE_BASIC; + +export const EX_URL_REQUIRED = new Error(`params.url is required in ${MODE_COMBINED} mode`); +export const EX_INVALID_MODE = new Error(`Invalid params.mode. Allowed values: ${MODE_BASIC}, ${MODE_COMBINED}`); + +/** @type {Submodule} */ +export const justIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * required for the gdpr enforcement module + */ + gvlid: GVLID, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{uid:string}} value + * @returns {{justId:string}} + */ + decode(value) { + utils.logInfo(LOG_PREFIX, 'decode', value); + const justId = value && value.uid; + return justId && {justId: justId}; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @param {ConsentData} consentData + * @param {(Object|undefined)} cacheIdObj + * @returns {IdResponse|undefined} + */ + getId(config, consentData, cacheIdObj) { + utils.logInfo(LOG_PREFIX, 'getId', config, consentData, cacheIdObj); + + var configWrapper + try { + configWrapper = new ConfigWrapper(config); + } catch (e) { + utils.logError(LOG_PREFIX, e); + } + + return configWrapper && { + callback: function(cbFun) { + try { + utils.logInfo(LOG_PREFIX, 'fetching uid...'); + + var uidProvider = configWrapper.isCombinedMode() + ? new CombinedUidProvider(configWrapper, consentData, cacheIdObj) + : new BasicUidProvider(configWrapper); + + uidProvider.getUid(justId => { + if (utils.isEmptyStr(justId)) { + utils.logError(LOG_PREFIX, 'empty uid!'); + cbFun(); + return; + } + cbFun({uid: justId}); + }, err => { + utils.logError(LOG_PREFIX, 'error during fetching', err); + cbFun(); + }); + } catch (e) { + utils.logError(LOG_PREFIX, 'Error during fetching...', e); + } + } + }; + } +}; + +export const ConfigWrapper = function(config) { + this.getConfig = function() { + return config; + } + + this.getMode = function() { + return (params().mode || DEFAULT_MODE).toUpperCase(); + } + + this.getPartner = function() { + return params().partner || DEFAULT_PARTNER; + } + + this.isCombinedMode = function() { + return this.getMode() === MODE_COMBINED; + } + + this.getAtmVarName = function() { + return params().atmVarName || DEFAULT_ATM_VAR_NAME; + } + + this.getUrl = function() { + const u = params().url; + const url = new URL(u); + url.searchParams.append('sourceId', this.getPartner()); + return url.toString(); + } + + function params() { + return config.params || {}; + } + + // validation + if (!includes([MODE_BASIC, MODE_COMBINED], this.getMode())) { + throw EX_INVALID_MODE; + } + + var url = params().url; + if (this.isCombinedMode() && (utils.isEmptyStr(url) || !utils.isStr(url))) { + throw EX_URL_REQUIRED; + } +} + +const CombinedUidProvider = function(configWrapper, consentData, cacheIdObj) { + const url = configWrapper.getUrl(); + + this.getUid = function(idCallback, errCallback) { + const scriptTag = loadExternalScript(url, EXTERNAL_SCRIPT_MODULE_CODE, () => { + utils.logInfo(LOG_PREFIX, 'script loaded', url); + + const eventDetails = { + detail: { + config: configWrapper.getConfig(), + consentData: consentData, + cacheIdObj: cacheIdObj + } + } + + scriptTag.dispatchEvent(new CustomEvent('prebidGetId', eventDetails)); + }) + + scriptTag.addEventListener('justIdReady', event => { + utils.logInfo(LOG_PREFIX, 'received justId', event); + idCallback(event.detail && event.detail.justId); + }); + + scriptTag.onerror = errCallback; + } +} + +const BasicUidProvider = function(configWrapper) { + const atmVarName = configWrapper.getAtmVarName(); + + this.getUid = function(idCallback, errCallback) { + var atm = getAtm(); + if (typeof atm !== 'function') { // it may be AsyncFunction, so we can't use utils.isFn + utils.logInfo(LOG_PREFIX, 'ATM function not found!', atmVarName, atm); + errCallback('ATM not found'); + return + } + + atm = function() { // stub is replaced after ATM is loaded so we must refer them directly by global variable + return getAtm().apply(this, arguments); + } + + atm('getReadyState', () => { + Promise.resolve(atm('getVersion')) // atm('getVersion') returns string || Promise + .then(atmVersion => { + utils.logInfo(LOG_PREFIX, 'ATM Version', atmVersion); + if (utils.isStr(atmVersion)) { // getVersion command was introduced in same ATM version as getUid command + atm('getUid', idCallback); + } else { + errCallback('ATM getUid not supported'); + } + }) + }); + } + + function getAtm() { + return jtUtils.getAtm(atmVarName); + } +} + +export const jtUtils = { + getAtm(atmVarName) { + return window[atmVarName]; + } +} + +submodule('userId', justIdSubmodule); diff --git a/modules/justIdSystem.md b/modules/justIdSystem.md new file mode 100644 index 00000000000..f58deef8010 --- /dev/null +++ b/modules/justIdSystem.md @@ -0,0 +1,70 @@ +## Just ID User ID Submodule + +For assistance setting up your module please contact us at [prebid@justtag.com](prebid@justtag.com). + +First, make sure to add the Just ID submodule to your Prebid.js package with: + +``` +gulp build --modules=userId,justIdSystem +``` + +### Modes + +- **BASIC** - in this mode we rely on Justtag library that already exists on publisher page. Typicaly that library expose global variable called `__atm` + +- **COMBINED** - Just ID generation process may differ between various cases depends on publishers. This mode combines our js library with prebid for ease of integration + +### Disclosure + +This module in `COMBINED` mode loads external JavaScript to generate optimal quality user ID. It is possible to retrieve user ID, without loading additional script by this module in `BASIC` mode. + +### Just ID Example + +ex. 1. Mode `COMBINED` + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'justId', + params: { + mode: 'COMBINED', + url: 'https://id.nsaudience.pl/getId.js', // required in COMBINED mode + partner: 'pbjs-just-id-module' // optional, may be required in some custom integrations with Justtag + } + }] + } +}); +``` + +ex. 2. Mode `BASIC` + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'justId', + params: { + mode: 'BASIC', // default + atmVarName: '__atm' // optional + } + }] + } +}); +``` + +### Prebid Params + +Individual params may be set for the Just ID Submodule. + +## Parameter Descriptions for the `userSync` Configuration Section +The below parameters apply only to the Just ID integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID of the module - `'justId'` | `'justId'` | +| params | Optional | Object | Details for Just ID syncing. | | +| params.mode | Optional | String | Mode in which the module works. Available Modes: `'COMBINED'`, `'BASIC'`(default) | `'COMBINED'` | +| params.atmVarName | Optional | String | Name of global object property that point to Justtag ATM Library. Defaults to `'__atm'` | `'__atm'` | +| params.url | Optional | String | API Url, **required** in `COMBINED` mode | `'https://id.nsaudience.pl/getId.js'` | +| params.partner | Optional | String | This is the Justtag Partner Id which may be required in some custom integrations with Justtag | `'some-publisher'` | diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index 56f9935ea6e..9993421ad1a 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -4,7 +4,7 @@ import { deepAccess } from '../src/utils.js'; const BIDDER_CODE = 'justpremium' const GVLID = 62 const ENDPOINT_URL = 'https://pre.ads.justpremium.com/v/2.0/t/xhr' -const JP_ADAPTER_VERSION = '1.8.1' +const JP_ADAPTER_VERSION = '1.8.2' const pixels = [] export const spec = { @@ -19,6 +19,7 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { const c = preparePubCond(validBidRequests) const dim = getWebsiteDim() + const ggExt = getGumGumParams() const payload = { zone: validBidRequests.map(b => { return parseInt(b.params.zone) @@ -32,7 +33,8 @@ export const spec = { wh: dim.innerHeight, c: c, id: validBidRequests[0].params.zone, - sizes: {} + sizes: {}, + ggExt: ggExt } validBidRequests.forEach(b => { const zone = b.params.zone @@ -253,4 +255,19 @@ function getWebsiteDim () { } } +function getGumGumParams () { + if (!window.top) return null + + const urlParams = new URLSearchParams(window.top.location.search) + const ggParams = { + 'ggAdbuyid': urlParams.get('gg_adbuyid'), + 'ggDealid': urlParams.get('gg_dealid'), + 'ggEadbuyid': urlParams.get('gg_eadbuyid') + } + + const checkIfEmpty = (obj) => Object.keys(obj).length === 0 ? null : obj + const removeNullEntries = (obj) => Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null)) + return checkIfEmpty(removeNullEntries(ggParams)) +} + registerBidder(spec) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 8332e720ae7..814aed59d85 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -9,12 +9,12 @@ * @requires module:modules/realTimeData */ -import { submodule } from '../src/hook.js'; -import { config } from '../src/config.js'; -import { ajaxBuilder } from '../src/ajax.js'; -import { logError } from '../src/utils.js'; -import find from 'core-js-pure/features/array/find.js'; -import { getGlobal } from '../src/prebidGlobal.js'; +import {submodule} from '../src/hook.js'; +import {config} from '../src/config.js'; +import {ajaxBuilder} from '../src/ajax.js'; +import {logError} from '../src/utils.js'; +import {find} from '../src/polyfill.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const SUBMODULE_NAME = 'jwplayer'; const segCache = {}; @@ -155,8 +155,10 @@ export function enrichAdUnits(adUnits) { if (!vat) { return; } + const contentId = getContentId(vat.mediaID); + const contentData = getContentData(vat.segments); const targeting = formatTargetingResponse(vat); - addTargetingToBids(adUnit.bids, targeting); + enrichBids(adUnit.bids, targeting, contentId, contentData); }; loadVat(jwTargeting, onVatResponse); }); @@ -235,6 +237,9 @@ export function getVatFromPlayer(playerID, mediaID) { }; } +/* + deprecated + */ export function formatTargetingResponse(vat) { const { segments, mediaID } = vat; const targeting = {}; @@ -243,23 +248,83 @@ export function formatTargetingResponse(vat) { } if (mediaID) { - const id = 'jw_' + mediaID; targeting.content = { - id + id: getContentId(mediaID) } } return targeting; } -function addTargetingToBids(bids, targeting) { - if (!bids || !targeting) { +export function getContentId(mediaID) { + if (!mediaID) { + return; + } + + return 'jw_' + mediaID; +} + +export function getContentData(segments) { + if (!segments || !segments.length) { + return; + } + + const formattedSegments = segments.reduce((convertedSegments, rawSegment) => { + convertedSegments.push({ + id: rawSegment, + value: rawSegment + }); + return convertedSegments; + }, []); + + return { + name: 'jwplayer', + ext: { + segtax: 502 + }, + segment: formattedSegments + }; +} + +export function addOrtbSiteContent(bid, contentId, contentData) { + if (!contentId && !contentData) { return; } - bids.forEach(bid => addTargetingToBid(bid, targeting)); + let ortb2 = bid.ortb2 || {}; + let site = ortb2.site = ortb2.site || {}; + let content = site.content = site.content || {}; + + if (contentId) { + content.id = contentId; + } + + if (contentData) { + const data = content.data = content.data || []; + data.push(contentData); + } + + bid.ortb2 = ortb2; } +function enrichBids(bids, targeting, contentId, contentData) { + if (!bids) { + return; + } + + bids.forEach(bid => { + addTargetingToBid(bid, targeting); + addOrtbSiteContent(bid, contentId, contentData); + }); +} + +/* + deprecated + */ export function addTargetingToBid(bid, targeting) { + if (!targeting) { + return; + } + const rtd = bid.rtd || {}; const jwRtd = {}; jwRtd[SUBMODULE_NAME] = Object.assign({}, rtd[SUBMODULE_NAME], { targeting }); diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 7fb1bb13d74..77f65909040 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -81,20 +81,30 @@ realTimeData = { # Usage for Bid Adapters: Implement the `buildRequests` function. When it is called, the `bidRequests` param will be an array of bids. -Each bid for which targeting information was found will conform to the following object structure: +Each bid for which targeting information was found will have a ortb2 param conforming to the [oRTB v2 object structure](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf). The `ortb2` object will contain our proprietaty targeting segments in a format compliant with the [IAB's segment taxonomy structure](https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/segtax.md). + +Example: ```javascript { adUnitCode: 'xyz', bidId: 'abc', ..., - rtd: { - jwplayer: { - targeting: { - segments: ['123', '456'], - content: { - id: 'jw_abc123' - } + ortb2: { + site: { + content: { + id: 'jw_abc123', + data: [{ + name: 'jwplayer', + ext: { + segtax: 502 + }, + segment: [{ + id: '123' + }, { + id: '456' + }] + }] } } } @@ -102,9 +112,15 @@ Each bid for which targeting information was found will conform to the following ``` where: -- `segments` is an array of jwpseg targeting segments, of type string. -- `content` is an object containing metadata for the media. It may contain the following information: - - `id` is a unique identifier for the specific media asset. +- `ortb2` is an object containing first party data + - `site` is an object containing page specific information + - `content` is an object containing metadata for the media. It may contain the following information: + - `id` is a unique identifier for the specific media asset + - `data` is an array containing segment taxonomy objects that have the following parameters: + - `name` is the `jwplayer` string indicating the provider name + - `ext.segtax` whose `502` value is the unique identifier for JW Player's proprietary taxonomy + - `segment` is an array containing the segment taxonomy values as an object where: + - `id` is the string representation of the data segment value. **Example:** diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 7bb8e580f2a..098e38b2c43 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -10,7 +10,7 @@ const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={I const SYNC_COUNT = 5; const GVLID = 972; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO] -const storage = getStorageManager(GVLID, BIDDER_CODE); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); let sessionId, lastPageUrl, @@ -73,8 +73,8 @@ export const spec = { let meta; if (adUnit.metadata && adUnit.metadata.landingPageDomain) { meta = { - clickUrl: adUnit.metadata.landingPageDomain, - advertiserDomains: [adUnit.metadata.landingPageDomain] + clickUrl: adUnit.metadata.landingPageDomain[0], + advertiserDomains: adUnit.metadata.landingPageDomain }; } bidResponses.push({ @@ -87,7 +87,7 @@ export const spec = { creativeId: adUnit.id, dealId: adUnit.targetingCustom, netRevenue: true, - currency: bidRequest.currency, + currency: adUnit.currency || bidRequest.currency, meta: meta }); } @@ -172,28 +172,6 @@ export const spec = { return spec._getCrbFromCookie(); }, - _getKruxUserId() { - return spec._getLocalStorageSafely('kxkar_user'); - }, - - _getKruxSegments() { - return spec._getLocalStorageSafely('kxkar_segs'); - }, - - _getKrux() { - const segmentsStr = spec._getKruxSegments(); - let segments = []; - - if (segmentsStr) { - segments = segmentsStr.split(','); - } - - return { - userID: spec._getKruxUserId(), - segments: segments - }; - }, - _getLocalStorageSafely(key) { try { return storage.getDataFromLocalStorage(key); @@ -205,7 +183,7 @@ export const spec = { _getUserIds(tdid, usp, gdpr) { const crb = spec._getCrb(); const userIds = { - kargoID: crb.userId, + kargoID: crb.lexId, clientID: crb.clientId, crbIDs: crb.syncIds || {}, optOut: crb.optOut, @@ -235,7 +213,6 @@ export const spec = { _getAllMetadata(tdid, usp, gdpr) { return { userIDs: spec._getUserIds(tdid, usp, gdpr), - krux: spec._getKrux(), pageURL: window.location.href, rawCRB: spec._readCookie('krg_crb'), rawCRBLocalStorage: spec._getLocalStorageSafely('krg_crb') diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 49be80e969c..80aa038a9f7 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -63,7 +63,7 @@ export const onBidWon = function (bid) { const adServerPrice = deepAccess(bid, 'adserverTargeting.hb_pb', 0); const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; if (isStr(bid.nurl) && bid.nurl !== '') { - const winNotificationUrl = replaceAuctionPrice(bid.nurl, cpm) + const winNotificationUrl = replaceAuctionPrice(bid.nurl, bid.originalCpm || cpm) .replace(/\${AUCTION_PRICE_CURRENCY}/g, cpmCurrency) .replace(/\${AD_SERVER_PRICE}/g, adServerPrice) .replace(/\${AD_SERVER_PRICE_CURRENCY}/g, adServerPriceCurrency); diff --git a/modules/kubientBidAdapter.js b/modules/kubientBidAdapter.js index 07c614230a7..46360572576 100644 --- a/modules/kubientBidAdapter.js +++ b/modules/kubientBidAdapter.js @@ -1,6 +1,7 @@ import { isArray, deepAccess } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'kubient'; const END_POINT = 'https://kssp.kbntx.ch/kubprebidjs'; @@ -23,22 +24,23 @@ export const spec = { return; } return validBidRequests.map(function (bid) { - let floor = 0.0; + let adSlot = { + bidId: bid.bidId, + zoneId: bid.params.zoneid || '' + }; + if (typeof bid.getFloor === 'function') { const mediaType = (Object.keys(bid.mediaTypes).length == 1) ? Object.keys(bid.mediaTypes)[0] : '*'; const sizes = bid.sizes || '*'; const floorInfo = bid.getFloor({currency: 'USD', mediaType: mediaType, size: sizes}); - if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { - floor = parseFloat(floorInfo.floor); + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD') { + let floor = parseFloat(floorInfo.floor) + if (!isNaN(floor) && floor > 0) { + adSlot.floor = parseFloat(floorInfo.floor); + } } } - let adSlot = { - bidId: bid.bidId, - zoneId: bid.params.zoneid || '', - floor: floor || 0.0 - }; - if (bid.mediaTypes.banner) { adSlot.banner = bid.mediaTypes.banner; } @@ -59,7 +61,11 @@ export const spec = { gdpr: (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0, consentGiven: kubientGetConsentGiven(bidderRequest.gdprConsent), uspConsent: bidderRequest.uspConsent - }; + } + + if (config.getConfig('coppa') === true) { + data.coppa = 1 + } if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { data.referer = bidderRequest.refererInfo.referer @@ -109,31 +115,39 @@ export const spec = { return bidResponses; }, getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncs = []; - let gdprParams = ''; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - gdprParams = `?consent_str=${gdprConsent.consentString}`; + let kubientSync = kubientGetSyncInclude(config); + + if (!syncOptions.pixelEnabled || kubientSync.image === 'exclude') { + return []; + } + + let values = {}; + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - gdprParams = gdprParams + `&gdpr=${Number(gdprConsent.gdprApplies)}`; + values['gdpr'] = Number(gdprConsent.gdprApplies); + } + if (typeof gdprConsent.consentString === 'string') { + values['consent'] = gdprConsent.consentString; } - gdprParams = gdprParams + `&consent_given=` + kubientGetConsentGiven(gdprConsent); - } - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: 'https://kdmp.kbntx.ch/init.html' + gdprParams - }); } - if (syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: 'https://kdmp.kbntx.ch/init.png' + gdprParams - }); + + if (uspConsent) { + values['usp'] = uspConsent; } - return syncs; + + return [{ + type: 'image', + url: 'https://matching.kubient.net/match/sp?' + encodeQueryData(values) + }]; } }; +function encodeQueryData(data) { + return Object.keys(data).map(function(key) { + return [key, data[key]].map(encodeURIComponent).join('='); + }).join('&'); +} + function kubientGetConsentGiven(gdprConsent) { let consentGiven = 0; if (typeof gdprConsent !== 'undefined') { @@ -149,4 +163,22 @@ function kubientGetConsentGiven(gdprConsent) { } return consentGiven; } + +function kubientGetSyncInclude(config) { + try { + let kubientSync = {}; + if (config.getConfig('userSync').filterSettings != null && typeof config.getConfig('userSync').filterSettings != 'undefined') { + let filterSettings = config.getConfig('userSync').filterSettings + if (filterSettings.iframe !== null && typeof filterSettings.iframe !== 'undefined') { + kubientSync.iframe = ((isArray(filterSettings.image.bidders) && filterSettings.iframe.bidders.indexOf('kubient') !== -1) || filterSettings.iframe.bidders === '*') ? filterSettings.iframe.filter : 'exclude'; + } + if (filterSettings.image !== null && typeof filterSettings.image !== 'undefined') { + kubientSync.image = ((isArray(filterSettings.image.bidders) && filterSettings.image.bidders.indexOf('kubient') !== -1) || filterSettings.image.bidders === '*') ? filterSettings.image.filter : 'exclude'; + } + } + return kubientSync; + } catch (e) { + return null; + } +} registerBidder(spec); diff --git a/modules/limelightDigitalBidAdapter.md b/modules/limelightDigitalBidAdapter.md index ab69ef8eaa4..a4abb6f1411 100644 --- a/modules/limelightDigitalBidAdapter.md +++ b/modules/limelightDigitalBidAdapter.md @@ -22,7 +22,7 @@ var adUnits = [{ bids: [{ bidder: 'limelightDigital', params: { - host: 'exchange.ortb.net', + host: 'exchange-9qao.ortb.net', adUnitId: 0, adUnitType: 'banner' } @@ -38,7 +38,7 @@ var videoAdUnit = [{ bids: [{ bidder: 'limelightDigital', params: { - host: 'exchange.ortb.net', + host: 'exchange-9qao.ortb.net', adUnitId: 0, adUnitType: 'video' } diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 91415daa497..68cbb3b2412 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -13,7 +13,7 @@ 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(null, MODULE_NAME); +export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); const calls = { ajaxGet: (url, onSuccess, onError, timeout) => { ajaxBuilder(timeout)( diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 5ef109aef96..1116fd99ba0 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -86,6 +86,8 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE bidResponse.readyToSend = 1; bidResponse.mediaType = args.mediaType == 'native' ? 2 : (args.mediaType == 'video' ? 4 : 1); bidResponse.floorData = args.floorData; + bidResponse.meta = args.meta; + if (!bidResponse.ttr) { bidResponse.ttr = time - bidResponse.start; } @@ -114,6 +116,9 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE let wonBid = cache.auctions[args.auctionId].bids[args.requestId]; wonBid.won = true; wonBid.floorData = args.floorData; + wonBid.rUp = args.rUp; + wonBid.meta = args.meta; + wonBid.dealId = args.dealId; if (wonBid.sendStatus != 0) { livewrappedAnalyticsAdapter.sendEvents(); } @@ -250,7 +255,8 @@ function getResponses(gdpr, auctionIds) { auctionId: auctionIdPos, auc: bid.auc, buc: bid.buc, - lw: bid.lw + lw: bid.lw, + meta: bid.meta }); } }); @@ -288,7 +294,10 @@ function getWins(gdpr, auctionIds) { auctionId: auctionIdPos, auc: bid.auc, buc: bid.buc, - lw: bid.lw + lw: bid.lw, + rUp: bid.rUp, + meta: bid.meta, + dealId: bid.dealId }); } }); diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 84b80ac14d4..32e09e4b28e 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -1,13 +1,12 @@ -import { isSafariBrowser, deepAccess, getWindowTop } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import find from 'core-js-pure/features/array/find.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; - -export const storage = getStorageManager(); +import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {find} from '../src/polyfill.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'livewrapped'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const URL = 'https://lwadm.com/ad'; const VERSION = '1.4'; @@ -60,11 +59,17 @@ export const spec = { const bundle = find(bidRequests, hasBundleParam); const tid = find(bidRequests, hasTidParam); const schain = bidRequests[0].schain; + let ortb2 = config.getConfig('ortb2'); + const eids = handleEids(bidRequests); 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); + if (eids) { + ortb2 = mergeDeep(ortb2 || {}, eids); + } + const payload = { auctionId: auctionId ? auctionId.auctionId : undefined, publisherId: publisherId ? publisherId.params.publisherId : undefined, @@ -86,7 +91,7 @@ export const spec = { cookieSupport: !isSafariBrowser() && storage.cookiesAreEnabled(), rcv: getAdblockerRecovered(), adRequests: [...adRequests], - rtbData: handleEids(bidRequests), + rtbData: ortb2, schain: schain }; diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js new file mode 100644 index 00000000000..275ab38915d --- /dev/null +++ b/modules/lkqdBidAdapter.js @@ -0,0 +1,234 @@ +import { logError, _each, generateUUID, buildUrl } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'lkqd'; +const BID_TTL_DEFAULT = 300; +const MIMES_TYPES = ['application/x-mpegURL', 'video/mp4', 'video/H264']; +const PROTOCOLS = [1, 2, 3, 4, 5, 6, 7, 8]; + +const PARAM_VOLUME_DEFAULT = '100'; +const DEFAULT_SIZES = [[640, 480]]; + +function calculateSizes(VIDEO_BID, bid) { + const userProvided = bid.sizes && Array.isArray(bid.sizes) ? (Array.isArray(bid.sizes[0]) ? bid.sizes : [bid.sizes]) : DEFAULT_SIZES; + const preBidProvided = VIDEO_BID.playerSize && Array.isArray(VIDEO_BID.playerSize) ? (Array.isArray(VIDEO_BID.playerSize[0]) ? VIDEO_BID.playerSize : [VIDEO_BID.playerSize]) : null; + + return preBidProvided || userProvided; +} + +function isSet(value) { + return value != null; +} + +export const spec = { + code: BIDDER_CODE, + aliases: [], + supportedMediaTypes: [VIDEO], + isBidRequestValid: function(bid) { + return bid.bidder === BIDDER_CODE && bid.params && Object.keys(bid.params).length > 0 && + ((isSet(bid.params.publisherId) && parseInt(bid.params.publisherId) > 0) || (isSet(bid.params.placementId) && parseInt(bid.params.placementId) > 0)) && + bid.params.siteId != null; + }, + buildRequests: function(validBidRequests, bidderRequest) { + const BIDDER_REQUEST = bidderRequest || {}; + const serverRequestObjects = []; + const UTC_OFFSET = new Date().getTimezoneOffset(); + const UA = navigator.userAgent; + const IP = navigator.ip ? navigator.ip : 'prebid.js'; + const USP = BIDDER_REQUEST.uspConsent || null; + const REFERER = BIDDER_REQUEST.refererInfo ? new URL(BIDDER_REQUEST.refererInfo.referer).hostname : window.location.hostname; + const BIDDER_GDPR = BIDDER_REQUEST.gdprConsent && BIDDER_REQUEST.gdprConsent.gdprApplies ? 1 : null; + const BIDDER_GDPRS = BIDDER_REQUEST.gdprConsent && BIDDER_REQUEST.gdprConsent.consentString ? BIDDER_REQUEST.gdprConsent.consentString : null; + + _each(validBidRequests, (bid) => { + const DOMAIN = bid.params.pageurl || REFERER; + const GDPR = BIDDER_GDPR || bid.params.gdpr || null; + const GDPRS = BIDDER_GDPRS || bid.params.gdprs || null; + const DNT = bid.params.dnt || null; + const BID_FLOOR = bid.params.flrd > bid.params.flrmp ? bid.params.flrd : bid.params.flrmp; + const VIDEO_BID = bid.video ? bid.video : {}; + + const requestData = { + id: generateUUID(), + imp: [], + site: { + domain: DOMAIN + }, + device: { + ua: UA, + geo: { + utcoffset: UTC_OFFSET + }, + ip: IP + }, + user: { + ext: {} + }, + test: 0, + at: 2, + tmax: bid.params.timeout || config.getConfig('bidderTimeout') || 100, + cur: ['USD'], + regs: { + ext: { + us_privacy: USP + } + } + } + + if (isSet(DNT)) { + requestData.device.dnt = DNT; + } + + if (isSet(config.getConfig('coppa'))) { + requestData.regs.coppa = config.getConfig('coppa') === true ? 1 : 0; + } + + if (isSet(GDPR)) { + requestData.regs.ext.gdpr = GDPR; + requestData.regs.ext.gdprs = GDPRS; + } + + if (isSet(bid.params.aid) || isSet(bid.params.appname) || isSet(bid.params.bundleid)) { + requestData.app = { + id: bid.params.aid, + name: bid.params.appname, + bundle: bid.params.bundleid + } + + if (bid.params.contentId) { + requestData.app.content = { + id: bid.params.contentId, + title: bid.params.contentTitle, + len: bid.params.contentLength, + url: bid.params.contentUrl + }; + } + } + + if (isSet(bid.params.idfa) || isSet(bid.params.aid)) { + requestData.device.ifa = bid.params.idfa || bid.params.aid; + } + + if (bid.schain) { + requestData.source = { + ext: { + schain: bid.schain + } + }; + } else if (bid.params.schain) { + const section = bid.params.schain.split('!'); + const verComplete = section[0].split(','); + const node = section[1].split(','); + + requestData.source = { + ext: { + schain: { + validation: 'strict', + config: { + ver: verComplete[0], + complete: parseInt(verComplete[1]), + nodes: [ + { + asi: decodeURIComponent(node[0]), + sid: decodeURIComponent(node[1]), + hp: parseInt(node[2]), + rid: decodeURIComponent(node[3]), + name: decodeURIComponent(node[4]), + domain: decodeURIComponent(node[5]) + } + ] + } + } + } + }; + } + + _each(calculateSizes(VIDEO_BID, bid), (sizes) => { + const impObj = { + id: generateUUID(), + displaymanager: bid.bidder, + bidfloor: BID_FLOOR, + video: { + mimes: VIDEO_BID.mimes || MIMES_TYPES, + protocols: VIDEO_BID.protocols || PROTOCOLS, + nvol: bid.params.volume || PARAM_VOLUME_DEFAULT, + w: sizes[0], + h: sizes[1], + skip: VIDEO_BID.skip || 0, + playbackmethod: VIDEO_BID.playbackmethod || [1], + placement: (bid.params.execution === 'outstream' || VIDEO_BID.context === 'outstream') ? 5 : 1, + ext: { + lkqdcustomparameters: {} + }, + }, + bidfloorcur: 'USD', + secure: 1 + }; + + for (let k = 1; k <= 40; k++) { + if (bid.params.hasOwnProperty(`c${k}`) && bid.params[`c${k}`]) { + impObj.video.ext.lkqdcustomparameters[`c${k}`] = bid.params[`c${k}`]; + } + } + + requestData.imp.push(impObj); + }); + + serverRequestObjects.push({ + method: 'POST', + url: buildUrl({ + protocol: 'https', + hostname: 'rtb.lkqd.net', + pathname: '/ad', + search: { + pid: bid.params.publisherId || bid.params.placementId, + sid: bid.params.siteId, + output: 'rtb', + prebid: true + } + }), + data: requestData + }); + }); + + return serverRequestObjects; + }, + interpretResponse: function(serverResponse, bidRequest) { + const serverBody = serverResponse.body; + const bidResponses = []; + + if (serverBody && serverBody.seatbid) { + _each(serverBody.seatbid, (seatbid) => { + _each(seatbid.bid, (bid) => { + if (bid.price > 0) { + const bidResponse = { + requestId: bidRequest.id, + creativeId: bid.crid, + cpm: bid.price, + width: bid.w, + height: bid.h, + currency: serverBody.cur, + netRevenue: true, + ttl: BID_TTL_DEFAULT, + ad: bid.adm, + meta: { + advertiserDomains: bid.adomain && Array.isArray(bid.adomain) ? bid.adomain : [], + mediaType: VIDEO + } + }; + + bidResponses.push(bidResponse); + } + }); + }); + } else { + logError('Error: No server response or server response was empty for the requested URL'); + } + + return bidResponses; + } +} + +registerBidder(spec); diff --git a/modules/lkqdBidAdapter.md b/modules/lkqdBidAdapter.md index 1bd57ced4e0..9d7d24edda7 100644 --- a/modules/lkqdBidAdapter.md +++ b/modules/lkqdBidAdapter.md @@ -12,7 +12,7 @@ Connects to LKQD exchange for bids. LKQD bid adapter supports Video ads currently. -For more information about [LKQD Ad Serving and Management](http://www.lkqd.com/ad-serving-and-management/), please contact [info@lkqd.com](info@lkqd.com). +For more information about [LKQD Ad Serving and Management](http://www.lkqd.com/ad-serving-and-management/), please contact [vgi-video-prebid@verve.com](vgi-video-prebid@verve.com). # Sample Ad Unit: For Publishers ```javascript diff --git a/modules/loglyliftBidAdapter.js b/modules/loglyliftBidAdapter.js new file mode 100644 index 00000000000..e1319d08766 --- /dev/null +++ b/modules/loglyliftBidAdapter.js @@ -0,0 +1,79 @@ +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { NATIVE } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'loglylift'; +const ENDPOINT_URL = 'https://bid.logly.co.jp/prebid/client/v1'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [NATIVE], + + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.adspotId); + }, + + buildRequests: function (bidRequests, bidderRequest) { + const requests = []; + for (let i = 0, len = bidRequests.length; i < len; i++) { + const request = { + method: 'POST', + url: ENDPOINT_URL + '?adspot_id=' + bidRequests[i].params.adspotId, + data: JSON.stringify(newBidRequest(bidRequests[i], bidderRequest)), + options: {}, + bidderRequest + }; + requests.push(request); + } + return requests; + }, + + interpretResponse: function (serverResponse, { bidderRequest }) { + serverResponse = serverResponse.body; + const bidResponses = []; + if (!serverResponse || serverResponse.error) { + return bidResponses; + } + serverResponse.bids.forEach(function (bid) { + bidResponses.push(bid); + }) + return bidResponses; + }, + + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'iframe', + url: 'https://sync.logly.co.jp/sync/sync.html' + }); + } + return syncs; + } + +}; + +function newBidRequest(bid, bidderRequest) { + const currencyObj = config.getConfig('currency'); + const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; + + return { + auctionId: bid.auctionId, + bidderRequestId: bid.bidderRequestId, + transactionId: bid.transactionId, + adUnitCode: bid.adUnitCode, + bidId: bid.bidId, + mediaTypes: bid.mediaTypes, + params: bid.params, + prebidJsVersion: '$prebid.version$', + url: window.location.href, + domain: config.getConfig('publisherDomain'), + referer: bidderRequest.refererInfo.referer, + auctionStartTime: bidderRequest.auctionStart, + currency: currency, + timeout: config.getConfig('bidderTimeout') + }; +} + +registerBidder(spec); diff --git a/modules/loglyliftBidAdapter.md b/modules/loglyliftBidAdapter.md new file mode 100644 index 00000000000..9bca238b03e --- /dev/null +++ b/modules/loglyliftBidAdapter.md @@ -0,0 +1,55 @@ +# Overview +``` +Module Name: LOGLY lift for Publisher +Module Type: Bidder Adapter +Maintainer: dev@logly.co.jp +``` + +# Description +Module that connects to Logly's demand sources. +Currently module supports only native mediaType. + +# Test Parameters +``` +var adUnits = [ + // Native adUnit + { + code: 'test-native-code', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'loglylift', + params: { + adspotId: 4302078 + } + }] + } +]; +``` + +# UserSync example + +``` +pbjs.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: '*', // '*' represents all bidders + filter: 'include' + } + } + } +}); +``` diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 9abebb5533c..a03626d4a1f 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -4,10 +4,20 @@ * @module modules/lotamePanoramaId * @requires module:modules/userId */ -import { timestamp, isStr, logError, isBoolean, buildUrl, isEmpty, isArray } from '../src/utils.js'; +import { + timestamp, + isStr, + logError, + isBoolean, + buildUrl, + isEmpty, + isArray, + isEmptyStr +} 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 KEY_ID = 'panoramaId'; const KEY_EXPIRY = `${KEY_ID}_expiry`; @@ -19,7 +29,7 @@ const DAY_MS = 60 * 60 * 24 * 1000; const MISSING_CORE_CONSENT = 111; const GVLID = 95; -export const storage = getStorageManager(GVLID, MODULE_NAME); +export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); let cookieDomain; /** @@ -115,14 +125,23 @@ function saveLotameCache( /** * Retrieve all the cached values from cookies and/or local storage + * @param {Number} clientId */ -function getLotameLocalCache() { +function getLotameLocalCache(clientId = undefined) { let cache = { data: getFromStorage(KEY_ID), expiryTimestampMs: 0, + clientExpiryTimestampMs: 0, }; try { + if (clientId) { + const rawClientExpiry = getFromStorage(`${KEY_EXPIRY}_${clientId}`); + if (isStr(rawClientExpiry)) { + cache.clientExpiryTimestampMs = parseInt(rawClientExpiry, 10); + } + } + const rawExpiry = getFromStorage(KEY_EXPIRY); if (isStr(rawExpiry)) { cache.expiryTimestampMs = parseInt(rawExpiry, 10); @@ -191,11 +210,25 @@ export const lotamePanoramaIdSubmodule = { */ getId(config, consentData, cacheIdObj) { cookieDomain = lotamePanoramaIdSubmodule.findRootDomain(); - let localCache = getLotameLocalCache(); + const configParams = (config && config.params) || {}; + const clientId = configParams.clientId; + const hasCustomClientId = !isEmpty(clientId); + const localCache = getLotameLocalCache(clientId); - let refreshNeeded = Date.now() > localCache.expiryTimestampMs; + const hasExpiredPanoId = Date.now() > localCache.expiryTimestampMs; + + if (hasCustomClientId) { + const hasFreshClientNoConsent = Date.now() < localCache.clientExpiryTimestampMs; + if (hasFreshClientNoConsent) { + // There is no consent + return { + id: undefined, + reason: 'NO_CLIENT_CONSENT', + }; + } + } - if (!refreshNeeded) { + if (!hasExpiredPanoId) { return { id: localCache.data, }; @@ -203,6 +236,18 @@ export const lotamePanoramaIdSubmodule = { const storedUserId = getProfileId(); + // Add CCPA Consent data handling + const usp = uspDataHandler.getConsentData(); + + let usPrivacy; + if (typeof usp !== 'undefined' && !isEmpty(usp) && !isEmptyStr(usp)) { + usPrivacy = usp; + } + if (!usPrivacy) { + // fallback to 1st party cookie + usPrivacy = getFromStorage('us_privacy'); + } + const resolveIdFunction = function (callback) { let queryParams = {}; if (storedUserId) { @@ -226,6 +271,17 @@ export const lotamePanoramaIdSubmodule = { if (consentString) { queryParams.gdpr_consent = consentString; } + + // Add usPrivacy to the url + if (usPrivacy) { + queryParams.us_privacy = usPrivacy; + } + + // Add clientId to the url + if (hasCustomClientId) { + queryParams.c = clientId; + } + const url = buildUrl({ protocol: 'https', host: `id.crwdcntrl.net`, @@ -239,15 +295,31 @@ export const lotamePanoramaIdSubmodule = { if (response) { try { let responseObj = JSON.parse(response); - const shouldUpdateProfileId = !( + const hasNoConsentErrors = !( isArray(responseObj.errors) && responseObj.errors.indexOf(MISSING_CORE_CONSENT) !== -1 ); + if (hasCustomClientId) { + if (hasNoConsentErrors) { + clearLotameCache(`${KEY_EXPIRY}_${clientId}`); + } else if (isStr(responseObj.no_consent) && responseObj.no_consent === 'CLIENT') { + saveLotameCache( + `${KEY_EXPIRY}_${clientId}`, + responseObj.expiry_ts, + responseObj.expiry_ts + ); + + // End Processing + callback(); + return; + } + } + saveLotameCache(KEY_EXPIRY, responseObj.expiry_ts, responseObj.expiry_ts); if (isStr(responseObj.profile_id)) { - if (shouldUpdateProfileId) { + if (hasNoConsentErrors) { setProfileId(responseObj.profile_id); } @@ -262,7 +334,7 @@ export const lotamePanoramaIdSubmodule = { clearLotameCache(KEY_ID); } } else { - if (shouldUpdateProfileId) { + if (hasNoConsentErrors) { clearLotameCache(KEY_PROFILE); } clearLotameCache(KEY_ID); diff --git a/modules/lunamediahbBidAdapter.js b/modules/lunamediahbBidAdapter.js index 2798eef33e4..ebd88d34940 100644 --- a/modules/lunamediahbBidAdapter.js +++ b/modules/lunamediahbBidAdapter.js @@ -1,9 +1,11 @@ import { logMessage } from '../src/utils.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 = 'lunamediahb'; const AD_URL = 'https://balancer.lmgssp.com/?c=o&m=multi'; +const SYNC_URL = 'https://cookie.lmgssp.com'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -14,7 +16,7 @@ function isBidResponseValid(bid) { case BANNER: return Boolean(bid.width && bid.height && bid.ad); case VIDEO: - return Boolean(bid.vastUrl); + return Boolean(bid.vastUrl) || Boolean(bid.vastXml); case NATIVE: return Boolean(bid.native && bid.native.impressionTrackers); default: @@ -74,10 +76,13 @@ export const spec = { if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { placement.sizes = mediaType[BANNER].sizes; placement.traffic = BANNER; - } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { - placement.wPlayer = mediaType[VIDEO].playerSize[0]; - placement.hPlayer = mediaType[VIDEO].playerSize[1]; + } else if (mediaType && mediaType[VIDEO]) { + if (mediaType[VIDEO].playerSize) { + placement.wPlayer = mediaType[VIDEO].playerSize[0]; + placement.hPlayer = mediaType[VIDEO].playerSize[1]; + } placement.traffic = VIDEO; + placement.videoContext = mediaType[VIDEO].context || 'instream' } else if (mediaType && mediaType[NATIVE]) { placement.native = mediaType[NATIVE]; placement.traffic = NATIVE; @@ -102,6 +107,29 @@ export const spec = { } 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/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js new file mode 100755 index 00000000000..897dc3c8825 --- /dev/null +++ b/modules/luponmediaBidAdapter.js @@ -0,0 +1,570 @@ +import {isArray, logMessage, deepAccess, logWarn, parseSizesInput, deepSetValue, generateUUID, isEmpty, logError, _each, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER} from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; + +const BIDDER_CODE = 'luponmedia'; +const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; + +const DIGITRUST_PROP_NAMES = { + PREBID_SERVER: { + id: 'id', + keyv: 'keyv' + } +}; + +var sizeMap = { + 1: '468x60', + 2: '728x90', + 5: '120x90', + 7: '125x125', + 8: '120x600', + 9: '160x600', + 10: '300x600', + 13: '200x200', + 14: '250x250', + 15: '300x250', + 16: '336x280', + 17: '240x400', + 19: '300x100', + 31: '980x120', + 32: '250x360', + 33: '180x500', + 35: '980x150', + 37: '468x400', + 38: '930x180', + 39: '750x100', + 40: '750x200', + 41: '750x300', + 42: '2x4', + 43: '320x50', + 44: '300x50', + 48: '300x300', + 53: '1024x768', + 54: '300x1050', + 55: '970x90', + 57: '970x250', + 58: '1000x90', + 59: '320x80', + 60: '320x150', + 61: '1000x1000', + 64: '580x500', + 65: '640x480', + 66: '930x600', + 67: '320x480', + 68: '1800x1000', + 72: '320x320', + 73: '320x160', + 78: '980x240', + 79: '980x300', + 80: '980x400', + 83: '480x300', + 85: '300x120', + 90: '548x150', + 94: '970x310', + 95: '970x100', + 96: '970x210', + 101: '480x320', + 102: '768x1024', + 103: '480x280', + 105: '250x800', + 108: '320x240', + 113: '1000x300', + 117: '320x100', + 125: '800x250', + 126: '200x600', + 144: '980x600', + 145: '980x150', + 152: '1000x250', + 156: '640x320', + 159: '320x250', + 179: '250x600', + 195: '600x300', + 198: '640x360', + 199: '640x200', + 213: '1030x590', + 214: '980x360', + 221: '1x1', + 229: '320x180', + 230: '2000x1400', + 232: '580x400', + 234: '6x6', + 251: '2x2', + 256: '480x820', + 257: '400x600', + 258: '500x200', + 259: '998x200', + 264: '970x1000', + 265: '1920x1080', + 274: '1800x200', + 278: '320x500', + 282: '320x400', + 288: '640x380', + 548: '500x1000', + 550: '980x480', + 552: '300x200', + 558: '640x640' +}; + +_each(sizeMap, (item, key) => sizeMap[item] = key); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.siteId && bid.params.keyId); // TODO: check for siteId and keyId + }, + buildRequests: function (bidRequests, bidderRequest) { + const bRequest = { + method: 'POST', + url: ENDPOINT_URL, + data: null, + options: {}, + bidderRequest + }; + + let currentImps = []; + + for (let i = 0, len = bidRequests.length; i < len; i++) { + let newReq = newOrtbBidRequest(bidRequests[i], bidderRequest, currentImps); + currentImps = newReq.imp; + bRequest.data = JSON.stringify(newReq); + } + + return bRequest; + }, + interpretResponse: (response, request) => { + const bidResponses = []; + var respCur = 'USD'; + let parsedRequest = JSON.parse(request.data); + let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; + try { + if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + // Supporting multiple bid responses for same adSize + respCur = response.body.cur || respCur; + response.body.seatbid.forEach(seatbidder => { + seatbidder.bid && + isArray(seatbidder.bid) && + seatbidder.bid.forEach(bid => { + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid, + currency: respCur, + netRevenue: false, + ttl: 300, + referrer: parsedReferrer, + ad: bid.adm + }; + + bidResponses.push(newBid); + }); + }); + } + } catch (error) { + logError(error); + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + let allUserSyncs = []; + if (!hasSynced && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + responses.forEach(csResp => { + if (csResp.body && csResp.body.ext && csResp.body.ext.usersyncs) { + try { + let response = csResp.body.ext.usersyncs + let bidders = response.bidder_status; + for (let synci in bidders) { + let thisSync = bidders[synci]; + if (thisSync.no_cookie) { + let url = thisSync.usersync.url; + let type = thisSync.usersync.type; + + if (!url) { + logError(`No sync url for bidder luponmedia.`); + } else if ((type === 'image' || type === 'redirect') && syncOptions.pixelEnabled) { + logMessage(`Invoking image pixel user sync for luponmedia`); + allUserSyncs.push({type: 'image', url: url}); + } else if (type == 'iframe' && syncOptions.iframeEnabled) { + logMessage(`Invoking iframe user sync for luponmedia`); + allUserSyncs.push({type: 'iframe', url: url}); + } else { + logError(`User sync type "${type}" not supported for luponmedia`); + } + } + } + } catch (e) { + logError(e); + } + } + }); + } else { + logWarn('Luponmedia: Please enable iframe/pixel based user sync.'); + } + + hasSynced = true; + return allUserSyncs; + }, + onBidWon: bid => { + const bidString = JSON.stringify(bid); + spec.sendWinningsToServer(bidString); + }, + sendWinningsToServer: data => { + let mutation = `mutation {createWin(input: {win: {eventData: "${window.btoa(data)}"}}) {win {createTime } } }`; + let dataToSend = JSON.stringify({ query: mutation }); + + ajax('https://analytics.adxpremium.services/graphql', null, dataToSend, { + contentType: 'application/json', + method: 'POST' + }); + } +}; + +export function hasValidSupplyChainParams(schain) { + let isValid = false; + const requiredFields = ['asi', 'sid', 'hp']; + if (!schain.nodes) return isValid; + isValid = schain.nodes.reduce((status, node) => { + if (!status) return status; + return requiredFields.every(field => node[field]); + }, true); + if (!isValid) logError('LuponMedia: required schain params missing'); + return isValid; +} + +var hasSynced = false; + +export function resetUserSync() { + hasSynced = false; +} + +export function masSizeOrdering(sizes) { + const MAS_SIZE_PRIORITY = [15, 2, 9]; + + return sizes.sort((first, second) => { + // sort by MAS_SIZE_PRIORITY priority order + const firstPriority = MAS_SIZE_PRIORITY.indexOf(first); + const secondPriority = MAS_SIZE_PRIORITY.indexOf(second); + + if (firstPriority > -1 || secondPriority > -1) { + if (firstPriority === -1) { + return 1; + } + if (secondPriority === -1) { + return -1; + } + return firstPriority - secondPriority; + } + + // and finally ascending order + return first - second; + }); +} + +function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { + bidRequest.startTime = new Date().getTime(); + + const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); + + let bannerSizes = []; + + 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 }; + }); + + bannerSizes = format; + } + + const data = { + id: bidRequest.transactionId, + test: config.getConfig('debug') ? 1 : 0, + source: { + tid: bidRequest.transactionId + }, + tmax: config.getConfig('timeout') || 1500, + imp: currentImps.concat([{ + id: bidRequest.bidId, + secure: 1, + ext: { + [bidRequest.bidder]: bidRequest.params + }, + banner: { + format: bannerSizes + } + }]), + ext: { + prebid: { + targeting: { + includewinners: true, + // includebidderkeys always false for openrtb + includebidderkeys: false + } + } + }, + user: { + } + } + + let bidFloor; + if (isFn(bidRequest.getFloor) && !config.getConfig('disableFloors')) { + let floorInfo; + try { + floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'video', + size: parseSizes(bidRequest, 'video') + }); + } catch (e) { + logError('LuponMedia: getFloor threw an error: ', e); + } + bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; + } else { + bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + } + if (!isNaN(bidFloor)) { + data.imp[0].bidfloor = bidFloor; + } + + appendSiteAppDevice(data, bidRequest, bidderRequest); + + const digiTrust = _getDigiTrustQueryParams(bidRequest, 'PREBID_SERVER'); + if (digiTrust) { + deepSetValue(data, 'user.ext.digitrust', digiTrust); + } + + if (bidderRequest.gdprConsent) { + // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module + let gdprApplies; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + + deepSetValue(data, 'regs.ext.gdpr', gdprApplies); + deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent) { + deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // Set user uuid + deepSetValue(data, 'user.id', generateUUID()); + + // set crumbs + if (bidRequest.crumbs && bidRequest.crumbs.pubcid) { + deepSetValue(data, 'user.buyeruid', bidRequest.crumbs.pubcid); + } else { + deepSetValue(data, 'user.buyeruid', generateUUID()); + } + + if (bidRequest.userId && typeof bidRequest.userId === 'object' && + (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb || bidRequest.userId.idl_env)) { + deepSetValue(data, 'user.ext.eids', []); + + if (bidRequest.userId.tdid) { + data.user.ext.eids.push({ + source: 'adserver.org', + uids: [{ + id: bidRequest.userId.tdid, + ext: { + rtiPartner: 'TDID' + } + }] + }); + } + + if (bidRequest.userId.pubcid) { + data.user.ext.eids.push({ + source: 'pubcommon', + uids: [{ + id: bidRequest.userId.pubcid, + }] + }); + } + + // support liveintent ID + if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { + data.user.ext.eids.push({ + source: 'liveintent.com', + uids: [{ + id: bidRequest.userId.lipb.lipbid + }] + }); + + data.user.ext.tpid = { + source: 'liveintent.com', + uid: bidRequest.userId.lipb.lipbid + }; + + if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) { + deepSetValue(data, 'rp.target.LIseg', bidRequest.userId.lipb.segments); + } + } + + // support identityLink (aka LiveRamp) + if (bidRequest.userId.idl_env) { + data.user.ext.eids.push({ + source: 'liveramp.com', + uids: [{ + id: bidRequest.userId.idl_env + }] + }); + } + } + + if (config.getConfig('coppa') === true) { + deepSetValue(data, 'regs.coppa', 1); + } + + if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { + deepSetValue(data, 'source.ext.schain', bidRequest.schain); + } + + const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); + const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); + + if (!isEmpty(siteData) || !isEmpty(userData)) { + const bidderData = { + bidders: [ bidderRequest.bidderCode ], + config: { + fpd: {} + } + }; + + if (!isEmpty(siteData)) { + bidderData.config.fpd.site = siteData; + } + + if (!isEmpty(userData)) { + bidderData.config.fpd.user = userData; + } + + deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); + } + + const pbAdSlot = deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + if (typeof pbAdSlot === 'string' && pbAdSlot) { + deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); + } + + return data; +} + +function _getDigiTrustQueryParams(bidRequest = {}, endpointName) { + if (!endpointName || !DIGITRUST_PROP_NAMES[endpointName]) { + return null; + } + const propNames = DIGITRUST_PROP_NAMES[endpointName]; + + function getDigiTrustId() { + const bidRequestDigitrust = deepAccess(bidRequest, 'userId.digitrustid.data'); + if (bidRequestDigitrust) { + return bidRequestDigitrust; + } + + let digiTrustUser = (window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'}))); + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; + } + + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return null; + } + + const digiTrustQueryParams = { + [propNames.id]: digiTrustId.id, + [propNames.keyv]: digiTrustId.keyv + }; + if (propNames.pref) { + digiTrustQueryParams[propNames.pref] = 0; + } + return digiTrustQueryParams; +} + +function _getPageUrl(bidRequest, bidderRequest) { + let pageUrl = config.getConfig('pageUrl'); + if (bidRequest.params.referrer) { + pageUrl = bidRequest.params.referrer; + } else if (!pageUrl) { + pageUrl = bidderRequest.refererInfo.referer; + } + return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; +} + +function appendSiteAppDevice(data, bidRequest, bidderRequest) { + if (!data) return; + + // ORTB specifies app OR site + if (typeof config.getConfig('app') === 'object') { + data.app = config.getConfig('app'); + } else { + data.site = { + page: _getPageUrl(bidRequest, bidderRequest) + } + } + if (typeof config.getConfig('device') === 'object') { + data.device = config.getConfig('device'); + } +} + +/** + * @param sizes + * @returns {*} + */ +function mapSizes(sizes) { + return parseSizesInput(sizes) + // map sizes while excluding non-matches + .reduce((result, size) => { + let mappedSize = parseInt(sizeMap[size], 10); + if (mappedSize) { + result.push(mappedSize); + } + return result; + }, []); +} + +function parseSizes(bid, mediaType) { + let params = bid.params; + if (mediaType === 'video') { + let size = []; + if (params.video && params.video.playerWidth && params.video.playerHeight) { + size = [ + params.video.playerWidth, + params.video.playerHeight + ]; + } else if (Array.isArray(deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { + size = bid.mediaTypes.video.playerSize[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { + size = bid.sizes[0]; + } + return size; + } + + // Deprecated: temp legacy support + let sizes = []; + if (Array.isArray(params.sizes)) { + sizes = params.sizes; + } else if (typeof deepAccess(bid, 'mediaTypes.banner.sizes') !== 'undefined') { + sizes = mapSizes(bid.mediaTypes.banner.sizes); + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizes = mapSizes(bid.sizes) + } else { + logWarn('LuponMedia: no sizes are setup or found'); + } + + return masSizeOrdering(sizes); +} + +registerBidder(spec); diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js index a7c5b2a9dde..53f745d4004 100644 --- a/modules/malltvBidAdapter.js +++ b/modules/malltvBidAdapter.js @@ -9,7 +9,7 @@ const SIZE_SEPARATOR = ';'; const BISKO_ID = 'biskoId'; const STORAGE_ID = 'bisko-sid'; const SEGMENTS = 'biskoSegments'; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, @@ -41,7 +41,8 @@ export const spec = { let contents = []; let data = {}; let auctionId = bidderRequest ? bidderRequest.auctionId : ''; - + let gdrpApplies = true; + let gdprConsent = ''; let placements = validBidRequests.map(bidRequest => { if (!propertyId) { propertyId = bidRequest.params.propertyId; } if (!pageViewGuid && bidRequest.params) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } @@ -49,7 +50,8 @@ export const spec = { if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } if (Object.keys(data).length === 0 && bidRequest.params.data && Object.keys(bidRequest.params.data).length !== 0) { data = bidRequest.params.data; } - + if (bidderRequest && bidRequest.gdprConsent) { gdrpApplies = bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : true; } + if (bidderRequest && bidRequest.gdprConsent) { gdprConsent = bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : ''; } let adUnitId = bidRequest.adUnitCode; let placementId = bidRequest.params.placementId; let sizes = generateSizeParam(bidRequest.sizes); @@ -75,7 +77,9 @@ export const spec = { requestid: bidderRequestId, placements: placements, contents: contents, - data: data + data: data, + gdpr_applies: gdrpApplies, + gdpr_consent: gdprConsent, } return [{ diff --git a/modules/malltvBidAdapter.md b/modules/malltvBidAdapter.md index e32eb54f90f..6b695ee8526 100644 --- a/modules/malltvBidAdapter.md +++ b/modules/malltvBidAdapter.md @@ -1,68 +1,81 @@ # Overview + Module Name: MallTV Bidder Adapter Module Type: Bidder Adapter -Maintainer: arditb@gjirafa.com +Maintainer: myhedin@gjirafa.com # Description + MallTV Bidder Adapter for Prebid.js. # Test Parameters + ```js var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 300] - ] - } + { + code: "test-div", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 300], + ], + }, + }, + bids: [ + { + bidder: "malltv", + params: { + propertyId: "105134", //Required + placementId: "846832", //Required + data: { + //Optional + catalogs: [ + { + catalogId: 9, + items: ["193", "4", "1"], + }, + ], + inventory: { + category: ["tech"], + query: ["iphone 12"], + }, + }, }, - bids: [{ - bidder: 'malltv', - params: { - propertyId: '105134', //Required - placementId: '846832', //Required - data: { //Optional - catalogs: [{ - catalogId: 9, - items: ["193", "4", "1"] - }], - inventory: { - category: ["tech"], - query: ["iphone 12"] - } - } - } - }] + }, + ], + }, + { + code: "test-div", + mediaTypes: { + video: { + context: "instream", + }, }, - { - code: 'test-div', - mediaTypes: { - video: { - context: 'instream' - } + bids: [ + { + bidder: "malltv", + params: { + propertyId: "105134", //Required + placementId: "846832", //Required + data: { + //Optional + catalogs: [ + { + catalogId: 9, + items: ["193", "4", "1"], + }, + ], + inventory: { + category: ["tech"], + query: ["iphone 12"], + }, + }, }, - bids: [{ - bidder: 'malltv', - params: { - propertyId: '105134', //Required - placementId: '846832', //Required - data: { //Optional - catalogs: [{ - catalogId: 9, - items: ["193", "4", "1"] - }], - inventory: { - category: ["tech"], - query: ["iphone 12"] - } - } - } - }] - } + }, + ], + }, ]; ``` diff --git a/modules/mantisBidAdapter.js b/modules/mantisBidAdapter.js index 61b7c31c8e4..8d62b0ffba7 100644 --- a/modules/mantisBidAdapter.js +++ b/modules/mantisBidAdapter.js @@ -1,7 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -export const storage = getStorageManager(); +export const storage = getStorageManager({bidderCode: 'mantis'}); function inIframe() { try { diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js index 79e2148084a..92374b748c7 100644 --- a/modules/marsmediaBidAdapter.js +++ b/modules/marsmediaBidAdapter.js @@ -16,8 +16,7 @@ function MarsmediaAdapter() { let SUPPORTED_VIDEO_DELIVERY = [1]; let SUPPORTED_VIDEO_API = [1, 2, 5]; let slotsToBids = {}; - let that = this; - let version = '2.4'; + let version = '2.5'; this.isBidRequestValid = function (bid) { return !!(bid.params && bid.params.zoneId); @@ -288,7 +287,6 @@ function MarsmediaAdapter() { let bidRequest = slotsToBids[bid.impid]; let bidResponse = { requestId: bidRequest.bidId, - bidderCode: that.code, cpm: parseFloat(bid.price), width: bid.w, height: bid.h, diff --git a/modules/mass.js b/modules/mass.js index 01135e7ddff..f38f833f4d3 100644 --- a/modules/mass.js +++ b/modules/mass.js @@ -2,9 +2,9 @@ * This module adds MASS support to Prebid.js. */ -import { config } from '../src/config.js'; -import { getHook } from '../src/hook.js'; -import find from 'core-js-pure/features/array/find.js'; +import {config} from '../src/config.js'; +import {getHook} from '../src/hook.js'; +import {auctionManager} from '../src/auctionManager.js'; const defaultCfg = { dealIdPattern: /^MASS/i @@ -78,7 +78,7 @@ export function updateRenderers() { /** * Before hook for 'addBidResponse'. */ -export function addBidResponseHook(next, adUnitCode, bid) { +export function addBidResponseHook(next, adUnitCode, bid, {index = auctionManager.index} = {}) { let renderer; for (let i = 0; i < renderers.length; i++) { if (renderers[i].match(bid)) { @@ -88,9 +88,7 @@ export function addBidResponseHook(next, adUnitCode, bid) { } if (renderer) { - const bidRequest = find(this.bidderRequest.bids, bidRequest => - bidRequest.bidId === bid.requestId - ); + const bidRequest = index.getBidRequest(bid); matchedBids[bid.requestId] = { renderer, diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js index 7d4f22b7916..c686a2e378d 100644 --- a/modules/mediaforceBidAdapter.js +++ b/modules/mediaforceBidAdapter.js @@ -262,7 +262,7 @@ export const spec = { onBidWon: function(bid) { const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; if (isStr(bid.burl) && bid.burl !== '') { - bid.burl = replaceAuctionPrice(bid.burl, cpm); + bid.burl = replaceAuctionPrice(bid.burl, bid.originalCpm || cpm); triggerPixel(bid.burl); } }, diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js new file mode 100644 index 00000000000..b77c965802e --- /dev/null +++ b/modules/mediafuseBidAdapter.js @@ -0,0 +1,1145 @@ +import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, logError, logInfo, deepAccess, logMessage, convertTypes, isStr, getParameterByName, deepClone, chunk, logWarn, getBidRequest, createTrackPixelHtml, isEmpty, transformBidderParamKeywords, getMaxValueFromArray, fill, getMinValueFromArray, isArrayOfNums, isFn } from '../src/utils.js'; +import { Renderer } from '../src/Renderer.js'; +import { config } from '../src/config.js'; +import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO, ADPOD } from '../src/mediaTypes.js'; +import { auctionManager } from '../src/auctionManager.js'; +import {find, includes} from '../src/polyfill.js'; +import { OUTSTREAM, INSTREAM } from '../src/video.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { bidderSettings } from '../src/bidderSettings.js'; + +const BIDDER_CODE = 'mediafuse'; +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 APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately +const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; +const VIDEO_MAPPING = { + playback_method: { + 'unknown': 0, + 'auto_play_sound_on': 1, + 'auto_play_sound_off': 2, + 'click_to_play': 3, + 'mouse_over': 4, + 'auto_play_sound_unknown': 5 + }, + context: { + 'unknown': 0, + 'pre_roll': 1, + 'mid_roll': 2, + 'post_roll': 3, + 'outstream': 4, + 'in-banner': 5 + } +}; +const NATIVE_MAPPING = { + body: 'description', + body2: 'desc2', + cta: 'ctatext', + image: { + serverName: 'main_image', + requiredParams: { required: true } + }, + icon: { + serverName: 'icon', + requiredParams: { required: true } + }, + sponsoredBy: 'sponsored_by', + privacyLink: 'privacy_link', + salePrice: 'saleprice', + displayUrl: 'displayurl' +}; +const SOURCE = 'pbjs'; +const MAX_IMPS_PER_REQUEST = 15; +const mappingFileUrl = 'https://acdn.adnxs-simple.com/prebid/mediafuse-mapping/mappings.json'; +const SCRIPT_TAG_START = ' includes(USER_PARAMS, param)) + .forEach((param) => { + let uparam = convertCamelToUnderscore(param); + if (param === 'segments' && isArray(userObjBid.params.user[param])) { + let segs = []; + userObjBid.params.user[param].forEach(val => { + if (isNumber(val)) { + segs.push({'id': val}); + } else if (isPlainObject(val)) { + segs.push(val); + } + }); + userObj[uparam] = segs; + } else if (param !== 'segments') { + userObj[uparam] = userObjBid.params.user[param]; + } + }); + } + + const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo); + let appDeviceObj; + if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { + appDeviceObj = {}; + Object.keys(appDeviceObjBid.params.app) + .filter(param => includes(APP_DEVICE_PARAMS, param)) + .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]); + } + + const appIdObjBid = find(bidRequests, hasAppId); + let appIdObj; + if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = { + appid: appIdObjBid.params.app.id + }; + } + + let debugObj = {}; + let debugObjParams = {}; + const debugCookieName = 'apn_prebid_debug'; + const debugCookie = storage.getCookie(debugCookieName) || null; + + if (debugCookie) { + try { + debugObj = JSON.parse(debugCookie); + } catch (e) { + logError('MediaFuse Debug Auction Cookie Error:\n\n' + e); + } + } else { + const debugBidRequest = find(bidRequests, hasDebug); + if (debugBidRequest && debugBidRequest.debug) { + debugObj = debugBidRequest.debug; + } + } + + if (debugObj && debugObj.enabled) { + Object.keys(debugObj) + .filter(param => includes(DEBUG_PARAMS, param)) + .forEach(param => { + debugObjParams[param] = debugObj[param]; + }); + } + + const memberIdBid = find(bidRequests, hasMemberId); + const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; + const schain = bidRequests[0].schain; + const omidSupport = find(bidRequests, hasOmidSupport); + + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + }, + schain: schain + }; + + if (omidSupport) { + payload['iab_support'] = { + omidpn: 'Mediafuse', + omidpv: '$prebid.version$' + } + } + + if (member > 0) { + payload.member_id = member; + } + + if (appDeviceObjBid) { + payload.device = appDeviceObj + } + if (appIdObjBid) { + payload.app = appIdObj; + } + + let auctionKeywords = config.getConfig('mediafuseAuctionKeywords'); + if (isPlainObject(auctionKeywords)) { + let aucKeywords = transformBidderParamKeywords(auctionKeywords); + + if (aucKeywords.length > 0) { + aucKeywords.forEach(deleteValues); + } + + payload.keywords = aucKeywords; + } + + if (config.getConfig('adpod.brandCategoryExclusion')) { + payload.brand_category_uniqueness = true; + } + + if (debugObjParams.enabled) { + payload.debug = debugObjParams; + logInfo('MediaFuse Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + // note - objects for impbus use underscore instead of camelCase + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + + if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { + let ac = bidderRequest.gdprConsent.addtlConsent; + // pull only the ids from the string (after the ~) and convert them to an array of ints + let acStr = ac.substring(ac.indexOf('~') + 1); + payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); + } + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent + } + + if (bidderRequest && bidderRequest.refererInfo) { + let refererinfo = { + rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + } + payload.referrer_detection = refererinfo; + } + + const hasAdPodBid = find(bidRequests, hasAdPod); + if (hasAdPodBid) { + bidRequests.filter(hasAdPod).forEach(adPodBid => { + const adPodTags = createAdPodRequest(tags, adPodBid); + // don't need the original adpod placement because it's in adPodTags + const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); + payload.tags = [...nonPodTags, ...adPodTags]; + }); + } + + if (bidRequests[0].userId) { + let eids = []; + + addUserId(eids, deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); + 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 (eids.length) { + payload.eids = eids; + } + } + + if (tags[0].publisher_id) { + payload.publisher_id = tags[0].publisher_id; + } + + const request = formatRequest(payload, bidderRequest); + return request; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, { bidderRequest }) { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } + logError(errorMessage); + return bids; + } + + if (serverResponse.tags) { + serverResponse.tags.forEach(serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + const cpmCheck = (bidderSettings.get(bidderRequest.bidderCode, 'allowZeroCpmBids') === true) ? rtbBid.cpm >= 0 : rtbBid.cpm > 0; + if (cpmCheck && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + bids.push(bid); + } + } + }); + } + + if (serverResponse.debug && serverResponse.debug.debug_info) { + let debugHeader = 'MediaFuse Debug Auction for Prebid\n\n' + let debugText = debugHeader + serverResponse.debug.debug_info + debugText = debugText + .replace(/(|)/gm, '\t') // Tables + .replace(/(<\/td>|<\/th>)/gm, '\n') // Tables + .replace(/^
/gm, '') // Remove leading
+ .replace(/(
\n|
)/gm, '\n') //
+ .replace(/

(.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') // Header H1 + .replace(/(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') // Headers + .replace(/(<([^>]+)>)/igm, ''); // Remove any other tags + // logMessage('https://console.appnexus.com/docs/understanding-the-debug-auction'); + logMessage(debugText); + } + + return bids; + }, + + /** + * @typedef {Object} mappingFileInfo + * @property {string} url mapping file json url + * @property {number} refreshInDays prebid stores mapping data in localstorage so you can return in how many days you want to update value stored in localstorage. + * @property {string} localStorageKey unique key to store your mapping json in localstorage + */ + + /** + * Returns mapping file info. This info will be used by bidderFactory to preload mapping file and store data in local storage + * @returns {mappingFileInfo} + */ + getMappingFileInfo: function () { + return { + url: mappingFileUrl, + refreshInDays: 2 + } + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) { + return [{ + type: 'iframe', + url: 'https://acdn.adnxs.com/dmp/async_usersync.html' + }]; + } + }, + + transformBidParams: function (params, isOpenRtb) { + params = convertTypes({ + 'member': 'string', + 'invCode': 'string', + 'placementId': 'number', + 'keywords': transformBidderParamKeywords, + 'publisherId': 'number' + }, 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); + } + + Object.keys(params).forEach(paramKey => { + let convertedKey = convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + } + + return params; + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function (bid) { + if (bid.native) { + reloadViewabilityScriptWithCorrectParameters(bid); + } + } +} + +function isPopulatedArray(arr) { + return !!(isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function reloadViewabilityScriptWithCorrectParameters(bid) { + let viewJsPayload = getMediafuseViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); + + if (viewJsPayload) { + let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; + + let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload) + + let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); + + // find iframe containing script tag + let frameArray = document.getElementsByTagName('iframe'); + + // boolean var to modify only one script. That way if there are muliple scripts, + // they won't all point to the same creative. + let modifiedAScript = false; + + // first, loop on all ifames + for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { + let currentFrame = frameArray[i]; + try { + // IE-compatible, see https://stackoverflow.com/a/3999191/2112089 + let nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; + + if (nestedDoc) { + // if the doc is present, we look for our jstracker + let scriptArray = nestedDoc.getElementsByTagName('script'); + for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) { + let currentScript = scriptArray[j]; + if (currentScript.getAttribute('data-src') == jsTrackerSrc) { + currentScript.setAttribute('src', newJsTrackerSrc); + currentScript.setAttribute('data-src', ''); + if (currentScript.removeAttribute) { + currentScript.removeAttribute('data-src'); + } + modifiedAScript = true; + } + } + } + } catch (exception) { + // trying to access a cross-domain iframe raises a SecurityError + // this is expected and ignored + if (!(exception instanceof DOMException && exception.name === 'SecurityError')) { + // all other cases are raised again to be treated by the calling function + throw exception; + } + } + } + } +} + +function strIsMediafuseViewabilityScript(str) { + let regexMatchUrlStart = str.match(VIEWABILITY_URL_START); + let viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; + + let regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); + let fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; + + return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; +} + +function getMediafuseViewabilityScriptFromJsTrackers(jsTrackerArray) { + let viewJsPayload; + if (isStr(jsTrackerArray) && strIsMediafuseViewabilityScript(jsTrackerArray)) { + viewJsPayload = jsTrackerArray; + } else if (isArray(jsTrackerArray)) { + for (let i = 0; i < jsTrackerArray.length; i++) { + let currentJsTracker = jsTrackerArray[i]; + if (strIsMediafuseViewabilityScript(currentJsTracker)) { + viewJsPayload = currentJsTracker; + } + } + } + return viewJsPayload; +} + +function getViewabilityScriptUrlFromPayload(viewJsPayload) { + // extracting the content of the src attribute + // -> substring between src=" and " + let indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; // offset of 5: the length of 'src=' + 1 + let indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote); + let jsTrackerSrc = viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote); + return jsTrackerSrc; +} + +function hasPurpose1Consent(bidderRequest) { + let result = true; + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { + result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); + } + } + return result; +} + +function formatRequest(payload, bidderRequest) { + let request = []; + let options = { + withCredentials: true + }; + + let endpointUrl = URL; + + if (!hasPurpose1Consent(bidderRequest)) { + endpointUrl = URL_SIMPLE; + } + + if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { + options.customHeaders = { + 'X-Is-Test': 1 + } + } + + if (payload.tags.length > MAX_IMPS_PER_REQUEST) { + const clonedPayload = deepClone(payload); + + chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { + clonedPayload.tags = tags; + const payloadString = JSON.stringify(clonedPayload); + request.push({ + method: 'POST', + url: endpointUrl, + data: payloadString, + bidderRequest, + options + }); + }); + } else { + const payloadString = JSON.stringify(payload); + request = { + method: 'POST', + url: endpointUrl, + data: payloadString, + bidderRequest, + options + }; + } + + return request; +} + +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: rtbBid.renderer_id, + url: rtbBid.renderer_url, + config: rendererOptions, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + + renderer.setEventHandlers({ + impression: () => logMessage('MediaFuse outstream video impression event'), + loaded: () => logMessage('MediaFuse outstream video loaded event'), + ended: () => { + logMessage('MediaFuse outstream renderer video event'); + document.querySelector(`#${adUnitCode}`).style.display = 'none'; + } + }); + return renderer; +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, rtbBid, bidderRequest) { + const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm, + creativeId: rtbBid.creative_id, + dealId: rtbBid.deal_id, + currency: 'USD', + netRevenue: true, + ttl: 300, + adUnitCode: bidRequest.adUnitCode, + mediafuse: { + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code + } + }; + + // 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: [] }); + } + + if (rtbBid.advertiser_id) { + bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); + } + + // temporary function; may remove at later date if/when adserver fully supports dchain + function setupDChain(rtbBid) { + let dchain = { + ver: '1.0', + complete: 0, + nodes: [{ + bsid: rtbBid.buyer_member_id.toString() + }], + }; + + return dchain; + } + if (rtbBid.buyer_member_id) { + bid.meta = Object.assign({}, bid.meta, {dchain: setupDChain(rtbBid)}); + } + + if (rtbBid.brand_id) { + bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); + } + + if (rtbBid.rtb.video) { + // shared video properties used for all 3 contexts + Object.assign(bid, { + width: rtbBid.rtb.video.player_width, + height: rtbBid.rtb.video.player_height, + vastImpUrl: rtbBid.notify_url, + ttl: 3600 + }); + + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + switch (videoContext) { + case ADPOD: + const primaryCatId = getIabSubCategory(bidRequest.bidder, rtbBid.brand_category_id); + bid.meta = Object.assign({}, bid.meta, { primaryCatId }); + const dealTier = rtbBid.deal_priority; + bid.video = { + context: ADPOD, + durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000), + dealTier + }; + bid.vastUrl = rtbBid.rtb.video.asset_url; + break; + case OUTSTREAM: + bid.adResponse = serverBid; + bid.adResponse.ad = bid.adResponse.ads[0]; + bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; + bid.vastXml = rtbBid.rtb.video.content; + + if (rtbBid.renderer_url) { + const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); + const rendererOptions = deepAccess(videoBid, 'renderer.options'); + bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); + } + break; + case INSTREAM: + bid.vastUrl = rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url); + break; + } + } else if (rtbBid.rtb[NATIVE]) { + const nativeAd = rtbBid.rtb[NATIVE]; + + // setting up the jsTracker: + // we put it as a data-src attribute so that the tracker isn't called + // until we have the adId (see onBidWon) + let jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src='); + + let jsTrackers = nativeAd.javascript_trackers; + + if (jsTrackers == undefined) { + jsTrackers = jsTrackerDisarmed; + } else if (isStr(jsTrackers)) { + jsTrackers = [jsTrackers, jsTrackerDisarmed]; + } else { + jsTrackers.push(jsTrackerDisarmed); + } + + bid[NATIVE] = { + title: nativeAd.title, + body: nativeAd.desc, + body2: nativeAd.desc2, + cta: nativeAd.ctatext, + rating: nativeAd.rating, + sponsoredBy: nativeAd.sponsored, + privacyLink: nativeAd.privacy_link, + address: nativeAd.address, + downloads: nativeAd.downloads, + likes: nativeAd.likes, + phone: nativeAd.phone, + price: nativeAd.price, + salePrice: nativeAd.saleprice, + clickUrl: nativeAd.link.url, + displayUrl: nativeAd.displayurl, + clickTrackers: nativeAd.link.click_trackers, + impressionTrackers: nativeAd.impression_trackers, + javascriptTrackers: jsTrackers + }; + if (nativeAd.main_img) { + bid['native'].image = { + url: nativeAd.main_img.url, + height: nativeAd.main_img.height, + width: nativeAd.main_img.width, + }; + } + if (nativeAd.icon) { + bid['native'].icon = { + url: nativeAd.icon.url, + height: nativeAd.icon.height, + width: nativeAd.icon.width, + }; + } + } else { + Object.assign(bid, { + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + ad: rtbBid.rtb.banner.content + }); + try { + if (rtbBid.rtb.trackers) { + for (let i = 0; i < rtbBid.rtb.trackers[0].impression_urls.length; i++) { + const url = rtbBid.rtb.trackers[0].impression_urls[i]; + const tracker = createTrackPixelHtml(url); + bid.ad += tracker; + } + } + } catch (error) { + logError('Error appending tracking pixel', error); + } + } + + return bid; +} + +function bidToTag(bid) { + const tag = {}; + 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); + } else { + tag.code = bid.params.invCode; + } + tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.prebid = true; + tag.disable_psa = true; + let bidFloor = getBidFloor(bid); + if (bidFloor) { + tag.reserve = bidFloor; + } + if (bid.params.position) { + tag.position = { 'above': 1, 'below': 2 }[bid.params.position] || 0; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = transformSizes(bid.params.privateSizes); + } + if (bid.params.supplyType) { + tag.supply_type = bid.params.supplyType; + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.extInvCode) { + tag.ext_inv_code = bid.params.extInvCode; + } + if (bid.params.publisherId) { + tag.publisher_id = parseInt(bid.params.publisherId, 10); + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } + if (!isEmpty(bid.params.keywords)) { + let keywords = transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + + let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + tag.gpid = gpid; + } + + if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { + tag.ad_types.push(NATIVE); + if (tag.sizes.length === 0) { + tag.sizes = transformSizes([1, 1]); + } + + if (bid.nativeParams) { + const nativeRequest = buildNativeRequest(bid.nativeParams); + tag[NATIVE] = { layouts: [nativeRequest] }; + } + } + + const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = deepAccess(bid, 'mediaTypes.video.context'); + + if (videoMediaType && context === 'adpod') { + tag.hb_source = 7; + } else { + tag.hb_source = 1; + } + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } + + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; + } + + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'context': + case 'playback_method': + let type = bid.params.video[param]; + type = (isArray(type)) ? type[0] : type; + tag.video[param] = VIDEO_MAPPING[param][type]; + break; + // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks + case 'frameworks': + break; + default: + tag.video[param] = bid.params.video[param]; + } + }); + + if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { + tag['video_frameworks'] = bid.params.video.frameworks; + } + } + + // use IAB ORTB values if the corresponding values weren't already set by bid.params.video + if (videoMediaType) { + tag.video = tag.video || {}; + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_RTB_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'minduration': + case 'maxduration': + if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; + break; + case 'skip': + if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); + break; + case 'skipafter': + if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; + break; + case 'playbackmethod': + if (typeof tag.video['playback_method'] !== 'number') { + let type = videoMediaType[param]; + type = (isArray(type)) ? type[0] : type; + + // we only support iab's options 1-4 at this time. + if (type >= 1 && type <= 4) { + tag.video['playback_method'] = type; + } + } + break; + case 'api': + if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { + // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) + let apiTmp = videoMediaType[param].map(val => { + let v = (val === 4) ? 5 : (val === 5) ? 4 : val; + + if (v >= 1 && v <= 5) { + return v; + } + }).filter(v => v); + tag['video_frameworks'] = apiTmp; + } + break; + } + }); + } + + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + } + + if (bid.params.frameworks && isArray(bid.params.frameworks)) { + tag['banner_frameworks'] = bid.params.frameworks; + } + + let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); + if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { + tag.ad_types.push(BANNER); + } + + if (tag.ad_types.length === 0) { + delete tag.ad_types; + } + + return tag; +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if (isArray(requestSizes) && requestSizes.length === 2 && + !isArray(requestSizes[0])) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function hasMemberId(bid) { + return !!parseInt(bid.params.member, 10); +} + +function hasAppDeviceInfo(bid) { + if (bid.params) { + return !!bid.params.app + } +} + +function hasAppId(bid) { + if (bid.params && bid.params.app) { + return !!bid.params.app.id + } + return !!bid.params.app +} + +function hasDebug(bid) { + return !!bid.debug +} + +function hasAdPod(bid) { + return ( + bid.mediaTypes && + bid.mediaTypes.video && + bid.mediaTypes.video.context === ADPOD + ); +} + +function hasOmidSupport(bid) { + let hasOmid = false; + const bidderParams = bid.params; + const videoParams = bid.params.video; + if (bidderParams.frameworks && isArray(bidderParams.frameworks)) { + hasOmid = includes(bid.params.frameworks, 6); + } + if (!hasOmid && videoParams && videoParams.frameworks && isArray(videoParams.frameworks)) { + hasOmid = includes(bid.params.video.frameworks, 6); + } + return hasOmid; +} + +/** + * Expand an adpod placement into a set of request objects according to the + * total adpod duration and the range of duration seconds. Sets minduration/ + * maxduration video property according to requireExactDuration configuration + */ +function createAdPodRequest(tags, adPodBid) { + const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; + + const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); + const maxDuration = getMaxValueFromArray(durationRangeSec); + + const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); + let request = fill(...tagToDuplicate, numberOfPlacements); + + if (requireExactDuration) { + const divider = Math.ceil(numberOfPlacements / durationRangeSec.length); + const chunked = chunk(request, divider); + + // each configured duration is set as min/maxduration for a subset of requests + durationRangeSec.forEach((duration, index) => { + chunked[index].map(tag => { + setVideoProperty(tag, 'minduration', duration); + setVideoProperty(tag, 'maxduration', duration); + }); + }); + } else { + // all maxdurations should be the same + request.map(tag => setVideoProperty(tag, 'maxduration', maxDuration)); + } + + return request; +} + +function getAdPodPlacementNumber(videoParams) { + const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; + const minAllowedDuration = getMinValueFromArray(durationRangeSec); + const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); + + return requireExactDuration + ? Math.max(numberOfPlacements, durationRangeSec.length) + : numberOfPlacements; +} + +function setVideoProperty(tag, key, value) { + if (isEmpty(tag.video)) { tag.video = {}; } + tag.video[key] = value; +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); +} + +function buildNativeRequest(params) { + const request = {}; + + // map standard prebid native asset identifier to /ut parameters + // e.g., tag specifies `body` but /ut only knows `description`. + // mapping may be in form {tag: ''} or + // {tag: {serverName: '', requiredParams: {...}}} + Object.keys(params).forEach(key => { + // check if one of the forms is used, otherwise + // a mapping wasn't specified so pass the key straight through + const requestKey = + (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || + NATIVE_MAPPING[key] || + key; + + // required params are always passed on request + const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; + request[requestKey] = Object.assign({}, requiredParams, params[key]); + + // convert the sizes of image/icon assets to proper format (if needed) + const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); + if (isImageAsset && request[requestKey].sizes) { + let sizes = request[requestKey].sizes; + if (isArrayOfNums(sizes) || (isArray(sizes) && sizes.length > 0 && sizes.every(sz => isArrayOfNums(sz)))) { + request[requestKey].sizes = transformSizes(request[requestKey].sizes); + } + } + + if (requestKey === NATIVE_MAPPING.privacyLink) { + request.privacy_supported = true; + } + }); + + return request; +} + +/** + * This function hides google div container for outstream bids to remove unwanted space on page. Mediafuse renderer creates a new iframe outside of google iframe to render the outstream creative. + * @param {string} elementId element id + */ +function hidedfpContainer(elementId) { + var el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); + if (el[0]) { + el[0].style.setProperty('display', 'none'); + } +} + +function hideSASIframe(elementId) { + try { + // find script tag with id 'sas_script'. This ensures it only works if you're using Smart Ad Server. + const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']"); + if (el[0].nextSibling && el[0].nextSibling.localName === 'iframe') { + el[0].nextSibling.style.setProperty('display', 'none'); + } + } catch (e) { + // element not found! + } +} + +function outstreamRender(bid) { + hidedfpContainer(bid.adUnitCode); + hideSASIframe(bid.adUnitCode); + // push to render queue because ANOutstreamVideo may not be loaded yet + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + tagId: bid.adResponse.tag_id, + sizes: [bid.getSize().split('x')], + targetId: bid.adUnitCode, // target div id to render video + uuid: bid.adResponse.uuid, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, handleOutstreamRendererEvents.bind(null, bid)); + }); +} + +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else if (adType === NATIVE) { + return NATIVE; + } else { + return BANNER; + } +} + +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; + } + + 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/mediafuseBidAdapter.md b/modules/mediafuseBidAdapter.md new file mode 100644 index 00000000000..f9ed9835b94 --- /dev/null +++ b/modules/mediafuseBidAdapter.md @@ -0,0 +1,151 @@ +# Overview + +``` +Module Name: Mediafuse Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid-js@xandr.com +``` + +# Description + +Connects to Mediafuse exchange for bids. + +Mediafuse bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'mediafuse', + params: { + placementId: 13144370 + } + }] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'mediafuse', + params: { + placementId: 13232354, + allowSmallerSizes: true + } + }] + }, + // Video instream adUnit + { + code: 'video-instream', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + }, + }, + bids: [{ + bidder: 'mediafuse', + params: { + placementId: 13232361, + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'] + } + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. + // To note - mediafuse supports additional values for our system that are not part of the ORTB spec. If you want + // to use these values, they will have to be declared in the bids[].params.video object instead using the mediafuse syntax. + // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will + // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. + minduration: 1, + maxduration: 60, + skip: 0, // 1 - true, 0 - false + skipafter: 5, + playbackmethod: [2], // note - we only support options 1-4 at this time + api: [1,2,3] // note - option 6 is not supported at this time + } + }, + bids: [ + { + bidder: 'mediafuse', + params: { + placementId: 13232385, + video: { + skippable: true, + playback_method: 'auto_play_sound_off' + } + } + } + ] + }, + // Banner adUnit in a App Webview + // Only use this for situations where prebid.js is in a webview of an App + // See Prebid Mobile for displaying ads via an SDK + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + } + bids: [{ + bidder: 'mediafuse', + params: { + placementId: 13144370, + app: { + id: "B1O2W3M4AN.com.prebid.webview", + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier + aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier + md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID + sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID + windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier + } + } + } + }] + } +]; +``` diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index 72dca2f1add..5eb32a3f6e4 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -1,10 +1,28 @@ -import find from 'core-js-pure/features/array/find.js'; -import arrayFrom from 'core-js-pure/features/array/from'; -import { getWindowTop, isFn, logWarn, getDNT, deepAccess, isArray, inIframe, mergeDeep, isStr, isEmpty, deepSetValue, deepClone, parseUrl, cleanObj, logError, triggerPixel, isInteger, isNumber } 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 { createEidsArray } from './userId/eids.js'; +import {arrayFrom, find} from '../src/polyfill.js'; +import { + cleanObj, + deepAccess, + deepClone, + deepSetValue, + getDNT, + getWindowTop, + inIframe, + isArray, + isEmpty, + isFn, + isInteger, + isNumber, + isStr, + logError, + logWarn, + mergeDeep, + parseUrl, + triggerPixel +} 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 {createEidsArray} from './userId/eids.js'; const AUCTION_TYPE = 1; const BIDDER_CODE = 'mediakeys'; @@ -653,11 +671,6 @@ export const spec = { if (fpd.user) { mergeDeep(payload, { user: fpd.user }); } - // Here we can handle device.geo prop - const deviceGeo = deepAccess(fpd, 'device.geo'); - if (deviceGeo) { - mergeDeep(payload.device, { geo: deviceGeo }); - } const request = { method: 'POST', diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index e281dde8ad0..09ebbc9bc31 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -1,11 +1,23 @@ -import { triggerPixel, deepAccess, getWindowTop, uniques, groupBy, isEmpty, _map, isPlainObject, logInfo, logError } from '../src/utils.js'; +import { + _map, + deepAccess, + getWindowTop, + groupBy, + isEmpty, + isPlainObject, + logError, + logInfo, + triggerPixel, + uniques, + getHighestCpm +} from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import { ajax } from '../src/ajax.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity } from '../src/auction.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {ajax} from '../src/ajax.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../src/auction.js'; +import {includes} from '../src/polyfill.js'; const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -490,6 +502,27 @@ function _getSizes(mediaTypes, sizes) { } } +/* + - The code is used to determine if the current bid is higher than the previous bid. + - If it is, then the code will return true and if not, it will return false. + */ +function canSelectCurrentBid(previousBid, currentBid) { + if (!(previousBid instanceof Bid)) return false; + + // For first bid response the previous bid will be containing bid request obj + // in which the cpm would be undefined so the current bid can directly be selected. + const isFirstBidResponse = previousBid.cpm === undefined && currentBid.cpm !== undefined; + if (isFirstBidResponse) return true; + + // if there are 2 bids, get the highest bid + const selectedBid = getHighestCpm(previousBid, currentBid); + + // Return true if selectedBid is currentBid, + // The timeToRespond field is used as an identifier for distinguishing + // between the current iterating bid and the previous bid. + return selectedBid.timeToRespond === currentBid.timeToRespond; +} + function bidResponseHandler(bid) { const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId } = bid; const {originalCpm, bidderCode, creativeId, adId, currency} = bid; @@ -498,7 +531,7 @@ function bidResponseHandler(bid) { return; } let bidObj = auctions[auctionId].findBid('bidId', requestId); - if (!(bidObj instanceof Bid)) { + if (!canSelectCurrentBid(bidObj, bid)) { return; } Object.assign( @@ -511,7 +544,7 @@ function bidResponseHandler(bid) { bidObj.originalCpm = originalCpm || cpm; let dfpbd = deepAccess(bid, 'adserverTargeting.hb_pb'); if (!dfpbd) { - let priceGranularity = getPriceGranularity(mediaType, bid); + let priceGranularity = getPriceGranularity(bid); let priceGranularityKey = PRICE_GRANULARITY[priceGranularity]; dfpbd = bid[priceGranularityKey] || cpm; } diff --git a/modules/medianetRtdProvider.js b/modules/medianetRtdProvider.js index cd86bf891f3..07b1d66fbc5 100644 --- a/modules/medianetRtdProvider.js +++ b/modules/medianetRtdProvider.js @@ -1,7 +1,7 @@ -import { isStr, isEmptyStr, logError, mergeDeep, isFn, insertElement } from '../src/utils.js'; -import { submodule } from '../src/hook.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {insertElement, isEmptyStr, isFn, isStr, logError, mergeDeep} from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'medianet'; const SOURCE = MODULE_NAME + 'rtd'; diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index e442b01a115..427a16f1341 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -7,11 +7,11 @@ const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' const BIDDER_URL_TEST = 'https://bidder-test.mediasquare.fr/' const BIDDER_ENDPOINT_AUCTION = 'msq_prebid'; -const BIDDER_ENDPOINT_SYNC = 'cookie_sync'; const BIDDER_ENDPOINT_WINNING = 'winning'; export const spec = { code: BIDDER_CODE, + gvlid: 791, aliases: ['msq'], // short code supportedMediaTypes: [BANNER, NATIVE, VIDEO], /** @@ -32,10 +32,16 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { let codes = []; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; + let floor = {}; const test = config.getConfig('debug') ? 1 : 0; let adunitValue = null; Object.keys(validBidRequests).forEach(key => { adunitValue = validBidRequests[key]; + if (typeof adunitValue.getFloor === 'function') { + floor = adunitValue.getFloor({currency: 'EUR', mediaType: '*', size: '*'}); + } else { + floor = {}; + } codes.push({ owner: adunitValue.params.owner, code: adunitValue.params.code, @@ -43,12 +49,14 @@ export const spec = { bidId: adunitValue.bidId, auctionId: adunitValue.auctionId, transactionId: adunitValue.transactionId, - mediatypes: adunitValue.mediaTypes + mediatypes: adunitValue.mediaTypes, + floor: floor }); }); const payload = { codes: codes, - referer: encodeURIComponent(bidderRequest.refererInfo.referer) + referer: encodeURIComponent(bidderRequest.refererInfo.referer), + pbjs: '$prebid.version$' }; if (bidderRequest) { // modules informations (gdpr, ccpa, schain, userId) if (bidderRequest.gdprConsent) { @@ -109,6 +117,9 @@ export const spec = { if ('match' in value) { bidResponse['mediasquare']['match'] = value['match']; } + if ('hasConsent' in value) { + bidResponse['mediasquare']['hasConsent'] = value['hasConsent']; + } if ('native' in value) { bidResponse['native'] = value['native']; bidResponse['mediaType'] = 'native'; @@ -132,18 +143,11 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - let params = ''; - let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; 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; } else { - if (gdprConsent && typeof gdprConsent.consentString === 'string') { params += typeof gdprConsent.gdprApplies === 'boolean' ? `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}` : `&gdpr_consent=${gdprConsent.consentString}`; } - if (uspConsent && typeof uspConsent === 'string') { params += '&uspConsent=' + uspConsent } - return { - type: 'iframe', - url: endpoint + BIDDER_ENDPOINT_SYNC + '?type=iframe' + params - }; + return []; } }, @@ -153,19 +157,22 @@ export const spec = { */ onBidWon: function(bid) { // fires a pixel to confirm a winning bid - let params = []; + let params = {'pbjs': '$prebid.version$'}; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'requestId', 'auctionId'] if (bid.hasOwnProperty('mediasquare')) { - if (bid['mediasquare'].hasOwnProperty('bidder')) { params.push('bidder=' + bid['mediasquare']['bidder']); } - if (bid['mediasquare'].hasOwnProperty('code')) { params.push('code=' + bid['mediasquare']['code']); } - if (bid['mediasquare'].hasOwnProperty('match')) { params.push('match=' + bid['mediasquare']['match']); } + if (bid['mediasquare'].hasOwnProperty('bidder')) { params['bidder'] = bid['mediasquare']['bidder']; } + if (bid['mediasquare'].hasOwnProperty('code')) { params['code'] = bid['mediasquare']['code']; } + if (bid['mediasquare'].hasOwnProperty('match')) { params['match'] = bid['mediasquare']['match']; } + if (bid['mediasquare'].hasOwnProperty('hasConsent')) { params['hasConsent'] = bid['mediasquare']['hasConsent']; } }; for (let i = 0; i < paramsToSearchFor.length; i++) { - if (bid.hasOwnProperty(paramsToSearchFor[i])) { params.push(paramsToSearchFor[i] + '=' + bid[paramsToSearchFor[i]]); } + if (bid.hasOwnProperty(paramsToSearchFor[i])) { + params[paramsToSearchFor[i]] = bid[paramsToSearchFor[i]]; + if (typeof params[paramsToSearchFor[i]] == 'number') { params[paramsToSearchFor[i]] = params[paramsToSearchFor[i]].toString() } + } } - if (params.length > 0) { params = '?' + params.join('&'); } - ajax(endpoint + BIDDER_ENDPOINT_WINNING + params, null, undefined, {method: 'GET', withCredentials: true}); + ajax(endpoint + BIDDER_ENDPOINT_WINNING, null, JSON.stringify(params), {method: 'POST', withCredentials: true}); return true; } diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index 5339b653596..352c2d074e8 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -149,6 +149,11 @@ export const merkleIdSubmodule = { logInfo('User ID - merkleId stored id ' + storedId); const configParams = (config && config.params) || {}; + if (typeof configParams.endpoint !== 'string') { + logWarn('User ID - merkleId submodule endpoint string is not defined'); + configParams.endpoint = ID_URL + } + if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { logError('User ID - merkleId submodule does not currently handle consent strings'); return; diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index c811a0b2981..51b713c8958 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -7,7 +7,7 @@ import { getStorageManager } from '../src/storageManager.js'; const GVLID = 358; const DEFAULT_CUR = 'USD'; const BIDDER_CODE = 'mgid'; -export const storage = getStorageManager(GVLID, BIDDER_CODE); +export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); const ENDPOINT_URL = 'https://prebid.mgid.com/prebid/'; const LOG_WARN_PREFIX = '[MGID warn]: '; const LOG_INFO_PREFIX = '[MGID info]: '; diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js new file mode 100644 index 00000000000..1a9ccfdf824 --- /dev/null +++ b/modules/minutemediaBidAdapter.js @@ -0,0 +1,360 @@ +import { logWarn, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; + +const SUPPORTED_AD_TYPES = [VIDEO]; +const BIDDER_CODE = 'minutemedia'; +const ADAPTER_VERSION = '5.0.1'; +const TTL = 360; +const CURRENCY = 'USD'; +const SELLER_ENDPOINT = 'https://hb.minutemedia-prebid.com/'; +const MODES = { + PRODUCTION: 'hb-mm', + TEST: 'hb-mm-test' +} +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} + +export const spec = { + code: BIDDER_CODE, + gvlid: 918, + version: ADAPTER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function(bidRequest) { + if (!bidRequest.params) { + logWarn('no params have been set to MinuteMedia adapter'); + return false; + } + + if (!bidRequest.params.org) { + logWarn('org is a mandatory param for MinuteMedia adapter'); + return false; + } + + return true; + }, + buildRequests: function (bidRequests, bidderRequest) { + if (bidRequests.length === 0) { + return []; + } + + const requests = []; + + bidRequests.forEach(bid => { + requests.push(buildVideoRequest(bid, bidderRequest)); + }); + + return requests; + }, + interpretResponse: function({body}) { + const bidResponses = []; + + const bidResponse = { + requestId: body.requestId, + cpm: body.cpm, + width: body.width, + height: body.height, + creativeId: body.requestId, + currency: body.currency, + netRevenue: body.netRevenue, + ttl: body.ttl || TTL, + vastXml: body.vastXml, + mediaType: VIDEO + }; + + if (body.adomain && body.adomain.length) { + bidResponse.meta = {}; + bidResponse.meta.advertiserDomains = body.adomain + } + bidResponses.push(bidResponse); + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && response.body.userSyncURL) { + syncs.push({ + type: 'iframe', + url: response.body.userSyncURL + }); + } + if (syncOptions.pixelEnabled && isArray(response.body.userSyncPixels)) { + const pixels = response.body.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }) + syncs.push(...pixels) + } + } + return syncs; + } +}; + +registerBidder(spec); + +/** + * Get floor price + * @param bid {bid} + * @returns {Number} + */ +function getFloor(bid) { + if (!isFn(bid.getFloor)) { + return 0; + } + let floorResult = bid.getFloor({ + currency: CURRENCY, + mediaType: VIDEO, + size: '*' + }); + return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; +} + +/** + * Build the video request + * @param bid {bid} + * @param bidderRequest {bidderRequest} + * @returns {Object} + */ +function buildVideoRequest(bid, bidderRequest) { + const sellerParams = generateParameters(bid, bidderRequest); + const {params} = bid; + return { + method: 'GET', + url: getEndpoint(params.testMode), + data: sellerParams + }; +} + +/** + * Get the the ad size from the bid + * @param bid {bid} + * @returns {Array} + */ +function getSizes(bid) { + if (deepAccess(bid, 'mediaTypes.video.sizes')) { + return bid.mediaTypes.video.sizes[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + return bid.sizes[0]; + } + return []; +} + +/** + * Get schain string value + * @param schainObject {Object} + * @returns {string} + */ +function getSupplyChain(schainObject) { + if (isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; + }); + return scStr; +} + +/** + * Get encoded node value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !isEmpty(val) ? encodeURIComponent(val) : ''; +} + +/** + * Get preferred user-sync method based on publisher configuration + * @param bidderCode {string} + * @returns {string} + */ +function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return SUPPORTED_SYNC_METHODS.IFRAME; + } + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; + } +} + +/** + * Check if sync rule is supported + * @param syncRule {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; + } + const isInclude = syncRule.filter === 'include'; + const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && contains(bidders, bidderCode); +} + +/** + * Get the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : SELLER_ENDPOINT + MODES.PRODUCTION; +} + +/** + * get device type + * @param uad {ua} + * @returns {string} + */ +function getDeviceType(ua) { + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i + .test(ua.toLowerCase())) { + return '5'; + } + if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i + .test(ua.toLowerCase())) { + return '4'; + } + if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i + .test(ua.toLowerCase())) { + return '3'; + } + return '1'; +} + +/** + * Generate query parameters for the request + * @param bid {bid} + * @param bidderRequest {bidderRequest} + * @returns {Object} + */ +function generateParameters(bid, bidderRequest) { + const {params} = bid; + const timeout = config.getConfig('bidderTimeout'); + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const [width, height] = getSizes(bid); + const {bidderCode} = bidderRequest; + const domain = window.location.hostname; + + // fix floor price in case of NAN + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const requestParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: ADAPTER_VERSION, + auction_start: timestamp(), + ad_unit_code: getBidIdParameter('adUnitCode', bid), + tmax: timeout, + width: width, + height: height, + publisher_id: params.org, + floor_price: Math.max(getFloor(bid), params.floorPrice), + ua: navigator.userAgent, + bid_id: getBidIdParameter('bidId', bid), + bidder_request_id: getBidIdParameter('bidderRequestId', bid), + transaction_id: getBidIdParameter('transactionId', bid), + session_id: getBidIdParameter('auctionId', bid), + publisher_name: domain, + site_domain: domain, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + device_type: getDeviceType(navigator.userAgent) + }; + + const userIdsParam = getBidIdParameter('userId', bid); + if (userIdsParam) { + requestParams.userIds = JSON.stringify(userIdsParam); + } + + const ortb2Metadata = config.getConfig('ortb2') || {}; + if (ortb2Metadata.site) { + requestParams.site_metadata = JSON.stringify(ortb2Metadata.site); + } + if (ortb2Metadata.user) { + requestParams.user_metadata = JSON.stringify(ortb2Metadata.user); + } + + const playbackMethod = deepAccess(bid, 'mediaTypes.video.playbackmethod'); + if (playbackMethod) { + requestParams.playback_method = playbackMethod; + } + const placement = deepAccess(bid, 'mediaTypes.video.placement'); + if (placement) { + requestParams.placement = placement; + } + const pos = deepAccess(bid, 'mediaTypes.video.pos'); + if (pos) { + requestParams.pos = pos; + } + const minduration = deepAccess(bid, 'mediaTypes.video.minduration'); + if (minduration) { + requestParams.min_duration = minduration; + } + const maxduration = deepAccess(bid, 'mediaTypes.video.maxduration'); + if (maxduration) { + requestParams.max_duration = maxduration; + } + const skip = deepAccess(bid, 'mediaTypes.video.skip'); + if (skip) { + requestParams.skip = skip; + } + const linearity = deepAccess(bid, 'mediaTypes.video.linearity'); + if (linearity) { + requestParams.linearity = linearity; + } + + if (params.placementId) { + requestParams.placement_id = params.placementId; + } + + if (syncEnabled) { + const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + requestParams.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest.uspConsent) { + requestParams.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (params.ifa) { + requestParams.ifa = params.ifa; + } + + if (bid.schain) { + requestParams.schain = getSupplyChain(bid.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + requestParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); + requestParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + } + + return requestParams; +} diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md new file mode 100644 index 00000000000..348cc586e08 --- /dev/null +++ b/modules/minutemediaBidAdapter.md @@ -0,0 +1,51 @@ +#Overview + +Module Name: MinuteMedia Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: hb@minutemedia.com + + +# Description + +Module that connects to MinuteMedia's demand sources. + +The MinuteMedia adapter requires setup and approval from the MinuteMedia. Please reach out to hb@minutemedia.com to create an MinuteMedia account. + +The adapter supports Video(instream). + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `org` | required | String | MinuteMedia publisher Id provided by your MinuteMedia representative | "56f91cd4d3e3660002000033" +| `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 + +# Test Parameters +```javascript +var adUnits = [ + { + code: 'dfp-video-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + } + }, + bids: [{ + bidder: 'minutemedia', + params: { + org: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + placementId: '12345678', // Optional + testMode: false // Optional + } + }] + } + ]; +``` diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 2b1d6bdb118..41bae4d6568 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -1,4 +1,4 @@ -import * as utils from '../src/utils.js'; +import { formatQS, logInfo } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -43,15 +43,10 @@ export const spec = { payload.consent_string = bidderRequest.gdprConsent.consentString; payload.consent_required = bidderRequest.gdprConsent.gdprApplies; } - + const baseUrl = bidRequest.params.baseUrl || ENDPOINT_URL; return { method: 'POST', - url: - ENDPOINT_URL + - '?' + - utils.formatQS({ - t: bidRequest.params.apiKey, - }), + url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), data: JSON.stringify(payload), }; }); @@ -79,7 +74,7 @@ export const spec = { * @param {data} Containing timeout specific data */ onTimeout: function onTimeout(timeoutData) { - utils.logInfo('Missena - Timeout from adapter', timeoutData); + logInfo('Missena - Timeout from adapter', timeoutData); }, /** @@ -87,7 +82,7 @@ export const spec = { * @param {Bid} The bid that won the auction */ onBidWon: function (bid) { - utils.logInfo('Missena - Bid won', bid); + logInfo('Missena - Bid won', bid); }, }; diff --git a/modules/multibid/index.js b/modules/multibid/index.js index ef0771e291f..8081e40ccb2 100644 --- a/modules/multibid/index.js +++ b/modules/multibid/index.js @@ -8,7 +8,7 @@ import {setupBeforeHookFnOnce, getHook} from '../../src/hook.js'; import { logWarn, deepAccess, getUniqueIdentifierStr, deepSetValue, groupBy } from '../../src/utils.js'; -import events from '../../src/events.js'; +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'; diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index 06586b80102..e07a124665f 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -12,11 +12,84 @@ const TIME_TO_LIVE = 360 const SUPPORTED_AD_TYPES = [BANNER] +/** + * Keep track of bid data by keys + * @returns {Object} - Map of bid data that can be referenced by multiple keys + */ +const BidDataMap = () => { + const referenceMap = {} + const bids = [] + + /** + * Add a refence to the index by key value + * @param {String} key - The key to store the index reference + * @param {Integer} index - The index value of the bidData + */ + function adKeyReference(key, index) { + if (!referenceMap.hasOwnProperty(key)) { + referenceMap[key] = index + } + } + + /** + * Adds a bid to the map + * @param {Object} bid - Bid data + * @param {Array/String} keys - Keys to reference the index value + */ + function addBidData(bid, keys) { + const index = bids.length + bids.push(bid) + + if (Array.isArray(keys)) { + keys.forEach((key) => { + adKeyReference(String(key), index) + }) + return + } + + adKeyReference(String(keys), index) + } + + /** + * Get's the bid data refrerenced by the key + * @param {String} key - The key value to find the bid data by + * @returns {Object} - The bid data + */ + function getBidData(key) { + const stringKey = String(key) + if (referenceMap.hasOwnProperty(stringKey)) { + return bids[referenceMap[stringKey]] + } + } + + // Return API + return { + addBidData, + getBidData, + } +} + const bidRequestMap = {} const adUnitsRequested = {} +const extData = {} + +// Filtering +const adsToFilter = new Set() +const advertisersToFilter = new Set() +const campaignsToFilter = new Set() // Prebid adapter referrence doc: https://docs.prebid.org/dev-docs/bidder-adaptor.html +// Validity checks for optionsl paramters +const validParameter = { + url: (value) => typeof value === 'string', + placementId: (value) => { + const isString = typeof value === 'string' + const isNumber = typeof value === 'number' + return isString || isNumber + }, +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -30,7 +103,23 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return true + // We don't need any specific parameters to make a bid request + // If not parameters are supplied just verify it's the correct bidder code + if (!bid.params) return bid.bidder === BIDDER_CODE + + // Check if any supplied parameters are invalid + const hasInvalidParameters = Object.keys(bid.params).some((key) => { + const value = bid.params[key] + const validityCheck = validParameter[key] + + // We don't have a test for this so it's not a paramter we care about + if (!validityCheck) return false + + // Return if the check is not passed + return !validityCheck(value) + }) + + return !hasInvalidParameters }, /** @@ -43,8 +132,8 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { const placementIds = new Set() - const placmentBidIdMap = {} let placementId, pageUrl + const bidDataMap = BidDataMap() validBidRequests.forEach((request) => { pageUrl = deepAccess( request, @@ -57,13 +146,13 @@ export const spec = { placementIds.add(placementId) } - var key = placementId || request.adUnitCode - placmentBidIdMap[key] = { + const bidData = { bidId: request.bidId, size: getLargestSize(request.sizes), } + bidDataMap.addBidData(bidData, [placementId, request.adUnitCode]) }) - bidRequestMap[bidderRequest.bidderRequestId] = placmentBidIdMap + bidRequestMap[bidderRequest.bidderRequestId] = bidDataMap // Build adUnit data const adUnitData = { @@ -97,6 +186,20 @@ export const spec = { }, ] + // Add filtering + if (adsToFilter.size > 0) { + params.unshift({ key: 'ntv_atf', value: Array.from(adsToFilter).join(',') }) + } + + if (advertisersToFilter.size > 0) { + params.unshift({ key: 'ntv_avtf', value: Array.from(advertisersToFilter).join(',') }) + } + + if (campaignsToFilter.size > 0) { + params.unshift({ key: 'ntv_ctf', value: Array.from(campaignsToFilter).join(',') }) + } + + // Add placement IDs if (placementIds.size > 0) { // Convert Set to Array (IE 11 Safe) const placements = [] @@ -105,6 +208,7 @@ export const spec = { params.unshift({ key: 'ntv_ptd', value: placements.join(',') }) } + // Add GDPR params if (bidderRequest.gdprConsent) { // Put on the beginning of the qs param array params.unshift({ @@ -113,6 +217,7 @@ export const spec = { }) } + // Add USP params if (bidderRequest.uspConsent) { // Put on the beginning of the qs param array params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) @@ -169,6 +274,8 @@ export const spec = { }, } + if (bid.ext) extData[bid.id] = bid.ext + bidResponses.push(bidResponse) }) }) @@ -274,7 +381,15 @@ export const spec = { * Will be called when a bid from the adapter won the auction. * @param {Object} bid - The bid that won the auction */ - onBidWon: function (bid) {}, + onBidWon: function (bid) { + const ext = extData[bid.dealId] + + if (!ext) return + + appendFilterData(adsToFilter, ext.adsToFilter) + appendFilterData(advertisersToFilter, ext.advertisersToFilter) + appendFilterData(campaignsToFilter, ext.campaignsToFilter) + }, /** * Will be called when the adserver targeting has been set for a bid from the adapter. @@ -289,12 +404,14 @@ export const spec = { * @returns {String} - The bidId value associated with the corresponding placementId */ getAdUnitData: function (bidderRequestId, bid) { - var data = deepAccess(bidRequestMap, `${bidderRequestId}.${bid.impid}`) + const bidDataMap = bidRequestMap[bidderRequestId] - if (data) return data + const placementId = bid.impid + const adUnitCode = deepAccess(bid, 'ext.ad_unit_id') - var unitCode = deepAccess(bid, 'ext.ad_unit_id') - return deepAccess(bidRequestMap, `${bidderRequestId}.${unitCode}`) + return ( + bidDataMap.getBidData(adUnitCode) || bidDataMap.getBidData(placementId) + ) }, } registerBidder(spec) @@ -349,3 +466,14 @@ function getLargestSize(sizes, method = area) { * @returns The calculated area */ const area = (size) => size[0] * size[1] + +/** + * Save any filter data from winning bid requests for subsequent requests + * @param {Array} filter - The filter data bucket currently stored + * @param {Array} filterData - The filter data to add + */ +function appendFilterData(filter, filterData) { + if (filterData && Array.isArray(filterData) && filterData.length) { + filterData.forEach((ad) => filter.add(ad)) + } +} diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index afc409c19f6..365d692e614 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -4,6 +4,7 @@ import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; +const SYNC_ENDPOINT = 'https://statics.nextmillmedia.com/load-cookie.html?v=4'; const TIME_TO_LIVE = 360; export const spec = { @@ -12,21 +13,27 @@ export const spec = { isBidRequestValid: function(bid) { return !!( - bid.params.placement_id && isStr(bid.params.placement_id) + (bid.params.placement_id && isStr(bid.params.placement_id)) || (bid.params.group_id && isStr(bid.params.group_id)) ); }, buildRequests: function(validBidRequests, bidderRequest) { const requests = []; + window.nmmRefreshCounts = window.nmmRefreshCounts || {}; _each(validBidRequests, function(bid) { + window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; const postBody = { 'id': bid.auctionId, 'ext': { 'prebid': { 'storedrequest': { - 'id': getBidIdParameter('placement_id', bid.params) + 'id': getPlacementId(bid) } + }, + + 'nextMillennium': { + 'refresh_count': window.nmmRefreshCounts[bid.adUnitCode]++, } } } @@ -40,12 +47,16 @@ export const spec = { if (uspConsent) { postBody.regs.ext.us_privacy = uspConsent; } - if (typeof gdprConsent.gdprApplies !== 'undefined') { - postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; - } - if (typeof gdprConsent.consentString !== 'undefined') { - postBody.user = { - ext: { consent: gdprConsent.consentString } + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies !== 'undefined') { + postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; + } + + if (typeof gdprConsent.consentString !== 'undefined') { + postBody.user = { + ext: { consent: gdprConsent.consentString } + } } } } @@ -83,13 +94,69 @@ export const spec = { meta: { advertiserDomains: bid.adomain || [] }, + ad: bid.adm }); }); }); return bidResponses; - } + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + if (!syncOptions.iframeEnabled) { + return + } + + let syncurl = gdprConsent && gdprConsent.gdprApplies ? `${SYNC_ENDPOINT}&gdpr=1&gdpr_consent=${gdprConsent.consentString}` : SYNC_ENDPOINT + + 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)) + }) + } + + if (bidders.length) { + syncurl += `&bidders=${bidders.join(',')}` + } + + return [{ + type: 'iframe', + url: syncurl + }]; + }, }; +function getPlacementId(bid) { + const groupId = getBidIdParameter('group_id', bid.params) + const placementId = getBidIdParameter('placement_id', bid.params) + if (!groupId) return placementId + + let windowTop = getTopWindow(window) + let size = [] + if (bid.mediaTypes) { + if (bid.mediaTypes.banner) size = bid.mediaTypes.banner.sizes && bid.mediaTypes.banner.sizes[0] + if (bid.mediaTypes.video) size = bid.mediaTypes.video.playerSize + } + + const host = (windowTop && windowTop.location && windowTop.location.host) || '' + return `g${groupId};${size.join('x')};${host}` +} + +function getTopWindow(curWindow, nesting = 0) { + if (nesting > 10) { + return curWindow + } + + try { + if (curWindow.parent.document) { + return getTopWindow(curWindow.parent.window, ++nesting) + } + } catch (err) { + return curWindow + } +} + registerBidder(spec); diff --git a/modules/nextMillenniumBidAdapter.md b/modules/nextMillenniumBidAdapter.md index 048fe907ac7..136f97d94d5 100644 --- a/modules/nextMillenniumBidAdapter.md +++ b/modules/nextMillenniumBidAdapter.md @@ -21,8 +21,9 @@ Currently module supports only banner mediaType. bids: [{ bidder: 'nextMillennium', params: { - placement_id: '-1' + placement_id: '-1', + group_id: '6731' } }] }]; -``` \ No newline at end of file +``` diff --git a/modules/nextrollBidAdapter.js b/modules/nextrollBidAdapter.js index b5af7ec1486..4e82bc1cbda 100644 --- a/modules/nextrollBidAdapter.js +++ b/modules/nextrollBidAdapter.js @@ -1,18 +1,18 @@ import { deepAccess, - parseUrl, - isNumber, getBidIdParameter, - isPlainObject, + isArray, isFn, + isNumber, + isPlainObject, isStr, + parseUrl, replaceAuctionPrice, - isArray, } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from '../src/polyfill.js'; const BIDDER_CODE = 'nextroll'; const BIDDER_ENDPOINT = 'https://d.adroll.com/bid/prebid/'; @@ -244,8 +244,8 @@ function _buildResponse(bidResponse, bid) { return response; } -const privacyLink = 'https://info.evidon.com/pub_info/573'; -const privacyIcon = 'https://c.betrad.com/pub/icon1.png'; +const privacyLink = 'https://app.adroll.com/optout/personalized'; +const privacyIcon = 'https://s.adroll.com/j/ad-choices-small.png'; function _getNativeResponse(adm, price) { let baseResponse = { diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js new file mode 100644 index 00000000000..a59bb635875 --- /dev/null +++ b/modules/nexx360BidAdapter.js @@ -0,0 +1,149 @@ +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +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' + +export const spec = { + code: BIDDER_CODE, + aliases: ['revenuemaker'], // short code + supportedMediaTypes: [BANNER], + /** + * 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) { + return !!(bid.params.account && bid.params.tagId); + }, + /** + * 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]; + adUnits.push({ + account: adunitValue.params.account, + tagId: adunitValue.params.tagId, + label: adunitValue.adUnitCode, + bidId: adunitValue.bidId, + auctionId: adunitValue.auctionId, + transactionId: adunitValue.transactionId, + mediatypes: adunitValue.mediaTypes + }); + if (adunitValue.userIdAsEids) userEids = adunitValue.userIdAsEids; + }); + const payload = { + adUnits, + href: encodeURIComponent(bidderRequest.refererInfo.referer) + }; + 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, + }; + }, + /** + * 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 headerValue = serverResponse.headers.get('some-response-header'); + const bidResponses = []; + let bidResponse = null; + let value = null; + if (serverBody.hasOwnProperty('responses')) { + Object.keys(serverBody['responses']).forEach(key => { + value = serverBody['responses'][key]; + bidResponse = { + requestId: value['bidId'], + cpm: value['cpm'], + currency: value['currency'], + width: value['width'], + height: value['height'], + adUrl: `${CACHE_URL}?uuid=${value['uuid']}`, + ttl: value['ttl'], + creativeId: value['creativeId'], + netRevenue: true, + nexx360: { + 'ssp': value['bidder'], + 'consent': value['consent'], + 'tagId': value['tagId'] + }, + /* + meta: { + 'advertiserDomains': value['adomain'] + } + */ + }; + bidResponses.push(bidResponse); + }); + } + return bidResponses; + }, + + /** + * 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') && + 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' }; + 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; + } + +} +registerBidder(spec); diff --git a/modules/nexx360BidAdapter.md b/modules/nexx360BidAdapter.md new file mode 100644 index 00000000000..882d83cb24e --- /dev/null +++ b/modules/nexx360BidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Nexx360 Bid Adapter +Module Type: Bidder Adapter +Maintainer: gabriel@nexx360.io +``` + +# Description + +Connects to Nexx360 network for bids. + +Nexx360 bid adapter supports Banner only for the time being. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'nexx360', + params: { + account: '1067', + tagId: 'luvxjvgn' + } + }] + }, +]; +``` diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index d10c1d0e430..f788093f833 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -6,7 +6,7 @@ import { getStorageManager } from '../src/storageManager.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; -const storage = getStorageManager(GVLID, BIDDER_CODE); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); window.nobidVersion = '1.3.2'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index 4c3324d3fc0..ae9cc4c818f 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -5,24 +5,25 @@ * @requires module:modules/userId */ -import { logInfo } from '../src/utils.js'; +import { logInfo, getWindowLocation } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; /** @type {Submodule} */ export const novatiqIdSubmodule = { -/** -* used to link submodule with config -* @type {string} -*/ + /** + * used to link submodule with config + * @type {string} + */ name: 'novatiq', /** -* decode the stored id value for passing to bid requests -* @function -* @returns {novatiq: {snowflake: string}} -*/ + * decode the stored id value for passing to bid requests + * @function + * @returns {novatiq: {snowflake: string}} + */ decode(novatiqId, config) { let responseObj = { novatiq: { @@ -33,42 +34,202 @@ export const novatiqIdSubmodule = { }, /** -* performs action to obtain id and return a value in the callback's response argument -* @function -* @param {SubmoduleConfig} config -* @returns {id: string} -*/ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @returns {id: string} + */ getId(config) { - function snowflakeId(placeholder) { - return placeholder - ? (placeholder ^ Math.random() * 16 >> placeholder / 4).toString(16) - : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11 + 1e3).replace(/[018]/g, snowflakeId); - } - const configParams = config.params || {}; - const srcId = this.getSrcId(configParams); + const urlParams = this.getUrlParams(configParams); + const srcId = this.getSrcId(configParams, urlParams); + const sharedId = this.getSharedId(configParams); + const useCallbacks = this.useCallbacks(configParams); + + logInfo('NOVATIQ config params: ' + JSON.stringify(configParams)); logInfo('NOVATIQ Sync request used sourceid param: ' + srcId); + logInfo('NOVATIQ Sync request Shared ID: ' + sharedId); + + return this.sendSyncRequest(useCallbacks, sharedId, srcId, urlParams); + }, + + sendSyncRequest(useCallbacks, sharedId, sspid, urlParams) { + const syncUrl = this.getSyncUrl(sharedId, sspid, urlParams); + const url = syncUrl.url; + const novatiqId = syncUrl.novatiqId; + + // for testing + const sharedStatus = (sharedId != undefined && sharedId != false) ? 'Found' : 'Not Found'; + + if (useCallbacks) { + let res = this.sendAsyncSyncRequest(novatiqId, url); ; + res.sharedStatus = sharedStatus; + + return res; + } else { + this.sendSimpleSyncRequest(novatiqId, url); + + return { 'id': novatiqId, + 'sharedStatus': sharedStatus } + } + }, - let partnerhost; - partnerhost = window.location.hostname; - logInfo('NOVATIQ partner hostname: ' + partnerhost); + sendAsyncSyncRequest(novatiqId, url) { + logInfo('NOVATIQ Setting up ASYNC sync request'); + + const resp = function (callback) { + logInfo('NOVATIQ *** Calling ASYNC sync request'); + + function onSuccess(response, responseObj) { + let syncrc; + var novatiqIdJson = { syncResponse: 0 }; + syncrc = responseObj.status; + logInfo('NOVATIQ Sync Response Code:' + syncrc); + logInfo('NOVATIQ *** ASYNC request returned ' + syncrc); + if (syncrc === 200) { + novatiqIdJson = { 'id': novatiqId, syncResponse: 1 }; + } else { + if (syncrc === 204) { + novatiqIdJson = { 'id': novatiqId, syncResponse: 2 }; + } + } + callback(novatiqIdJson); + } + + ajax(url, + { success: onSuccess }, + undefined, { method: 'GET', withCredentials: false }); + } + + return {callback: resp}; + }, + + sendSimpleSyncRequest(novatiqId, url) { + logInfo('NOVATIQ Sending SIMPLE sync request'); - const novatiqId = snowflakeId(); - const url = 'https://spadsync.com/sync?sptoken=' + novatiqId + '&sspid=' + srcId + '&ssphost=' + partnerhost; ajax(url, undefined, undefined, { method: 'GET', withCredentials: false }); logInfo('NOVATIQ snowflake: ' + novatiqId); - return { 'id': novatiqId } }, - getSrcId(configParams) { - logInfo('NOVATIQ Configured sourceid param: ' + configParams.sourceid); + getNovatiqId(urlParams) { + // standard uuid format + let uuidFormat = [1e7] + -1e3 + -4e3 + -8e3 + -1e11; + if (urlParams.useStandardUuid == false) { + // novatiq standard uuid(like) format + uuidFormat = uuidFormat + 1e3; + } + + return (uuidFormat).replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ); + }, + + getSyncUrl(sharedId, sspid, urlParams) { + let novatiqId = this.getNovatiqId(urlParams); + + let url = 'https://spadsync.com/sync?' + urlParams.novatiqId + '=' + novatiqId; + + if (urlParams.useSspId) { + url = url + '&sspid=' + sspid; + } + + if (urlParams.useSspHost) { + let ssphost = getWindowLocation().hostname; + logInfo('NOVATIQ partner hostname: ' + ssphost); + + url = url + '&ssphost=' + ssphost; + } + + // append on the shared ID if we have one + if (sharedId != null) { + url = url + '&sharedId=' + sharedId; + } + + return { + url: url, + novatiqId: novatiqId + } + }, + + getUrlParams(configParams) { + let urlParams = { + novatiqId: 'snowflake', + useStandardUuid: false, + useSspId: true, + useSspHost: true + } + + if (typeof configParams.urlParams != 'undefined') { + if (configParams.urlParams.novatiqId != undefined) { + urlParams.novatiqId = configParams.urlParams.novatiqId; + } + if (configParams.urlParams.useStandardUuid != undefined) { + urlParams.useStandardUuid = configParams.urlParams.useStandardUuid; + } + if (configParams.urlParams.useSspId != undefined) { + urlParams.useSspId = configParams.urlParams.useSspId; + } + if (configParams.urlParams.useSspHost != undefined) { + urlParams.useSspHost = configParams.urlParams.useSspHost; + } + } + + return urlParams; + }, + + useCallbacks(configParams) { + return typeof configParams.useCallbacks != 'undefined' && configParams.useCallbacks === true; + }, - function isHex(str) { - var a = parseInt(str, 16); - return (a.toString(16) === str) + useSharedId(configParams) { + return typeof configParams.useSharedId != 'undefined' && configParams.useSharedId === true; + }, + + getCookieOrStorageID(configParams) { + let cookieOrStorageID = '_pubcid'; + + if (typeof configParams.sharedIdName != 'undefined' && configParams.sharedIdName != null && configParams.sharedIdName != '') { + cookieOrStorageID = configParams.sharedIdName; + logInfo('NOVATIQ sharedID name redefined: ' + cookieOrStorageID); + } + + return cookieOrStorageID; + }, + + // return null if we aren't supposed to use one or we are but there isn't one present + getSharedId(configParams) { + let sharedId = null; + if (this.useSharedId(configParams)) { + let cookieOrStorageID = this.getCookieOrStorageID(configParams); + const storage = getStorageManager({moduleName: 'pubCommonId'}); + + // first check local storage + if (storage.hasLocalStorage()) { + sharedId = storage.getDataFromLocalStorage(cookieOrStorageID); + logInfo('NOVATIQ sharedID retrieved from local storage:' + sharedId); + } + + // if nothing check the local cookies + if (sharedId == null) { + sharedId = storage.getCookie(cookieOrStorageID); + logInfo('NOVATIQ sharedID retrieved from cookies:' + sharedId); + } } + logInfo('NOVATIQ sharedID returning: ' + sharedId); + + return sharedId; + }, + + getSrcId(configParams, urlParams) { + if (urlParams.useSspId == false) { + logInfo('NOVATIQ Configured to NOT use sspid'); + return ''; + } + + logInfo('NOVATIQ Configured sourceid param: ' + configParams.sourceid); + let srcId; if (typeof configParams.sourceid === 'undefined' || configParams.sourceid === null || configParams.sourceid === '') { srcId = '000'; @@ -76,9 +237,6 @@ export const novatiqIdSubmodule = { } else if (configParams.sourceid.length < 3 || configParams.sourceid.length > 3) { srcId = '001'; logInfo('NOVATIQ sourceid param set to value 001 due to wrong size in config section 3 chars max e.g. 1ab'); - } else if (isHex(configParams.sourceid) == false) { - srcId = '002'; - logInfo('NOVATIQ sourceid param set to value 002 due to wrong format in config section expecting hex value only'); } else { srcId = configParams.sourceid; } diff --git a/modules/novatiqIdSystem.md b/modules/novatiqIdSystem.md index ce561a696e3..f33fc700311 100644 --- a/modules/novatiqIdSystem.md +++ b/modules/novatiqIdSystem.md @@ -1,8 +1,8 @@ -# Novatiq Snowflake ID +# Novatiq Hyper ID -Novatiq proprietary dynamic snowflake ID is a unique, non sequential and single use identifier for marketing activation. Our in network solution matches verification requests to telco network IDs, safely and securely inside telecom infrastructure. Novatiq snowflake ID can be used for identity validation and as a secured 1st party data delivery mechanism. +The Novatiq proprietary dynamic Hyper ID is a unique, non sequential and single use identifier for marketing activation. Our in network solution matches verification requests to telco network IDs safely and securely inside telecom infrastructure. The Novatiq Hyper ID can be used for identity validation and as a secured 1st party data delivery mechanism. -## Novatiq Snowflake ID Configuration +## Novatiq Hyper ID Configuration Enable by adding the Novatiq submodule to your Prebid.js package with: @@ -18,19 +18,80 @@ pbjs.setConfig({ userIds: [{ name: 'novatiq', params: { - sourceid '1a3', // change to the Partner Number you received from Novatiq + // change to the Partner Number you received from Novatiq + sourceid '1a3' } } }], - auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + // 50ms maximum auction delay, applies to all userId modules + auctionDelay: 50 } }); ``` -| Param under userSync.userIds[] | Scope | Type | Description | Example | +### Parameters for the Novatiq Module +| Param | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String | Module identification: `"novatiq"` | `"novatiq"` | | params | Required | Object | Configuration specifications for the Novatiq module. | | -| params.sourceid | Required | String | This is the Novatiq Partner Number obtained via Novatiq registration. | `1a3` | +| params.sourceid | Required | String | The Novatiq Partner Number obtained via Novatiq | `1a3` | +| params.useSharedId | Optional | Boolean | Use the sharedID module if it's activated. | `true` | +| params.sharedIdName | Optional | String | Same as the SharedID "name" parameter
Defaults to "_pubcid" | `"demo_pubcid"` | +| params.useCallbacks | Optional | Boolean | Use callbacks for custom integrations | `false` | +| params.urlParams | Optional | Object | Sync URl configuration for custom integrations | | +| params.urlParams.novatiqId | Optional | String | The name of the parameter used to indicate the novatiq ID uuid | `snowflake` | +| params.urlParams.useStandardUuid | Optional | Boolean | Use a standard UUID format, or the Novatiq UUID format | `false` | +| params.urlParams.useSspId | Optional | Boolean | Send the sspid (sourceid) along with the sync request | `false` | +| params.urlParams.useSspHost | Optional | Boolean | Send the ssphost along with the sync request | `false` | + +# Novatiq Hyper ID with Prebid SharedID support +You can make use of the Prebid.js SharedId module as follows. + +## Novatiq Hyper ID Configuration + +Enable by adding the Novatiq and SharedId submodule to your Prebid.js package with: + +``` +gulp build --modules=novatiqIdSystem,userId,pubCommonId +``` + +Module activation and configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: "pubCommonId", + storage: { + type: "cookie", + // optional: will default to _pubcid if left blank + name: "demo_pubcid", + + // expires in 1 years + expires: 365 + }, + bidders: [ 'adtarget' ] + }, + { + name: 'novatiq', + params: { + // change to the Partner Number you received from Novatiq + sourceid '1a3', + + // Use the sharedID module + useSharedId: true, + + // optional: will default to _pubcid if left blank. + // If not left blank must match "name" in the the module above + sharedIdName: 'demo_pubcid' + } + } + }], + // 50ms maximum auction delay, applies to all userId modules + auctionDelay: 50 + } +}); +``` If you have any questions, please reach out to us at prebid@novatiq.com. diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 40843d58d02..7d2989b2066 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -10,6 +10,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.10'; function isBidRequestValid(bid) { const adUnitSizes = getAdUnitSizes(bid); @@ -40,10 +41,10 @@ function buildRequests(validBidRequests, bidderRequest) { const openRtbBidRequestBanner = { id: bidderRequest.auctionId, tmax: DEFAULT_TIMEOUT, - at: 2, + at: 1, regs: { ext: { - gdpr: 1 + gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? 1 : 0 }, }, site: { @@ -55,17 +56,14 @@ function buildRequests(validBidRequests, bidderRequest) { consent: '' } }, - imp: [] + imp: [], + ext: { + adapterversion: ADAPTER_VERSION, + prebidversion: '$prebid.version$' + } }; - if (bidderRequest.hasOwnProperty('gdprConsent') && - bidderRequest.gdprConsent.hasOwnProperty('gdprApplies')) { - openRtbBidRequestBanner.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0 - } - - if (bidderRequest.hasOwnProperty('gdprConsent') && - bidderRequest.gdprConsent.hasOwnProperty('consentString') && - bidderRequest.gdprConsent.consentString.length > 0) { + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { openRtbBidRequestBanner.user.ext.consent = bidderRequest.gdprConsent.consentString } @@ -73,7 +71,7 @@ function buildRequests(validBidRequests, bidderRequest) { const sizes = getAdUnitSizes(bidRequest) .map(size => ({ w: size[0], h: size[1] })); - if (bidRequest.hasOwnProperty('mediaTypes') && + if (bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('banner')) { openRtbBidRequestBanner.site.id = bidRequest.params.assetKey; @@ -83,7 +81,8 @@ function buildRequests(validBidRequests, bidderRequest) { bidfloor: getFloor(bidRequest), banner: { format: sizes - } + }, + ext: bidRequest.params }); } }); @@ -122,7 +121,9 @@ function interpretResponse(openRtbBidResponse) { meta: { advertiserDomains: bid.adomain }, - nurl: bid.nurl + nurl: bid.nurl, + adapterVersion: ADAPTER_VERSION, + prebidVersion: '$prebid.version$' }; bidResponse.ad = bid.adm; @@ -158,11 +159,11 @@ function onBidWon(bid) { w.OG_PREBID_BID_OBJECT = { ...(bid && { ...bid }), } - if (bid && bid.hasOwnProperty('nurl') && bid.nurl.length > 0) ajax(bid['nurl'], null); + if (bid && bid.nurl) ajax(bid.nurl, null); } function onTimeout(timeoutData) { - ajax(`${TIMEOUT_MONITORING_HOST}/bid_timeout`, null, JSON.stringify(timeoutData[0]), { + ajax(`${TIMEOUT_MONITORING_HOST}/bid_timeout`, null, JSON.stringify({...timeoutData[0], location: window.location.href}), { method: 'POST', contentType: 'application/json' }); diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js index e0db143dc0f..aeb19e7c32c 100644 --- a/modules/oneVideoBidAdapter.js +++ b/modules/oneVideoBidAdapter.js @@ -10,6 +10,7 @@ export const spec = { SYNC_ENDPOINT1: 'https://pixel.advertising.com/ups/57304/sync?gdpr=&gdpr_consent=&_origin=0&redir=true', SYNC_ENDPOINT2: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1', supportedMediaTypes: ['video', 'banner'], + gvlid: 25, /** * Determines whether or not the given bid request is valid. * diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index c8899070e5e..89c614dba23 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -1,20 +1,20 @@ 'use strict'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { INSTREAM, OUTSTREAM } from '../src/video.js'; -import { Renderer } from '../src/Renderer.js'; -import find from 'core-js-pure/features/array/find.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { createEidsArray } from './userId/eids.js'; -import { deepClone } from '../src/utils.js'; +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 {getStorageManager} from '../src/storageManager.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {createEidsArray} from './userId/eids.js'; +import {deepClone} from '../src/utils.js'; const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; const BIDDER_CODE = 'onetag'; const GVLID = 241; -const storage = getStorageManager(GVLID); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); /** * Determines whether or not the given bid request is valid. @@ -347,11 +347,13 @@ function getSizes(sizes) { function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { let syncs = []; let params = ''; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - params += '&gdpr_consent=' + gdprConsent.consentString; + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); } + if (typeof gdprConsent.consentString === 'string') { + params += '&gdpr_consent=' + gdprConsent.consentString; + } } if (uspConsent && typeof uspConsent === 'string') { params += '&us_privacy=' + uspConsent; diff --git a/modules/open8BidAdapter.js b/modules/open8BidAdapter.js new file mode 100644 index 00000000000..7fa97235525 --- /dev/null +++ b/modules/open8BidAdapter.js @@ -0,0 +1,188 @@ +import { Renderer } from '../src/Renderer.js'; +import {ajax} from '../src/ajax.js'; +import { createTrackPixelHtml, getBidIdParameter, logError, logWarn, tryAppendQueryString } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'open8'; +const URL = 'https://as.vt.open8.com/v1/control/prebid'; +const AD_TYPE = { + VIDEO: 1, + BANNER: 2 +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO, BANNER], + + isBidRequestValid: function(bid) { + return !!(bid.params.slotKey); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + var requests = []; + for (var i = 0; i < validBidRequests.length; i++) { + var bid = validBidRequests[i]; + var queryString = ''; + var slotKey = getBidIdParameter('slotKey', bid.params); + queryString = tryAppendQueryString(queryString, 'slot_key', slotKey); + queryString = tryAppendQueryString(queryString, 'imp_id', generateImpId()); + queryString += ('bid_id=' + bid.bidId); + + requests.push({ + method: 'GET', + url: URL, + data: queryString + }); + } + return requests; + }, + + interpretResponse: function(serverResponse, request) { + var bidderResponse = serverResponse.body; + + if (!bidderResponse.isAdReturn) { + return []; + } + + var ad = bidderResponse.ad; + + const bid = { + slotKey: bidderResponse.slotKey, + userId: bidderResponse.userId, + impId: bidderResponse.impId, + media: bidderResponse.media, + ds: ad.ds, + spd: ad.spd, + fa: ad.fa, + pr: ad.pr, + mr: ad.mr, + nurl: ad.nurl, + requestId: ad.bidId, + cpm: ad.price, + creativeId: ad.creativeId, + dealId: ad.dealId, + currency: ad.currency || 'JPY', + netRevenue: true, + ttl: 360, // 6 minutes + meta: { + advertiserDomains: ad.adomain || [] + } + } + + if (ad.adType === AD_TYPE.VIDEO) { + const videoAd = bidderResponse.ad.video; + Object.assign(bid, { + vastXml: videoAd.vastXml, + width: videoAd.w, + height: videoAd.h, + renderer: newRenderer(bidderResponse), + adResponse: bidderResponse, + mediaType: VIDEO + }); + } else if (ad.adType === AD_TYPE.BANNER) { + const bannerAd = bidderResponse.ad.banner; + Object.assign(bid, { + width: bannerAd.w, + height: bannerAd.h, + ad: bannerAd.adm, + mediaType: BANNER + }); + if (bannerAd.imps) { + try { + bannerAd.imps.forEach(impTrackUrl => { + const tracker = createTrackPixelHtml(impTrackUrl); + bid.ad += tracker; + }); + } catch (error) { + logError('Error appending imp tracking pixel', error); + } + } + } + return [bid]; + }, + + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + if (syncOptions.iframeEnabled && serverResponses.length) { + const syncIFs = serverResponses[0].body.syncIFs; + if (syncIFs) { + syncIFs.forEach(sync => { + syncs.push({ + type: 'iframe', + url: sync + }); + }); + } + } + if (syncOptions.pixelEnabled && serverResponses.length) { + const syncPixs = serverResponses[0].body.syncPixels; + if (syncPixs) { + syncPixs.forEach(sync => { + syncs.push({ + type: 'image', + url: sync + }); + }); + } + } + return syncs; + }, + onBidWon: function(bid) { + if (!bid.nurl) { return; } + const winUrl = bid.nurl.replace( + /\$\{AUCTION_PRICE\}/, + bid.cpm + ); + ajax(winUrl, null); + } +} + +function generateImpId() { + var l = 16; + var c = 'abcdefghijklmnopqrstuvwsyz0123456789'; + var cl = c.length; + var r = ''; + for (var i = 0; i < l; i++) { + r += c[Math.floor(Math.random() * cl)]; + } + return r; +} + +function newRenderer(bidderResponse) { + const renderer = Renderer.install({ + id: bidderResponse.ad.bidId, + url: bidderResponse.ad.video.purl, + loaded: false, + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + logWarn('Prebid Error calling setRender on newRenderer', err); + } + + return renderer; +} + +function outstreamRender(bid) { + bid.renderer.push(() => { + window.op8.renderPrebid({ + vastXml: bid.vastXml, + adUnitCode: bid.adUnitCode, + slotKey: bid.slotKey, + impId: bid.impId, + userId: bid.userId, + media: bid.media, + ds: bid.ds, + spd: bid.spd, + fa: bid.fa, + pr: bid.pr, + mr: bid.mr, + adResponse: bid.adResponse, + mediaType: bid.mediaType + }); + }); +} + +registerBidder(spec); diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index 9476d2d2914..f515eb14011 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -1,8 +1,8 @@ -import { isNumber, deepAccess, isArray, flatten, convertTypes, parseSizesInput } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { ADPOD, BANNER, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import find from 'core-js-pure/features/array/find.js'; +import {convertTypes, deepAccess, flatten, isArray, isNumber, parseSizesInput} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {find} from '../src/polyfill.js'; const ENDPOINT = 'https://ghb.spotim.market/v2/auction'; const BIDDER_CODE = 'openweb'; diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index f67f8bd0c75..89140c0aacd 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -1,10 +1,23 @@ -import { logInfo, logError, getWindowLocation, parseQS, logMessage, _each, deepAccess, logWarn, _map, flatten, uniques, isEmpty, parseSizesInput } from '../src/utils.js'; +import { + _each, + _map, + deepAccess, + flatten, + getWindowLocation, + isEmpty, + logError, + logInfo, + logMessage, + logWarn, + parseQS, + parseSizesInput, + uniques +} from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import { ajax } from '../src/ajax.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {ajax} from '../src/ajax.js'; +import {find, includes} from '../src/polyfill.js'; export const AUCTION_STATES = { INIT: 'initialized', // auction has initialized diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index eb165e886e8..85dcfbb3b47 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,8 +1,18 @@ -import { deepAccess, convertTypes, isArray, inIframe, _map, deepSetValue, _each, parseSizesInput, parseUrl } from '../src/utils.js'; +import { + _each, + _map, + convertTypes, + deepAccess, + deepSetValue, + inIframe, + isArray, + parseSizesInput, + parseUrl +} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import includes from 'core-js-pure/features/array/includes.js' +import {includes} from '../src/polyfill.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', @@ -42,7 +52,12 @@ export const USER_ID_CODE_TO_QUERY_ARG = { novatiq: 'novatiqid', // Novatiq ID mwOpenLinkId: 'mwopenlinkid', // MediaWallah OpenLink ID dapId: 'dapid', // Akamai DAP ID - amxId: 'amxid' // AMX RTB ID + amxId: 'amxid', // AMX RTB ID + kpuid: 'kpuid', // Kinesso ID + publinkId: 'publinkid', // Publisher Link + naveggId: 'naveggid', // Navegg ID + imuid: 'imuid', // IM-UID by Intimate Merger + adtelligentId: 'adtelligentid' // Adtelligent ID }; export const spec = { diff --git a/modules/optimeraRtdProvider.js b/modules/optimeraRtdProvider.js index b7ce3c6c6d9..dfe8f1bfcf2 100644 --- a/modules/optimeraRtdProvider.js +++ b/modules/optimeraRtdProvider.js @@ -101,7 +101,7 @@ export function scoreFileRequest() { export function returnTargetingData(adUnits, config) { const targeting = {}; try { - adUnits.forEach(function(adUnit) { + adUnits.forEach((adUnit) => { if (optimeraTargeting[adUnit]) { targeting[adUnit] = {}; targeting[adUnit][optimeraKeyName] = [optimeraTargeting[adUnit]]; @@ -141,12 +141,11 @@ export function init(moduleConfig) { setScoresURL(); scoreFileRequest(); return true; - } else { - if (!_moduleParams.clientID) { - logError('Optimera clientID is missing in the Optimera RTD configuration.'); - } - return false; } + if (!_moduleParams.clientID) { + logError('Optimera clientID is missing in the Optimera RTD configuration.'); + } + return false; } /** @@ -163,7 +162,7 @@ export function init(moduleConfig) { export function setScoresURL() { const optimeraHost = window.location.host; const optimeraPathName = window.location.pathname; - let newScoresURL = `${scoresBaseURL}${clientID}/${optimeraHost}${optimeraPathName}.js`; + const newScoresURL = `${scoresBaseURL}${clientID}/${optimeraHost}${optimeraPathName}.js`; if (scoresURL !== newScoresURL) { scoresURL = newScoresURL; fetchScoreFile = true; @@ -173,7 +172,9 @@ export function setScoresURL() { } /** - * Set the scores for the divice if given. + * Set the scores for the device if given. + * Add data and insights to the winddow.optimera object. + * * @param {*} result * @returns {string} JSON string of Optimera Scores. */ @@ -184,6 +185,18 @@ export function setScores(result) { if (device !== 'default' && scores.device[device]) { scores = scores.device[device]; } + logInfo(scores); + window.optimera = window.optimera || {}; + window.optimera.data = window.optimera.data || {}; + window.optimera.insights = window.optimera.insights || {}; + Object.keys(scores).map((key) => { + if (key !== 'insights') { + window.optimera.data[key] = scores[key]; + } + }); + if (scores.insights) { + window.optimera.insights = scores.insights; + } } catch (e) { logError('Optimera score file could not be parsed.'); } diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 111c1876e14..682e11090a3 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -3,7 +3,38 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; -const storageManager = getStorageManager(); +const storageManager = getStorageManager({bidderCode: 'orbidder'}); + +/** + * Determines whether or not the given bid response is valid. + * + * @param {object} bidResponse The bid response to validate. + * @return boolean True if this is a valid bid response, and false if it is not valid. + */ +function isBidResponseValid(bidResponse) { + let requiredKeys = ['requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency']; + + switch (bidResponse.mediaType) { + case BANNER: + requiredKeys = requiredKeys.concat(['width', 'height', 'ad']); + break; + case NATIVE: + if (!bidResponse.native.hasOwnProperty('impressionTrackers')) { + return false + } + break; + default: + return false + } + + for (const key of requiredKeys) { + if (!bidResponse.hasOwnProperty(key)) { + return false + } + } + + return true +} /** * Determines whether or not the given bid response is valid. diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 439570e976e..e903f053c7e 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -27,9 +27,28 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [ NATIVE, BANNER ], isBidRequestValid: (bid) => { + if (typeof bid.params !== 'object') { + return false; + } + + if (typeof deepAccess(bid, 'params.publisher.id') !== 'string') { + return false; + } + + if (!!bid.params.tagid && typeof bid.params.tagid !== 'string') { + return false; + } + + if (!!bid.params.bcat && (typeof bid.params.bcat !== 'object' || !bid.params.bcat.every(item => typeof item === 'string'))) { + return false; + } + + if (!!bid.params.badv && (typeof bid.params.badv !== 'object' || !bid.params.badv.every(item => typeof item === 'string'))) { + return false; + } + return ( !!config.getConfig('outbrain.bidderUrl') && - !!deepAccess(bid, 'params.publisher.id') && !!(bid.nativeParams || bid.sizes) ); }, @@ -67,6 +86,13 @@ export const spec = { } } + if (typeof bid.getFloor === 'function') { + const floor = _getFloor(bid, bid.nativeParams ? NATIVE : BANNER); + if (floor) { + imp.bidfloor = floor; + } + } + return imp; }); @@ -190,7 +216,7 @@ export const spec = { registerBidder(spec); function parseNative(bid) { - const { assets, link, eventtrackers } = JSON.parse(bid.adm); + const { assets, link, privacy, eventtrackers } = JSON.parse(bid.adm); const result = { clickUrl: link.url, clickTrackers: link.clicktrackers || undefined @@ -202,6 +228,9 @@ function parseNative(bid) { result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; } }); + if (privacy) { + result.privacyLink = privacy; + } if (eventtrackers) { result.impressionTrackers = []; eventtrackers.forEach(tracker => { @@ -251,8 +280,8 @@ function getNativeAssets(bid) { if (bidParams.sizes) { const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; + w = parseInt(sizes[0], 10); + h = parseInt(sizes[1], 10); } asset[props.name] = { @@ -291,3 +320,15 @@ function transformSizes(requestSizes) { return []; } + +function _getFloor(bid, type) { + const floorInfo = bid.getFloor({ + currency: CURRENCY, + mediaType: type, + size: '*' + }); + if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + return parseFloat(floorInfo.floor); + } + return null; +} diff --git a/modules/outbrainBidAdapter.js~HEAD b/modules/outbrainBidAdapter.js~HEAD deleted file mode 100644 index 052122e95f1..00000000000 --- a/modules/outbrainBidAdapter.js~HEAD +++ /dev/null @@ -1,288 +0,0 @@ -// jshint esversion: 6, es3: false, node: true -'use strict'; - -import { - registerBidder -} from '../src/adapters/bidderFactory.js'; -import { NATIVE, BANNER } from '../src/mediaTypes.js'; -import * as utils from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'outbrain'; -const GVLID = 164; -const CURRENCY = 'USD'; -const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; -const NATIVE_PARAMS = { - title: { id: 0, name: 'title' }, - icon: { id: 2, type: 1, name: 'img' }, - image: { id: 3, type: 3, name: 'img' }, - sponsoredBy: { id: 5, name: 'data', type: 1 }, - body: { id: 4, name: 'data', type: 2 }, - cta: { id: 1, type: 12, name: 'data' } -}; - -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - supportedMediaTypes: [ NATIVE, BANNER ], - isBidRequestValid: (bid) => { - return ( - !!config.getConfig('outbrain.bidderUrl') && - !!utils.deepAccess(bid, 'params.publisher.id') && - !!(bid.nativeParams || bid.sizes) - ); - }, - buildRequests: (validBidRequests, bidderRequest) => { - const page = bidderRequest.refererInfo.referer; - const ua = navigator.userAgent; - const test = setOnAny(validBidRequests, 'params.test'); - const publisher = setOnAny(validBidRequests, 'params.publisher'); - const bcat = setOnAny(validBidRequests, 'params.bcat'); - const badv = setOnAny(validBidRequests, 'params.badv'); - const cur = CURRENCY; - const endpointUrl = config.getConfig('outbrain.bidderUrl'); - const timeout = bidderRequest.timeout; - - const imps = validBidRequests.map((bid, id) => { - bid.netRevenue = 'net'; - const imp = { - id: id + 1 + '' - } - - if (bid.params.tagid) { - imp.tagid = bid.params.tagid - } - - if (bid.nativeParams) { - imp.native = { - request: JSON.stringify({ - assets: getNativeAssets(bid) - }) - } - } else { - imp.banner = { - format: transformSizes(bid.sizes) - } - } - - return imp; - }); - - const request = { - id: bidderRequest.auctionId, - site: { page, publisher }, - device: { ua }, - source: { fd: 1 }, - cur: [cur], - tmax: timeout, - imp: imps, - bcat: bcat, - badv: badv, - ext: { - prebid: { - channel: { - name: 'pbjs', - version: '$prebid.version$' - } - } - } - }; - - if (test) { - request.is_debug = !!test; - request.test = 1; - } - - if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { - utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString) - utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1) - } - if (bidderRequest.uspConsent) { - utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent) - } - if (config.getConfig('coppa') === true) { - utils.deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1) - } - - return { - method: 'POST', - url: endpointUrl, - data: JSON.stringify(request), - bids: validBidRequests - }; - }, - interpretResponse: (serverResponse, { bids }) => { - if (!serverResponse.body) { - return []; - } - const { seatbid, cur } = serverResponse.body; - - const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { - result[bid.impid - 1] = bid; - return result; - }, []); - - return bids.map((bid, id) => { - const bidResponse = bidResponses[id]; - if (bidResponse) { - const type = bid.nativeParams ? NATIVE : BANNER; - const bidObject = { - requestId: bid.bidId, - cpm: bidResponse.price, - creativeId: bidResponse.crid, - ttl: 360, - netRevenue: bid.netRevenue === 'net', - currency: cur, - mediaType: type, - nurl: bidResponse.nurl, - }; - if (type === NATIVE) { - bidObject.native = parseNative(bidResponse); - } else { - bidObject.ad = bidResponse.adm; - bidObject.width = bidResponse.w; - bidObject.height = bidResponse.h; - } - bidObject.meta = {}; - if (bidResponse.adomain && bidResponse.adomain.length > 0) { - bidObject.meta.advertiserDomains = bidResponse.adomain; - } - return bidObject; - } - }).filter(Boolean); - }, - getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { - const syncs = []; - let syncUrl = config.getConfig('outbrain.usersyncUrl'); - - let query = []; - if (syncOptions.pixelEnabled && syncUrl) { - if (gdprConsent) { - query.push('gdpr=' + (gdprConsent.gdprApplies & 1)); - query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); - } - if (uspConsent) { - query.push('us_privacy=' + encodeURIComponent(uspConsent)); - } - - syncs.push({ - type: 'image', - url: syncUrl + (query.length ? '?' + query.join('&') : '') - }); - } - return syncs; - }, - onBidWon: (bid) => { - // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server - // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute - if (bid.nurl) { - ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) - } - } -}; - -registerBidder(spec); - -function parseNative(bid) { - const { assets, link, eventtrackers } = JSON.parse(bid.adm); - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined - }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; - } - }); - if (eventtrackers) { - result.impressionTrackers = []; - eventtrackers.forEach(tracker => { - if (tracker.event !== 1) return; - switch (tracker.method) { - case 1: // img - result.impressionTrackers.push(tracker.url); - break; - case 2: // js - result.javascriptTrackers = ``; - break; - } - }); - } - return result; -} - -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = utils.deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - -function flatten(arr) { - return [].concat(...arr); -} - -function getNativeAssets(bid) { - return utils._map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1, - }; - if (props) { - asset.id = props.id; - let wmin, hmin, w, h; - let aRatios = bidParams.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - } - - if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; - } - - asset[props.name] = { - len: bidParams.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; - } - }).filter(Boolean); -} - -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - if (!utils.isArray(requestSizes)) { - return []; - } - - if (requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { - return [{ - w: parseInt(requestSizes[0], 10), - h: parseInt(requestSizes[1], 10) - }]; - } else if (utils.isArray(requestSizes[0])) { - return requestSizes.map(item => - ({ - w: parseInt(item[0], 10), - h: parseInt(item[1], 10) - }) - ); - } - - return []; -} diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 5b46a2cb80b..3b5147907eb 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -4,16 +4,16 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {getPriceBucketString} from '../src/cpmBucketManager.js'; import { Renderer } from '../src/Renderer.js'; + const BIDDER_CODE = 'ozone'; -// *** PROD *** const ORIGIN = 'https://elb.the-ozone-project.com' // applies only to auction & cookie const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.6.0'; +const OZONEVERSION = '2.7.0'; export const spec = { gvlid: 524, aliases: [{code: 'lmc', gvlid: 524}, {code: 'newspassid', gvlid: 524}], @@ -52,6 +52,7 @@ export const spec = { this.propertyBag.whitelabel.auctionUrl = bidderConfig.endpointOverride.origin + AUCTIONURI; this.propertyBag.whitelabel.cookieSyncUrl = bidderConfig.endpointOverride.origin + OZONECOOKIESYNC; } + if (arr.hasOwnProperty('renderer')) { if (arr.renderer.match('%3A%2F%2F')) { this.propertyBag.whitelabel.rendererUrl = decodeURIComponent(arr['renderer']); @@ -99,9 +100,9 @@ export const spec = { this.loadWhitelabelData(bid); logInfo('isBidRequestValid : ', config.getConfig(), bid); let adUnitCode = bid.adUnitCode; // adunit[n].code - + let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED' if (!(bid.params.hasOwnProperty('placementId'))) { - logError('VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + logError(err1.replace('{param}', 'placementId'), adUnitCode); return false; } if (!this.isValidPlacementId(bid.params.placementId)) { @@ -109,7 +110,7 @@ export const spec = { return false; } if (!(bid.params.hasOwnProperty('publisherId'))) { - logError('VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + logError(err1.replace('{param}', 'publisherId'), adUnitCode); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { @@ -117,7 +118,7 @@ export const spec = { return false; } if (!(bid.params.hasOwnProperty('siteId'))) { - logError('VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + logError(err1.replace('{param}', 'siteId'), adUnitCode); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { @@ -173,7 +174,6 @@ export const spec = { let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${OZONEVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); - // First check - is there any config to block this request? if (this.blockTheRequest()) { return []; } @@ -191,7 +191,6 @@ export const spec = { let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params delete ozoneRequest.test; // don't allow test to be set in the config - ONLY use $_GET['pbjs_debug'] - // First party data module : look for ortb2 in setconfig & set the User object. NOTE THAT this should happen before we set the consentString let fpd = config.getConfig('ortb2'); if (fpd && deepAccess(fpd, 'user')) { logInfo('added FPD user object'); @@ -203,14 +202,13 @@ export const spec = { const isTestMode = getParams[wlOztestmodeKey] || null; // this can be any string, it's used for testing ads ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string - // build the array of params to attach to `imp` + let schain = null; let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; let placementId = placementIdOverrideFromGetParam || this.getPlacementId(ozoneBidRequest); // prefer to use a valid override param, else the bidRequest placement Id obj.id = ozoneBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder ozone made bid for unknown request ID: mb7953.859498327448. Ignoring." obj.tagid = placementId; obj.secure = window.location.protocol === 'https:' ? 1 : 0; - // is there a banner (or nothing declared, so banner is the default)? let arrBannerSizes = []; if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { @@ -226,13 +224,11 @@ export const spec = { } if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { logInfo('openrtb 2.5 compliant video'); - // examine all the video attributes in the config, and either put them into obj.video if allowed by IAB2.5 or else in to obj.video.ext if (typeof ozoneBidRequest.mediaTypes[VIDEO] == 'object') { let childConfig = deepAccess(ozoneBidRequest, 'params.video', {}); obj.video = this.unpackVideoConfigIntoIABformat(ozoneBidRequest.mediaTypes[VIDEO], childConfig); obj.video = this.addVideoDefaults(obj.video, ozoneBidRequest.mediaTypes[VIDEO], childConfig); } - // we need to duplicate some of the video values let wh = getWidthAndHeightFromVideoObject(obj.video); logInfo('setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video, 'wh=', wh); if (wh && typeof wh === 'object') { @@ -249,12 +245,10 @@ export const spec = { logWarn('cannot set w, h & format values for video; the config is not right'); } } - // Native integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { obj.native = ozoneBidRequest.mediaTypes[NATIVE]; logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); } - // is the publisher specifying floors, and is the floors module enabled? if (ozoneBidRequest.hasOwnProperty('getFloor')) { logInfo('This bidRequest object has property: getFloor'); obj.floor = this.getFloorObjectForAuction(ozoneBidRequest); @@ -264,7 +258,6 @@ export const spec = { } } if (arrBannerSizes.length > 0) { - // build the banner request using banner sizes we found in either possible location: obj.banner = { topframe: 1, w: arrBannerSizes[0][0] || 0, @@ -274,11 +267,8 @@ export const spec = { }) }; } - // these 3 MUST exist - we check them in the validation method obj.placementId = placementId; - // build the imp['ext'] object - NOTE - Dont obliterate anything that' already in obj.ext deepSetValue(obj, 'ext.prebid', {'storedrequest': {'id': placementId}}); - // obj.ext = {'prebid': {'storedrequest': {'id': placementId}}}; obj.ext[whitelabelBidder] = {}; obj.ext[whitelabelBidder].adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' obj.ext[whitelabelBidder].transactionId = ozoneBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit @@ -298,33 +288,30 @@ export const spec = { } } if (fpd && deepAccess(fpd, 'site')) { - // attach the site fpd into exactly : imp[n].ext.[whitelabel].customData.0.targeting - logInfo('added FPD site object'); + logInfo('added fpd.site'); if (deepAccess(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', false)) { obj.ext[whitelabelBidder].customData[0].targeting = Object.assign(obj.ext[whitelabelBidder].customData[0].targeting, fpd.site); - // let keys = getKeys(fpd.site); - // for (let i = 0; i < keys.length; i++) { - // obj.ext[whitelabelBidder].customData[0].targeting[keys[i]] = fpd.site[keys[i]]; - // } } else { deepSetValue(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', fpd.site); } } + if (!schain && deepAccess(ozoneBidRequest, 'schain')) { + schain = ozoneBidRequest.schain; + } return obj; }); - // in v 2.0.0 we moved these outside of the individual ad slots let extObj = {}; extObj[whitelabelBidder] = {}; extObj[whitelabelBidder][whitelabelPrefix + '_pb_v'] = OZONEVERSION; extObj[whitelabelBidder][whitelabelPrefix + '_rw'] = placementIdOverrideFromGetParam ? 1 : 0; if (validBidRequests.length > 0) { let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info - // let userIds = this.findAllUserIds(validBidRequests[0]); if (userIds.hasOwnProperty('pubcid')) { extObj[whitelabelBidder].pubcid = userIds.pubcid; } } + extObj[whitelabelBidder].pv = this.getPageId(); // attach the page ID that will be common to all auciton calls for this page if refresh() is called let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') logInfo(`${whitelabelPrefix}_omp_floor dollar value = `, ozOmpFloorDollars); @@ -340,14 +327,12 @@ export const spec = { logInfo('setting aliases object'); extObj.prebid = {aliases: {'ozone': whitelabelBidder}}; } - // 20210413 - adding a set of GET params to pass to auction if (getParams.hasOwnProperty('ozf')) { extObj[whitelabelBidder]['ozf'] = getParams.ozf == 'true' || getParams.ozf == 1 ? 1 : 0; } if (getParams.hasOwnProperty('ozpf')) { extObj[whitelabelBidder]['ozpf'] = getParams.ozpf == 'true' || getParams.ozpf == 1 ? 1 : 0; } if (getParams.hasOwnProperty('ozrp') && getParams.ozrp.match(/^[0-3]$/)) { extObj[whitelabelBidder]['ozrp'] = parseInt(getParams.ozrp); } if (getParams.hasOwnProperty('ozip') && getParams.ozip.match(/^\d+$/)) { extObj[whitelabelBidder]['ozip'] = parseInt(getParams.ozip); } if (this.propertyBag.endpointOverride != null) { extObj[whitelabelBidder]['origin'] = this.propertyBag.endpointOverride; } - // extObj.ortb2 = config.getConfig('ortb2'); // original test location var userExtEids = this.generateEids(validBidRequests); // generate the UserIDs in the correct format for UserId module ozoneRequest.site = { @@ -357,7 +342,6 @@ export const spec = { }; ozoneRequest.test = (getParams.hasOwnProperty('pbjs_debug') && getParams['pbjs_debug'] === 'true') ? 1 : 0; - // this should come as late as possible so it overrides any user.ext.consent value if (bidderRequest && bidderRequest.gdprConsent) { logInfo('ADDING GDPR info'); let apiVersion = deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1); @@ -376,21 +360,22 @@ export const spec = { } else { logInfo('WILL NOT ADD CCPA info; no bidderRequest.uspConsent.'); } + if (schain) { // we set this while iterating over the bids + logInfo('schain found'); + deepSetValue(ozoneRequest, 'source.ext.schain', schain); + } - // this is for 2.2.1 - // coppa compliance if (config.getConfig('coppa') === true) { deepSetValue(ozoneRequest, 'regs.coppa', 1); } - // return the single request object OR the array: if (singleRequest) { logInfo('buildRequests starting to generate response for a single request'); ozoneRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. ozoneRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? ozoneRequest.imp = tosendtags; ozoneRequest.ext = extObj; - ozoneRequest.source = {'tid': bidderRequest.auctionId}; // RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + deepSetValue(ozoneRequest, 'source.tid', bidderRequest.auctionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); var ret = { method: 'POST', @@ -403,7 +388,6 @@ export const spec = { logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); return ret; } - // not single request - pull apart the tosendtags array & return an array of objects each containing one element in the imp array. let arrRet = tosendtags.map(imp => { logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); let ozoneRequestSingle = Object.assign({}, ozoneRequest); @@ -412,7 +396,7 @@ export const spec = { ozoneRequestSingle.auctionId = imp.ext[whitelabelBidder].transactionId; // not sure if this should be here? ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.ext = extObj; - ozoneRequestSingle.source = {'tid': imp.ext[whitelabelBidder].transactionId}; + deepSetValue(ozoneRequestSingle, 'source.tid', imp.ext[whitelabelBidder].transactionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). deepSetValue(ozoneRequestSingle, 'user.ext.eids', userExtEids); logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); return { @@ -477,7 +461,6 @@ export const spec = { logInfo(`interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); serverResponse = serverResponse.body || {}; - // note that serverResponse.id value is the auction_id we might want to use for reporting reasons. if (!serverResponse.hasOwnProperty('seatbid')) { return []; } @@ -492,7 +475,6 @@ export const spec = { } logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); - // 2021-03-05 - comment this out for a build without adding adid to the response serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute. serverResponse.seatbid = this.removeSingleBidderMultipleBids(serverResponse.seatbid); @@ -508,7 +490,6 @@ export const spec = { logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); let thisBid = ozoneAddStandardProperties(sb.bid[j], defaultWidth, defaultHeight); - // prebid 4.0 compliance thisBid.meta = {advertiserDomains: thisBid.adomain || []}; let videoContext = null; let isVideo = false; @@ -527,11 +508,9 @@ export const spec = { let adserverTargeting = {}; if (enhancedAdserverTargeting) { let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); - // add all the winning & non-winning bids for this bidId: logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); - // let bidderName = bidderNameWH.split('_')[0]; adserverTargeting[whitelabelPrefix + '_' + bidderName] = bidderName; adserverTargeting[whitelabelPrefix + '_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); @@ -565,7 +544,6 @@ export const spec = { logInfo(`${whitelabelBidder}.enhancedAdserverTargeting is set to false, so no per-bid keys will be sent to adserver.`); } } - // also add in the winning bid, to be sent to dfp let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); adserverTargeting[whitelabelPrefix + '_auc_id'] = String(request.bidderRequest.auctionId); adserverTargeting[whitelabelPrefix + '_winner'] = String(winningSeat); @@ -594,14 +572,14 @@ export const spec = { /** * Use this to get all config values * Now it's getting complicated with whitelabeling, this simplifies the code for getting config values. - * eg. to get ozone.oz_omp_floor you just send '_omp_floor' + * eg. to get whitelabelled version you just sent the ozone default string like ozone.oz_omp_floor * @param ozoneVersion string like 'ozone.oz_omp_floor' * @return {string|object} */ getWhitelabelConfigItem(ozoneVersion) { if (this.propertyBag.whitelabel.bidder == 'ozone') { return config.getConfig(ozoneVersion); } let whitelabelledSearch = ozoneVersion.replace('ozone', this.propertyBag.whitelabel.bidder); - whitelabelledSearch = ozoneVersion.replace('oz_', this.propertyBag.whitelabel.keyPrefix + '_'); + whitelabelledSearch = whitelabelledSearch.replace('oz_', this.propertyBag.whitelabel.keyPrefix + '_'); return config.getConfig(whitelabelledSearch); }, /** @@ -631,8 +609,6 @@ export const spec = { } return ret; }, - // see http://prebid.org/dev-docs/bidder-adaptor.html#registering-user-syncs - // us privacy: https://docs.prebid.org/dev-docs/modules/consentManagementUsp.html getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'gdprConsent', gdprConsent, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); if (!serverResponse || serverResponse.length === 0) { @@ -646,11 +622,6 @@ export const spec = { arrQueryString.push('gdpr=' + (deepAccess(gdprConsent, 'gdprApplies', false) ? '1' : '0')); arrQueryString.push('gdpr_consent=' + deepAccess(gdprConsent, 'consentString', '')); arrQueryString.push('usp_consent=' + (usPrivacy || '')); - // var objKeys = Object.getOwnPropertyNames(this.cookieSyncBag.userIdObject); - // for (let idx in objKeys) { - // let keyname = objKeys[idx]; - // arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); - // } for (let keyname in this.cookieSyncBag.userIdObject) { arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); } @@ -704,10 +675,7 @@ export const spec = { */ findAllUserIds(bidRequest) { var ret = {}; - // @todo - what is Neustar fabrick called & where to look for it? If it's a simple value then it will automatically be ok - // it is not in the table 'Bidder Adapter Implementation' on https://docs.prebid.org/dev-docs/modules/userId.html#prebidjs-adapters let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; - if (bidRequest.hasOwnProperty('userId')) { for (let arrayId in searchKeysSingle) { let key = searchKeysSingle[arrayId]; @@ -716,7 +684,6 @@ export const spec = { ret[key] = bidRequest.userId[key]; } else if (typeof (bidRequest.userId[key]) == 'object') { logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); - // fallback - get the value of the first key in the object; this is NOT desirable behaviour ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values } else { logError(`failed to get string key value for userId : ${key}`); @@ -815,7 +782,6 @@ export const spec = { }); } }, - // Try to use this as the mechanism for reading GET params because it's easy to mock it for tests getGetParametersAsObject() { let items = location.search.substr(1).split('&'); let ret = {}; @@ -831,7 +797,6 @@ export const spec = { * @return {boolean|*[]} true = block the request, else false */ blockTheRequest() { - // if there is an ozone.oz_request = false then quit now. let ozRequest = this.getWhitelabelConfigItem('ozone.oz_request'); if (typeof ozRequest == 'boolean' && !ozRequest) { logWarn(`Will not allow auction : ${this.propertyBag.whitelabel.keyPrefix}one.${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); @@ -882,7 +847,6 @@ export const spec = { ret.ext[key] = objConfig[key]; } } - // handle ext separately, if it exists; we have probably built up an ext object already if (objConfig.hasOwnProperty('ext') && typeof objConfig.ext === 'object') { if (objConfig.hasOwnProperty('ext')) { ret.ext = mergeDeep(ret.ext, objConfig.ext); @@ -906,7 +870,6 @@ export const spec = { * @private */ _addVideoDefaults(objRet, objConfig, addIfMissing) { - // add inferred values & any default values we want. let context = deepAccess(objConfig, 'context'); if (context === 'outstream') { objRet.placement = 3; @@ -936,8 +899,6 @@ export function injectAdIdsIntoAllBidResponses(seatbid) { for (let i = 0; i < seatbid.length; i++) { let sb = seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { - // modify the bidId per-bid, so each bid has a unique adId within this response, and dfp can select one. - // 2020-06 we now need a second level of ID because there might be multiple identical impid's within a seatbid! sb.bid[j]['adId'] = `${sb.bid[j]['impid']}-${i}-${spec.propertyBag.whitelabel.keyPrefix}-${j}`; } } @@ -985,7 +946,6 @@ export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) let thisSeat = serverResponseSeatBid[j].seat; for (let k = 0; k < theseBids.length; k++) { if (theseBids[k].impid === requestBidId) { - // we've found a matching server response bid for this request bid if ((thisBidWinner == null) || (thisBidWinner.price < theseBids[k].price)) { thisBidWinner = theseBids[k]; winningSeat = thisSeat; @@ -1011,7 +971,6 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { for (let k = 0; k < theseBids.length; k++) { if (theseBids[k].impid === matchBidId) { if (objBids.hasOwnProperty(thisSeat)) { // > 1 bid for an adunit from a bidder - only use the one with the highest bid - // objBids[`${thisSeat}${theseBids[k].w}x${theseBids[k].h}`] = theseBids[k]; if (objBids[thisSeat]['price'] < theseBids[k].price) { objBids[thisSeat] = theseBids[k]; } @@ -1044,7 +1003,6 @@ export function getRoundedBid(price, mediaType) { config.getConfig('currency.granularityMultiplier') ); logInfo('priceStringsObj', priceStringsObj); - // by default, without any custom granularity set, you get granularity name : 'medium' let granularityNamePriceStringsKeyMapping = { 'medium': 'med', 'custom': 'custom', @@ -1195,7 +1153,6 @@ function newRenderer(adUnitCode, rendererOptions = {}) { } function outstreamRender(bid) { logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid =', JSON.parse(JSON.stringify(bid))); - // push to render queue because ozoneVideo may not be loaded yet bid.renderer.push(() => { window.ozoneVideo.outstreamRender(bid); }); diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index b1553bcb134..04f36d0cb63 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -7,13 +7,13 @@ // ci trigger: 1 -import { timestamp, logError, logWarn, isEmpty, contains, inIframe, deepClone, isPlainObject } from '../src/utils.js'; -import find from 'core-js-pure/features/array/find.js'; -import { ajax } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { uspDataHandler } from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {contains, deepClone, inIframe, isEmpty, isPlainObject, logError, logWarn, timestamp} from '../src/utils.js'; +import {find} from '../src/polyfill.js'; +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {uspDataHandler} from '../src/adapterManager.js'; +import {getStorageManager} from '../src/storageManager.js'; const PARRABLE_URL = 'https://h.parrable.com/prebid'; const PARRABLE_COOKIE_NAME = '_parrable_id'; @@ -23,7 +23,7 @@ const LEGACY_OPTOUT_COOKIE_NAME = '_parrable_optout'; const ONE_YEAR_MS = 364 * 24 * 60 * 60 * 1000; const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT'; -const storage = getStorageManager(PARRABLE_GVLID); +const storage = getStorageManager({gvlid: PARRABLE_GVLID}); function getExpirationDate() { const oneYearFromNow = new Date(timestamp() + ONE_YEAR_MS); diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 40282567506..c9d22655a31 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -5,15 +5,16 @@ * @module modules/permutiveRtdProvider * @requires module:modules/realTimeData */ -import { getGlobal } from '../src/prebidGlobal.js' -import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js' -import { deepSetValue, deepAccess, isFn, mergeDeep, logError } from '../src/utils.js' -import { config } from '../src/config.js' -import includes from 'core-js-pure/features/array/includes.js' +import {getGlobal} from '../src/prebidGlobal.js'; +import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {deepAccess, deepSetValue, isFn, logError, mergeDeep} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {includes} from '../src/polyfill.js'; + const MODULE_NAME = 'permutive' -export const storage = getStorageManager(null, MODULE_NAME) +export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) function init (moduleConfig, userConsent) { return true diff --git a/modules/pilotxBidAdapter.js b/modules/pilotxBidAdapter.js new file mode 100644 index 00000000000..335c461e3d9 --- /dev/null +++ b/modules/pilotxBidAdapter.js @@ -0,0 +1,147 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +const BIDDER_CODE = 'pilotx'; +const ENDPOINT_URL = '//adn.pilotx.tv/hb' +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + aliases: ['pilotx'], // short code + /** + * 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 sizesCheck = !!bid.sizes + let paramSizesCheck = !!bid.params.sizes + var sizeConfirmed = false + if (sizesCheck) { + if (bid.sizes.length < 1) { + return false + } else { + sizeConfirmed = true + } + } + if (paramSizesCheck) { + if (bid.params.sizes.length < 1 && !sizeConfirmed) { + return false + } else { + sizeConfirmed = true + } + } + if (!sizeConfirmed) { + return false + } + return !!(bid.params.placementId); + }, + /** + * 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) { + let payloadItems = {}; + validBidRequests.forEach(bidRequest => { + let sizes = []; + let placementId = this.setPlacementID(bidRequest.params.placementId) + payloadItems[placementId] = {} + if (bidRequest.sizes.length > 0) { + if (Array.isArray(bidRequest.sizes[0])) { + for (let i = 0; i < bidRequest.sizes.length; i++) { + sizes[i] = [(bidRequest.sizes[i])[0], (bidRequest.sizes[i])[1]] + } + } else { + sizes[0] = [bidRequest.sizes[0], bidRequest.sizes[1]] + } + payloadItems[placementId]['sizes'] = sizes + } + if (bidRequest.mediaTypes != null) { + for (let i in bidRequest.mediaTypes) { + payloadItems[placementId][i] = { + ...bidRequest.mediaTypes[i] + } + } + } + let consentTemp = '' + let consentRequiredTemp = false + if (bidderRequest && bidderRequest.gdprConsent) { + consentTemp = bidderRequest.gdprConsent.consentString + // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true + consentRequiredTemp = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + } + + payloadItems[placementId]['gdprConsentString'] = consentTemp + payloadItems[placementId]['gdprConsentRequired'] = consentRequiredTemp + payloadItems[placementId]['bidId'] = bidRequest.bidId + }); + const payload = payloadItems; + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL, + 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 serverBody = serverResponse.body; + const bidResponses = []; + if (serverBody.mediaType == 'banner') { + const bidResponse = { + requestId: serverBody.requestId, + cpm: serverBody.cpm, + width: serverBody.width, + height: serverBody.height, + creativeId: serverBody.creativeId, + currency: serverBody.currency, + netRevenue: false, + ttl: serverBody.ttl, + ad: serverBody.ad, + mediaType: 'banner', + meta: { + mediaType: 'banner', + advertiserDomains: serverBody.advertiserDomains + } + } + bidResponses.push(bidResponse) + } else if (serverBody.mediaType == 'video') { + const bidResponse = { + requestId: serverBody.requestId, + cpm: serverBody.cpm, + width: serverBody.width, + height: serverBody.height, + creativeId: serverBody.creativeId, + currency: serverBody.currency, + netRevenue: false, + ttl: serverBody.ttl, + vastUrl: serverBody.vastUrl, + mediaType: 'video', + meta: { + mediaType: 'video', + advertiserDomains: serverBody.advertiserDomains + } + } + bidResponses.push(bidResponse) + } + + return bidResponses; + }, + + /** + * Formats placement ids for adserver ingestion purposes + * @param {string[]} The placement ID/s in an array + */ + setPlacementID: function (placementId) { + if (Array.isArray(placementId)) { + return placementId.join('#') + } + return placementId + }, +} +registerBidder(spec); diff --git a/modules/pilotxBidAdapter.md b/modules/pilotxBidAdapter.md new file mode 100644 index 00000000000..37489bda4a0 --- /dev/null +++ b/modules/pilotxBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: Pilotx Prebid Adapter +Module Type: Bidder Adapter +Maintainer: tony@pilotx.tv +``` + +# Description + +Connects to Pilotx + +Pilotx's bid adapter supports banner and video. + +# Test Parameters +``` +// Banner adUnit +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [{ + bidder: 'pilotx', + params: { + placementId: ["1423"] + } + }] + +}]; + +// Video adUnit +var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + } + }, + bids: [{ + bidder: 'pilotx', + params: { + placementId: '1422', + } + }] +}; +``` \ No newline at end of file diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index e9db875fc2f..29552ec796d 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -1,14 +1,22 @@ -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 includes from 'core-js-pure/features/array/includes.js'; -import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, deepAccess, isEmpty, transformBidderParamKeywords, isFn } from '../src/utils.js'; -import { auctionManager } from '../src/auctionManager.js'; -import find from 'core-js-pure/features/array/find.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, + isArray, + isEmpty, + isFn, + isNumber, + isPlainObject, + transformBidderParamKeywords +} from '../src/utils.js'; +import {auctionManager} from '../src/auctionManager.js'; const SOURCE = 'pbjs'; -const storageManager = getStorageManager(); +const storageManager = getStorageManager({bidderCode: 'pixfuture'}); const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; export const spec = { code: 'pixfuture', diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index e82fdfd02a9..d30fd5bf810 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1,22 +1,43 @@ import Adapter from '../../src/adapter.js'; -import { createBid } from '../../src/bidfactory.js'; +import {createBid} from '../../src/bidfactory.js'; import { - getPrebidInternal, logError, isStr, isPlainObject, logWarn, generateUUID, bind, logMessage, - triggerPixel, insertUserSyncIframe, deepAccess, mergeDeep, deepSetValue, cleanObj, parseSizesInput, - getBidRequest, getDefinedParams, createTrackPixelHtml, pick, deepClone, uniques, flatten, isNumber, - isEmpty, isArray, logInfo + bind, + cleanObj, + createTrackPixelHtml, + deepAccess, + deepClone, + deepSetValue, + flatten, + generateUUID, + getBidRequest, + getDefinedParams, + getPrebidInternal, + insertUserSyncIframe, + isArray, + isEmpty, + isNumber, + isPlainObject, + isStr, + logError, + logInfo, + logMessage, + logWarn, + mergeDeep, + parseSizesInput, + pick, timestamp, + triggerPixel, + uniques } from '../../src/utils.js'; import CONSTANTS from '../../src/constants.json'; import adapterManager from '../../src/adapterManager.js'; import { config } from '../../src/config.js'; import { VIDEO, NATIVE } from '../../src/mediaTypes.js'; -import { processNativeAdUnitParams } from '../../src/native.js'; import { isValid } from '../../src/adapters/bidderFactory.js'; -import events from '../../src/events.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import * as events from '../../src/events.js'; +import {find, includes} from '../../src/polyfill.js'; import { S2S_VENDORS } from './config.js'; import { ajax } from '../../src/ajax.js'; -import find from 'core-js-pure/features/array/find.js'; +import {hook} from '../../src/hook.js'; const getConfig = config.getConfig; @@ -54,8 +75,10 @@ let eidPermissions; * @typedef {Object} S2SDefaultConfig * @summary Base config properties for server to server header bidding * @property {string} [adapter='prebidServer'] adapter code to use for S2S + * @property {boolean} [allowUnknownBidderCodes=false] allow bids from bidders that were not explicitly requested * @property {boolean} [enabled=false] enables S2S bidding * @property {number} [timeout=1000] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})` + * @property {number} [syncTimeout=1000] timeout for cookie sync iframe / image rendering * @property {number} [maxBids=1] * @property {AdapterOptions} [adapterOptions] adds arguments to resulting OpenRTB payload to Prebid Server * @property {Object} [syncUrlModifier] @@ -76,9 +99,12 @@ let eidPermissions; * @type {S2SDefaultConfig} */ const s2sDefaultConfig = { + bidders: Object.freeze([]), timeout: 1000, + syncTimeout: 1000, maxBids: 1, adapter: 'prebidServer', + allowUnknownBidderCodes: false, adapterOptions: {}, syncUrlModifier: {} }; @@ -118,7 +144,7 @@ function updateConfigDefaultVendor(option) { */ function validateConfigRequiredProps(option) { const keys = Object.keys(option); - if (['accountId', 'bidders', 'endpoint'].filter(key => { + if (['accountId', 'endpoint'].filter(key => { if (!includes(keys, key)) { logError(key + ' missing in server to server config'); return true; @@ -274,11 +300,9 @@ function doAllSyncs(bidders, s2sConfig) { */ function doPreBidderSync(type, url, bidder, done, s2sConfig) { if (s2sConfig.syncUrlModifier && typeof s2sConfig.syncUrlModifier[bidder] === 'function') { - const newSyncUrl = s2sConfig.syncUrlModifier[bidder](type, url, bidder); - doBidderSync(type, newSyncUrl, bidder, done) - } else { - doBidderSync(type, url, bidder, done) + url = s2sConfig.syncUrlModifier[bidder](type, url, bidder); } + doBidderSync(type, url, bidder, done, s2sConfig.syncTimeout) } /** @@ -288,17 +312,18 @@ function doPreBidderSync(type, url, bidder, done, s2sConfig) { * @param {string} url the url to sync * @param {string} bidder name of bidder doing sync for * @param {function} done an exit callback; to signify this pixel has either: finished rendering or something went wrong + * @param {number} timeout: maximum time to wait for rendering in milliseconds */ -function doBidderSync(type, url, bidder, done) { +function doBidderSync(type, url, bidder, done, timeout) { if (!url) { logError(`No sync url for bidder "${bidder}": ${url}`); done(); } else if (type === 'image' || type === 'redirect') { logMessage(`Invoking image pixel user sync for bidder: "${bidder}"`); - triggerPixel(url, done); - } else if (type == 'iframe') { + triggerPixel(url, done, timeout); + } else if (type === 'iframe') { logMessage(`Invoking iframe user sync for bidder: "${bidder}"`); - insertUserSyncIframe(url, done); + insertUserSyncIframe(url, done, timeout); } else { logError(`User sync type "${type}" not supported for bidder: "${bidder}"`); done(); @@ -432,7 +457,6 @@ let nativeEventTrackerMethodMap = { * Protocol spec for OpenRTB endpoint * e.g., https:///v1/openrtb2/auction */ -let bidIdMap = {}; let nativeAssetCache = {}; // store processed native params to preserve /** @@ -490,8 +514,24 @@ export function resetWurlMap() { wurlMap = {}; } -const OPEN_RTB_PROTOCOL = { - buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig, requestedBidders) { +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]; @@ -499,6 +539,14 @@ const OPEN_RTB_PROTOCOL = { // 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' @@ -509,8 +557,9 @@ const OPEN_RTB_PROTOCOL = { impressionId = `${adUnit.code}-${i}`; } impIds.add(impressionId); + this.adUnitsByImp[impressionId] = adUnit; - const nativeParams = processNativeAdUnitParams(deepAccess(adUnit, 'mediaTypes.native')); + const nativeParams = adUnit.nativeParams; let nativeAssets; if (nativeParams) { try { @@ -539,10 +588,13 @@ const OPEN_RTB_PROTOCOL = { } if (Array.isArray(params.aspect_ratios)) { // pass aspect_ratios as ext data I guess? - asset.ext = { - aspectratios: params.aspect_ratios.map( - ratio => `${ratio.ratio_width}:${ratio.ratio_height}` - ) + const aspectRatios = params.aspect_ratios + .filter((ar) => ar.ratio_width && ar.ratio_height) + .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`); + if (aspectRatios.length > 0) { + asset.ext = { + aspectratios: aspectRatios + } } } assets.push(newAsset({ @@ -580,9 +632,7 @@ const OPEN_RTB_PROTOCOL = { const bannerParams = deepAccess(adUnit, 'mediaTypes.banner'); adUnit.bids.forEach(bid => { - // OpenRTB response contains imp.id and bidder name. These are - // combined to create a unique key for each bid since an id isn't returned - bidIdMap[`${impressionId}${bid.bidder}`] = bid.bid_id; + 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]; @@ -657,6 +707,7 @@ const OPEN_RTB_PROTOCOL = { // 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); @@ -665,7 +716,7 @@ const OPEN_RTB_PROTOCOL = { return acc; }, {...deepAccess(adUnit, 'ortb2Imp.ext')}); - const imp = { id: impressionId, ext, secure: s2sConfig.secure }; + const imp = { ...adUnit.ortb2Imp, id: impressionId, ext, secure: s2sConfig.secure }; const ortb2 = {...deepAccess(adUnit, 'ortb2Imp.ext.data')}; Object.keys(ortb2).forEach(prop => { @@ -696,7 +747,7 @@ const OPEN_RTB_PROTOCOL = { } }); - Object.assign(imp, mediaTypes); + mergeDeep(imp, mediaTypes); // if storedAuctionResponse has been set, pass SRID const storedAuctionResponseBid = find(firstBidRequest.bids, bid => (bid.adUnitCode === adUnit.code && bid.storedAuctionResponse)); @@ -731,7 +782,7 @@ const OPEN_RTB_PROTOCOL = { return; } const request = { - id: s2sBidRequest.tid, + id: firstBidRequest.auctionId, source: {tid: s2sBidRequest.tid}, tmax: s2sConfig.timeout, imp: imps, @@ -751,7 +802,7 @@ const OPEN_RTB_PROTOCOL = { } }; - // Sets pbjs version, can be overwritten below if channel exists in s2sConfig.extPrebid + // 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 @@ -761,7 +812,7 @@ const OPEN_RTB_PROTOCOL = { // s2sConfig video.ext.prebid is passed through openrtb to PBS if (s2sConfig.extPrebid && typeof s2sConfig.extPrebid === 'object') { - request.ext.prebid = Object.assign(request.ext.prebid, s2sConfig.extPrebid); + request.ext.prebid = mergeDeep(request.ext.prebid, s2sConfig.extPrebid); } /** @@ -850,10 +901,12 @@ const OPEN_RTB_PROTOCOL = { addBidderFirstPartyDataToRequest(request); + request.imp.forEach((imp) => this.impRequested[imp.id] = imp); return request; }, - interpretResponse(response, bidderRequests, s2sConfig) { + interpretResponse(response) { + const {bidderRequests, s2sConfig} = this; const bids = []; [['errors', 'serverErrors'], ['responsetimemillis', 'serverResponseTimeMs']] @@ -863,22 +916,27 @@ const OPEN_RTB_PROTOCOL = { // a seatbid object contains a `bid` array and a `seat` string response.seatbid.forEach(seatbid => { (seatbid.bid || []).forEach(bid => { - let bidRequest; - let key = `${bid.impid}${seatbid.seat}`; - if (bidIdMap[key]) { - bidRequest = getBidRequest( - bidIdMap[key], - bidderRequests - ); + 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, bidRequest || { + let bidObject = createBid(status, { bidder: seatbid.seat, - src: TYPE + 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; // temporarily leaving attaching it to each bidResponse so no breaking change @@ -896,7 +954,7 @@ const OPEN_RTB_PROTOCOL = { // 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(bidRequest.auctionId, bidObject.adId, deepAccess(bid, 'ext.prebid.events.win')); + addWurl(this.auctionId, bidObject.adId, deepAccess(bid, 'ext.prebid.events.win')); } let extPrebidTargeting = deepAccess(bid, 'ext.prebid.targeting'); @@ -917,9 +975,8 @@ const OPEN_RTB_PROTOCOL = { if (deepAccess(bid, 'ext.prebid.type') === VIDEO) { bidObject.mediaType = VIDEO; - let sizes = bidRequest.sizes && bidRequest.sizes[0]; - bidObject.playerWidth = sizes[0]; - bidObject.playerHeight = sizes[1]; + 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 @@ -1002,7 +1059,6 @@ const OPEN_RTB_PROTOCOL = { bidObject.width = bid.w; bidObject.height = bid.h; if (bid.dealid) { bidObject.dealId = bid.dealid; } - bidObject.requestId = bidRequest.bidId || bidRequest.bid_Id; bidObject.creative_id = bid.crid; bidObject.creativeId = bid.crid; if (bid.burl) { bidObject.burl = bid.burl; } @@ -1017,14 +1073,24 @@ const OPEN_RTB_PROTOCOL = { bidObject.ttl = (bid.exp) ? bid.exp : configTtl; bidObject.netRevenue = (bid.netRevenue) ? bid.netRevenue : DEFAULT_S2S_NETREVENUE; - bids.push({ adUnit: bidRequest.adUnitCode, bid: bidObject }); + 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 @@ -1072,20 +1138,8 @@ export function PrebidServer() { /* Prebid executes this function when the page asks to send out bid requests */ baseAdapter.callBids = function(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { - const adUnits = deepClone(s2sBidRequest.ad_units); let { gdprConsent, uspConsent } = getConsentData(bidRequests); - // at this point ad units should have a size array either directly or mapped so filter for that - const validAdUnits = adUnits.filter(unit => - unit.mediaTypes && (unit.mediaTypes.native || (unit.mediaTypes.banner && unit.mediaTypes.banner.sizes) || (unit.mediaTypes.video && unit.mediaTypes.video.playerSize)) - ); - - // in case config.bidders contains invalid bidders, we only process those we sent requests for - const requestedBidders = validAdUnits - .map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(uniques)) - .reduce(flatten) - .filter(uniques); - if (Array.isArray(_s2sConfigs)) { if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) { let syncBidders = s2sBidRequest.s2sConfig.bidders @@ -1095,59 +1149,23 @@ export function PrebidServer() { queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig); } - const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig, requestedBidders); - const requestJson = request && JSON.stringify(request); - logInfo('BidRequest: ' + requestJson); - const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); - if (request && requestJson && endpointUrl) { - ajax( - endpointUrl, - { - success: response => handleResponse(response, requestedBidders, bidRequests, addBidResponse, done, s2sBidRequest.s2sConfig), - error: done - }, - requestJson, - { contentType: 'text/plain', withCredentials: true } - ); - } else { - logError('PBS request not made. Check endpoints.'); - } - } - }; - - /* Notify Prebid of bid responses so bids can get in the auction */ - function handleResponse(response, requestedBidders, bidderRequests, addBidResponse, done, s2sConfig) { - let result; - let bids = []; - let { gdprConsent, uspConsent } = getConsentData(bidderRequests); - - try { - result = JSON.parse(response); - - bids = OPEN_RTB_PROTOCOL.interpretResponse( - result, - bidderRequests, - s2sConfig - ); - - bids.forEach(({adUnit, bid}) => { - if (isValid(adUnit, bid, bidderRequests)) { - addBidResponse(adUnit, bid); + processPBSRequest(s2sBidRequest, bidRequests, ajax, { + onResponse: function (isValid, requestedBidders) { + if (isValid) { + bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); + } + done(); + doClientSideSyncs(requestedBidders, gdprConsent, uspConsent); + }, + onError: done, + onBid: function ({adUnit, bid}) { + if (isValid(adUnit, bid)) { + addBidResponse(adUnit, bid); + } } - }); - - bidderRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); - } catch (error) { - logError(error); - } - - if (!result || (result.status && includes(result.status, 'Error'))) { - logError('error parsing response: ', result.status); + }) } - - done(); - doClientSideSyncs(requestedBidders, gdprConsent, uspConsent); - } + }; // Listen for bid won to call wurl events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); @@ -1159,6 +1177,66 @@ export function PrebidServer() { }); } +/** + * Build and send the appropriate HTTP request over the network, then interpret the response. + * @param s2sBidRequest + * @param bidRequests + * @param ajax + * @param onResponse {function(boolean, Array[String])} invoked on a successful HTTP response - with a flag indicating whether it was successful, + * and a list of the unique bidder codes that were sent in the request + * @param onError {function(String, {})} invoked on HTTP failure - with status message and XHR error + * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse + */ +export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}) { + let { gdprConsent } = getConsentData(bidRequests); + const adUnits = deepClone(s2sBidRequest.ad_units); + + // at this point ad units should have a size array either directly or mapped so filter for that + const validAdUnits = adUnits.filter(unit => + unit.mediaTypes && (unit.mediaTypes.native || (unit.mediaTypes.banner && unit.mediaTypes.banner.sizes) || (unit.mediaTypes.video && unit.mediaTypes.video.playerSize)) + ); + + // in case config.bidders contains invalid bidders, we only process those we sent requests for + const requestedBidders = validAdUnits + .map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(uniques)) + .reduce(flatten) + .filter(uniques); + + const ortb2 = new ORTB2(s2sBidRequest, bidRequests, validAdUnits, requestedBidders); + const request = ortb2.buildRequest(); + const requestJson = request && JSON.stringify(request); + logInfo('BidRequest: ' + requestJson); + const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); + if (request && requestJson && endpointUrl) { + ajax( + endpointUrl, + { + success: function (response) { + let result; + try { + result = JSON.parse(response); + const bids = ortb2.interpretResponse(result); + bids.forEach(onBid); + } catch (error) { + logError(error); + } + if (!result || (result.status && includes(result.status, 'Error'))) { + logError('error parsing response: ', result ? result.status : 'not valid JSON'); + onResponse(false, requestedBidders); + } else { + onResponse(true, requestedBidders); + } + }, + error: onError + }, + requestJson, + {contentType: 'text/plain', withCredentials: true} + ); + } else { + logError('PBS request not made. Check endpoints.'); + } +}, 'processPBSRequest'); + /** * Global setter that sets eids permissions for bidders * This setter is to be used by userId module when included diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index a1a0a636e3c..1ac7ba84916 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -7,7 +7,7 @@ import { getStorageManager } from '../src/storageManager.js'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager */ -export const storage = getStorageManager(undefined, 'prebidmanager'); +export const storage = getStorageManager({gvlid: undefined, moduleName: 'prebidmanager'}); const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint' const analyticsType = 'endpoint'; const analyticsName = 'Prebid Manager Analytics: '; diff --git a/modules/priceFloors.js b/modules/priceFloors.js index b55638c1a5c..ff4213f1330 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -1,13 +1,30 @@ -import { parseUrl, deepAccess, parseGPTSingleSizeArray, getGptSlotInfoForAdUnitCode, deepSetValue, logWarn, deepClone, getParameterByName, generateUUID, logError, logInfo, isNumber, pick, debugTurnedOn } from '../src/utils.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { config } from '../src/config.js'; -import { ajaxBuilder } from '../src/ajax.js'; -import events from '../src/events.js'; +import { + debugTurnedOn, + deepAccess, + deepClone, + deepSetValue, + generateUUID, + getGptSlotInfoForAdUnitCode, + getParameterByName, + isNumber, + logError, + logInfo, + logWarn, + parseGPTSingleSizeArray, + parseUrl, + pick +} from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {config} from '../src/config.js'; +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 'core-js-pure/features/array/find.js'; -import { getRefererInfo } from '../src/refererDetection.js'; +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'; /** * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. @@ -65,9 +82,14 @@ function getHostNameFromReferer(referer) { } // First look into bidRequest! -function getGptSlotFromBidRequest(bidRequest) { - const isGam = deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.name') === 'gam'; - return isGam && bidRequest.ortb2Imp.ext.data.adserver.adslot; +function getGptSlotFromAdUnit(transactionId, {index = auctionManager.index} = {}) { + const adUnit = index.getAdUnit({transactionId}); + const isGam = deepAccess(adUnit, 'ortb2Imp.ext.data.adserver.name') === 'gam'; + return isGam && adUnit.ortb2Imp.ext.data.adserver.adslot; +} + +function getAdUnitCode(request, response, {index = auctionManager.index} = {}) { + return request?.adUnitCode || index.getAdUnit(response).code; } /** @@ -76,9 +98,9 @@ function getGptSlotFromBidRequest(bidRequest) { export let fieldMatchingFunctions = { 'size': (bidRequest, bidResponse) => parseGPTSingleSizeArray(bidResponse.size) || '*', 'mediaType': (bidRequest, bidResponse) => bidResponse.mediaType || 'banner', - 'gptSlot': (bidRequest, bidResponse) => getGptSlotFromBidRequest(bidRequest) || getGptSlotInfoForAdUnitCode(bidRequest.adUnitCode).gptSlot, + 'gptSlot': (bidRequest, bidResponse) => getGptSlotFromAdUnit((bidRequest || bidResponse).transactionId) || getGptSlotInfoForAdUnitCode(getAdUnitCode(bidRequest, bidResponse)).gptSlot, 'domain': (bidRequest, bidResponse) => referrerHostname || getHostNameFromReferer(getRefererInfo().referer), - 'adUnitCode': (bidRequest, bidResponse) => bidRequest.adUnitCode + 'adUnitCode': (bidRequest, bidResponse) => getAdUnitCode(bidRequest, bidResponse) } /** @@ -116,7 +138,7 @@ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) let matchingData = { floorMin: floorData.floorMin || 0, - floorRuleValue: floorData.values[matchingRule] || floorData.default, + floorRuleValue: isNaN(floorData.values[matchingRule]) ? floorData.default : floorData.values[matchingRule], matchingData: allPossibleMatches[0], // the first possible match is an "exact" so contains all data relevant for anlaytics adapters matchingRule }; @@ -147,7 +169,7 @@ 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 = deepAccess(getGlobal(), `bidderSettings.${bidderName}.bidCpmAdjustment`) || deepAccess(getGlobal(), 'bidderSettings.standard.bidCpmAdjustment'); + const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment'); if (adjustmentFunction) { return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm})); } @@ -664,15 +686,16 @@ function shouldFloorBid(floorData, floorInfo, bid) { * And if the rule we find determines a bid should be floored we will do so. */ export function addBidResponseHook(fn, adUnitCode, bid) { - let floorData = _floorDataForAuction[this.bidderRequest.auctionId]; - // if no floor data or associated bidRequest then bail - const matchingBidRequest = find(this.bidderRequest.bids, bidRequest => bidRequest.bidId && bidRequest.bidId === bid.requestId); - if (!floorData || !bid || floorData.skipped || !matchingBidRequest) { + let floorData = _floorDataForAuction[bid.auctionId]; + // if no floor data then bail + if (!floorData || !bid || floorData.skipped) { return fn.call(this, adUnitCode, bid); } + const matchingBidRequest = auctionManager.index.getBidRequest(bid) + // get the matching rule - let floorInfo = getFirstMatchingFloor(floorData.data, {...matchingBidRequest}, {...bid, size: [bid.width, bid.height]}); + let floorInfo = getFirstMatchingFloor(floorData.data, matchingBidRequest, {...bid, size: [bid.width, bid.height]}); if (!floorInfo.matchingFloor) { logWarn(`${MODULE_NAME}: unable to determine a matching price floor for bidResponse`, bid); @@ -706,7 +729,7 @@ export function addBidResponseHook(fn, adUnitCode, bid) { 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, matchingBidRequest); + let flooredBid = createBid(CONSTANTS.STATUS.NO_BID, bid.getIdentifiers()); Object.assign(flooredBid, pick(bid, [ 'floorData', 'width', diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index b747c5aca2d..42a98bcdb09 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -172,8 +172,6 @@ function interpretResponse(serverResponse, bidRequest) { function _assignFloor(bid) { if (!isFn(bid.getFloor)) { - // eslint-disable-next-line no-console - console.log(bid.params.bidFloor); return bid.params.bidFloor ? bid.params.bidFloor : null; } const floor = bid.getFloor({ diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 6ecf0723aae..faca59cce1c 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -5,7 +5,7 @@ */ import { logMessage, parseUrl, buildUrl, triggerPixel, generateUUID, isArray } from '../src/utils.js'; import { config } from '../src/config.js'; -import events from '../src/events.js'; +import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 89dea545434..28c4fdefd42 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -16,7 +16,7 @@ import { } from '../src/utils.js'; const BIDDER_VERSION = '1.1.0'; -const BASE_URL = 'https://ortb.adpearl.io'; +const BASE_URL = 'https://auction.adpearl.io'; export const spec = { code: 'pubgenius', diff --git a/modules/publinkIdSystem.js b/modules/publinkIdSystem.js index 990227e7cfe..9d5645a38cb 100644 --- a/modules/publinkIdSystem.js +++ b/modules/publinkIdSystem.js @@ -16,7 +16,7 @@ const GVLID = 24; const PUBLINK_COOKIE = '_publink'; const PUBLINK_S2S_COOKIE = '_publink_srv'; -export const storage = getStorageManager(GVLID); +export const storage = getStorageManager({gvlid: GVLID}); function isHex(s) { return /^[A-F0-9]+$/i.test(s); diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 2477ab4c0a3..f69fb20e5d5 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -200,6 +200,21 @@ function getAdapterNameForAlias(aliasName) { return adapterManager.aliasRegistry[aliasName] || aliasName; } +function getAdDomain(bidResponse) { + if (bidResponse.meta && bidResponse.meta.advertiserDomains) { + let adomain = bidResponse.meta.advertiserDomains[0] + if (adomain) { + try { + let hostname = (new URL(adomain)); + return hostname.hostname.replace('www.', ''); + } catch (e) { + logWarn(LOG_PRE_FIX + 'Adomain URL (Not a proper URL):', adomain); + return adomain.replace('www.', ''); + } + } + } +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { @@ -218,6 +233,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'dc': bid.bidResponse ? (bid.bidResponse.dealChannel || EMPTY_STRING) : EMPTY_STRING, 'l1': bid.bidResponse ? bid.clientLatencyTimeMs : 0, 'l2': 0, + 'adv': bid.bidResponse ? getAdDomain(bid.bidResponse) || undefined : undefined, 'ss': (s2sBidders.indexOf(bid.bidder) > -1) ? 1 : 0, 't': (bid.status == ERROR && bid.error.code == TIMEOUT_ERROR) ? 1 : 0, 'wb': (highestBid && highestBid.requestId === bid.bidId ? 1 : 0), diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 462c3b94509..0667ac0fc74 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -11,6 +11,8 @@ const USER_SYNC_URL_IFRAME = 'https://ads.pubmatic.com/AdServer/js/user_sync.htm const USER_SYNC_URL_IMAGE = 'https://image8.pubmatic.com/AdServer/ImgSync?p='; const DEFAULT_CURRENCY = 'USD'; const AUCTION_TYPE = 1; +const GROUPM_ALIAS = {code: 'groupm', gvlid: 98}; +const MARKETPLACE_PARTNERS = ['groupm'] const UNDEFINED = undefined; const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; @@ -866,10 +868,10 @@ function _handleEids(payload, validBidRequests) { function _checkMediaType(bid, newBid) { // Create a regex here to check the strings - if (bid.ext && bid.ext['BidType'] != undefined) { - newBid.mediaType = MEDIATYPE[bid.ext.BidType]; + if (bid.ext && bid.ext['bidtype'] != undefined) { + newBid.mediaType = MEDIATYPE[bid.ext.bidtype]; } else { - logInfo(LOG_WARN_PREFIX + 'bid.ext.BidType does not exist, checking alternatively for mediaType') + logInfo(LOG_WARN_PREFIX + 'bid.ext.bidtype does not exist, checking alternatively for mediaType') var adm = bid.adm; var admStr = ''; var videoRegex = new RegExp(/VAST\s+version/); @@ -1005,6 +1007,7 @@ export const spec = { code: BIDDER_CODE, gvlid: 76, supportedMediaTypes: [BANNER, VIDEO, NATIVE], + aliases: [GROUPM_ALIAS], /** * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid * @@ -1063,6 +1066,12 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { + if (bidderRequest && MARKETPLACE_PARTNERS.includes(bidderRequest.bidderCode)) { + // We have got the buildRequests function call for Marketplace Partners + logInfo('For all publishers using ' + bidderRequest.bidderCode + ' bidder, the PubMatic bidder will also be enabled so PubMatic server will respond back with the bids that needs to be submitted for PubMatic and ' + bidderRequest.bidderCode + ' in the network call sent by PubMatic bidder. Hence we do not want to create a network call for ' + bidderRequest.bidderCode + '. This way we are trying to save a network call from browser.'); + return; + } + var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -1285,6 +1294,13 @@ export const spec = { }; } + // if from the server-response the bid.ext.marketplace is set then + // submit the bid to Prebid as marketplace name + if (bid.ext && !!bid.ext.marketplace && MARKETPLACE_PARTNERS.includes(bid.ext.marketplace)) { + newBid.bidderCode = bid.ext.marketplace; + newBid.bidder = bid.ext.marketplace; + } + bidResponses.push(newBid); }); }); diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 3232d34ccba..669bd062206 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -21,6 +21,14 @@ let events = { deviceDetail: {} }; +function getStorage() { + try { + return window.top['sessionStorage']; + } catch (e) { + return null; + } +} + var pubxaiAnalyticsAdapter = Object.assign(adapter( { emptyUrl, @@ -132,16 +140,25 @@ pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { function send(data, status) { if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { let location = getWindowLocation(); + const storage = getStorage(); data.initOptions = initOptions; + data.pageDetail = {}; + Object.assign(data.pageDetail, { + host: location.host, + path: location.pathname, + search: location.search + }); if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - Object.assign(data.pageDetail, { - host: location.host, - path: location.pathname, - search: location.search, - adUnitCount: data.auctionInit.adUnitCodes ? data.auctionInit.adUnitCodes.length : null - }); + data.pageDetail.adUnitCount = data.auctionInit.adUnitCodes ? data.auctionInit.adUnitCodes.length : null; data.initOptions.auctionId = data.auctionInit.auctionId; delete data.auctionInit; + + data.pmcDetail = {} + Object.assign(data.pmcDetail, { + bidDensity: storage ? storage.getItem('pbx:dpbid') : null, + maxBid: storage ? storage.getItem('pbx:mxbid') : null, + auctionId: storage ? storage.getItem('pbx:aucid') : null, + }); } data.deviceDetail = {}; Object.assign(data.deviceDetail, { @@ -150,6 +167,7 @@ function send(data, status) { deviceOS: getOS(), browser: getBrowser() }); + let pubxaiAnalyticsRequestUrl = buildUrl({ protocol: 'https', hostname: (initOptions && initOptions.hostName) || defaultHost, diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index e168339426d..449c7d12d6f 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -1,9 +1,9 @@ -import { deepAccess, logInfo, logError, isEmpty, isArray } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import find from 'core-js-pure/features/array/find.js'; +import {deepAccess, isArray, isEmpty, logError, logInfo} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {find} from '../src/polyfill.js'; const BIDDER_CODE = 'quantcast'; const DEFAULT_BID_FLOOR = 0.0000000001; @@ -21,7 +21,7 @@ export const QUANTCAST_PROTOCOL = 'https'; export const QUANTCAST_PORT = '8443'; export const QUANTCAST_FPA = '__qca'; -export const storage = getStorageManager(QUANTCAST_VENDOR_ID, BIDDER_CODE); +export const storage = getStorageManager({gvlid: QUANTCAST_VENDOR_ID, bidderCode: BIDDER_CODE}); function makeVideoImp(bid) { const videoInMediaType = deepAccess(bid, 'mediaTypes.video') || {}; diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js index 31e430d79f9..099e1fb6332 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -1,7 +1,7 @@ import { logError, replaceAuctionPrice, parseUrl } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; -import { NATIVE } from '../src/mediaTypes.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; export const ENDPOINT = 'https://app.readpeak.com/header/prebid'; @@ -19,10 +19,9 @@ const BIDDER_CODE = 'readpeak'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [NATIVE], + supportedMediaTypes: [NATIVE, BANNER], - isBidRequestValid: bid => - !!(bid && bid.params && bid.params.publisherId && bid.nativeParams), + isBidRequestValid: bid => !!(bid && bid.params && bid.params.publisherId), buildRequests: (bidRequests, bidderRequest) => { const currencyObj = config.getConfig('currency'); @@ -31,8 +30,7 @@ export const spec = { const request = { id: bidRequests[0].bidderRequestId, imp: bidRequests - .map(slot => impression(slot)) - .filter(imp => imp.native != null), + .map(slot => impression(slot)), site: site(bidRequests, bidderRequest), app: app(bidRequests), device: device(), @@ -96,10 +94,16 @@ function bidResponseAvailable(bidRequest, bidResponse) { creativeId: idToBidMap[id].crid, ttl: 300, netRevenue: true, - mediaType: NATIVE, - currency: bidResponse.cur, - native: nativeResponse(idToImpMap[id], idToBidMap[id]) + mediaType: idToImpMap[id].native ? NATIVE : BANNER, + currency: bidResponse.cur }; + if (idToImpMap[id].native) { + bid.native = nativeResponse(idToImpMap[id], idToBidMap[id]); + } else if (idToImpMap[id].banner) { + bid.ad = idToBidMap[id].adm + bid.width = idToBidMap[id].w + bid.height = idToBidMap[id].h + } if (idToBidMap[id].adomain) { bid.meta = { advertiserDomains: idToBidMap[id].adomain @@ -121,13 +125,19 @@ function impression(slot) { }); bidFloorFromModule = floorInfo.currency === 'USD' ? floorInfo.floor : undefined; } - return { + const imp = { id: slot.bidId, - native: nativeImpression(slot), bidfloor: bidFloorFromModule || slot.params.bidfloor || 0, bidfloorcur: (bidFloorFromModule && 'USD') || slot.params.bidfloorcur || 'USD', tagId: slot.params.tagId || '0' }; + + if (slot.mediaTypes.native) { + imp.native = nativeImpression(slot); + } else if (slot.mediaTypes.banner) { + imp.banner = bannerImpression(slot); + } + return imp } function nativeImpression(slot) { @@ -218,6 +228,15 @@ function dataAsset(id, params, type, defaultLen) { : null; } +function bannerImpression(slot) { + var sizes = slot.mediaTypes.banner.sizes || slot.sizes; + return { + format: sizes.map((s) => ({ w: s[0], h: s[1] })), + w: sizes[0][0], + h: sizes[0][1], + } +} + function site(bidRequests, bidderRequest) { const url = config.getConfig('pageUrl') || diff --git a/modules/readpeakBidAdapter.md b/modules/readpeakBidAdapter.md index da250e7f77a..8f8e7369ea5 100644 --- a/modules/readpeakBidAdapter.md +++ b/modules/readpeakBidAdapter.md @@ -15,17 +15,48 @@ Please reach out to your account team or hello@readpeak.com for more information # Test Parameters ```javascript - var adUnits = [{ - code: '/19968336/prebid_native_example_2', - mediaTypes: { native: { type: 'image' } }, - bids: [{ - bidder: 'readpeak', - params: { - bidfloor: 5.00, - publisherId: 'test', - siteId: 'test', - tagId: 'test-tag-1' + var adUnits = [ + { + code: '/19968336/prebid_native_example_2', + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + body: { + required: true + }, + } }, - }] - }]; + bids: [{ + bidder: 'readpeak', + params: { + bidfloor: 5.00, + publisherId: 'test', + siteId: 'test', + tagId: 'test-tag-1' + }, + }] + }, + { + code: '/19968336/prebid_banner_example_2', + mediaTypes: { + banner: { + sizes: [[640, 320], [300, 600]], + } + }, + bids: [{ + bidder: 'readpeak', + params: { + bidfloor: 5.00, + publisherId: 'test', + siteId: 'test', + tagId: 'test-tag-2' + }, + }] + } + ]; ``` diff --git a/modules/reconciliationRtdProvider.js b/modules/reconciliationRtdProvider.js index fc5f0ab621a..9b6a3d7aca3 100644 --- a/modules/reconciliationRtdProvider.js +++ b/modules/reconciliationRtdProvider.js @@ -16,10 +16,10 @@ * @property {?boolean} allowAccess */ -import { submodule } from '../src/hook.js'; -import { ajaxBuilder } from '../src/ajax.js'; -import { isGptPubadsDefined, timestamp, generateUUID, logError } from '../src/utils.js'; -import find from 'core-js-pure/features/array/find.js'; +import {submodule} from '../src/hook.js'; +import {ajaxBuilder} from '../src/ajax.js'; +import {generateUUID, isGptPubadsDefined, logError, timestamp} from '../src/utils.js'; +import {find} from '../src/polyfill.js'; /** @type {Object} */ const MessageType = { diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index 16e01f80819..db381555ef9 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -6,11 +6,11 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.0.6'; +const ADAPTER_VERSION = '1.0.7'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); function isBidRequestValid(bid) { if (!deepAccess(bid, 'params.placementId')) { @@ -31,14 +31,14 @@ function isBidRequestValid(bid) { } function buildRequests(validBidRequests, bidderRequest) { - let bidRequests = []; + const bids = []; + let imuid = null; + let bidDomain = null; + let bidder = null; + let count = null; for (let i = 0; i < validBidRequests.length; i++) { const bidRequest = validBidRequests[i]; - const placementId = getBidIdParameter('placementId', bidRequest.params); - const bidDomain = bidRequest.params.domain || BIDDER_DOMAIN; - const bidUrl = `https://${bidDomain}/bid/v1/prebid/${placementId}`; - const uuid = getUuid(); let mediaType = ''; let width = 0; let height = 0; @@ -55,46 +55,63 @@ function buildRequests(validBidRequests, bidderRequest) { mediaType = BANNER; } - let payload = { - version: ADAPTER_VERSION, - timeout_ms: bidderRequest.timeout, - ad_unit_code: bidRequest.adUnitCode, - auction_id: bidRequest.auctionId, - bidder: bidRequest.bidder, - bidder_request_id: bidRequest.bidderRequestId, - bid_requests_count: bidRequest.bidRequestsCount, - bid_id: bidRequest.bidId, - transaction_id: bidRequest.transactionId, - media_type: mediaType, - uuid: uuid, - width: width, - height: height, - pv: '$prebid.version$' - }; + if (!imuid) { + const pickImuid = deepAccess(bidRequest, 'userId.imuid'); + if (pickImuid) { + imuid = pickImuid; + } + } + + if (!bidDomain) { + bidDomain = bidRequest.params.domain; + } + + if (!bidder) { + bidder = bidRequest.bidder + } + + if (!bidder) { + bidder = bidRequest.bidder + } - const imuid = deepAccess(bidRequest, 'userId.imuid'); - if (imuid) { - payload.imuid = imuid; + if (!count) { + count = bidRequest.bidRequestsCount; } - // It may not be encoded, so add it at the end of the payload - payload.ref = bidderRequest.refererInfo.referer; - - bidRequests.push({ - method: 'GET', - url: bidUrl, - data: payload, - options: { - withCredentials: true - }, - bidId: bidRequest.bidId, + bids.push({ + bid_id: bidRequest.bidId, + placement_id: getBidIdParameter('placementId', bidRequest.params), + transaction_id: bidRequest.transactionId, + bidder_request_id: bidRequest.bidderRequestId, + ad_unit_code: bidRequest.adUnitCode, + auction_id: bidRequest.auctionId, player: bidRequest.params.player, - width: payload.width, - height: payload.height, - mediaType: payload.media_type + width: width, + height: height, + media_type: mediaType }); } - return bidRequests; + + const data = JSON.stringify({ + version: ADAPTER_VERSION, + bids: bids, + timeout_ms: bidderRequest.timeout, + bidder: bidder, + bid_requests_count: count, + uuid: getUuid(), + pv: '$prebid.version$', + imuid: imuid, + ref: bidderRequest.refererInfo.referer + }) + + return { + method: 'POST', + url: `https://${bidDomain || BIDDER_DOMAIN}/bid/v1/sprebid`, + options: { + withCredentials: true + }, + data: data + }; } function interpretResponse(serverResponse, bidRequest) { @@ -104,35 +121,39 @@ function interpretResponse(serverResponse, bidRequest) { return []; } - const playerUrl = bidRequest.player || body.playerUrl; - const mediaType = bidRequest.mediaType || VIDEO; - - let bidResponse = { - requestId: bidRequest.bidId, - width: bidRequest.width, - height: bidRequest.height, - cpm: body.price, - currency: body.currency, - creativeId: body.creativeId, - dealId: body.dealId || '', - ttl: body.ttl || DEFAULT_TTL, - netRevenue: true, - mediaType: mediaType, - meta: { - advertiserDomains: body.adomain || [], - mediaType: VIDEO + for (const res of body.ads) { + const playerUrl = res.playerUrl || bidRequest.player || body.playerUrl; + let bidResponse = { + requestId: res.bidId, + width: res.width, + height: res.height, + cpm: res.price, + currency: res.currency, + creativeId: res.creativeId, + playerUrl: playerUrl, + dealId: body.dealId || '', + ttl: body.ttl || DEFAULT_TTL, + netRevenue: true, + mediaType: res.mediaType || VIDEO, + meta: { + advertiserDomains: res.adomain || [], + mediaType: VIDEO + } + }; + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastXml = res.vast; + bidResponse.renderer = newRenderer(res.bidId, playerUrl); + } else { + const playerTag = createPlayerTag(playerUrl); + const renderTag = createRenderTag(res.width, res.height, res.vast); + bidResponse.ad = `
${playerTag}${renderTag}
`; } - }; - if (mediaType === VIDEO) { - bidResponse.vastXml = body.vast; - bidResponse.renderer = newRenderer(bidRequest.bidId, playerUrl); - } else { - const playerTag = createPlayerTag(playerUrl); - const renderTag = createRenderTag(bidRequest.width, bidRequest.height, body.vast); - bidResponse.ad = `
${playerTag}${renderTag}
`; + bidResponses.push(bidResponse); } - bidResponses.push(bidResponse); + // eslint-disable-next-line no-console + console.log(JSON.stringify(bidResponses)); return bidResponses; } diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index d0e399ab7e3..9e378f2d2ed 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -8,6 +8,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; function RhythmOneBidAdapter() { this.code = 'rhythmone'; this.supportedMediaTypes = [VIDEO, BANNER]; + this.gvlid = 36; let SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6]; let SUPPORTED_VIDEO_MIMES = ['video/mp4']; diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index eace460eb22..b49d7c5584c 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -1,4 +1,4 @@ -import { isEmpty, deepAccess, isStr } from '../src/utils.js'; +import {isEmpty, deepAccess, isStr} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -52,17 +52,22 @@ export const spec = { videoData: raiGetVideoInfo(bid), scr_rsl: raiGetResolution(), cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), - kws: (!isEmpty(bid.params.keywords) ? bid.params.keywords : null) + kws: (!isEmpty(bid.params.keywords) ? bid.params.keywords : null), + schain: bid.schain }; REFERER = (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null) payload.gdpr_consent = ''; - payload.gdpr = null; + payload.gdpr = false; if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; + if (typeof bidderRequest.gdprConsent.gdprApplies != 'undefined') { + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; + } + if (typeof bidderRequest.gdprConsent.consentString != 'undefined') { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } } var payloadString = JSON.stringify(payload); @@ -192,6 +197,13 @@ function raiGetSizes(bid) { function raiGetDemandType(bid) { let raiFormat = 'display'; + if (typeof bid.sizes != 'undefined') { + bid.sizes.forEach(function (sz) { + if ((sz[0] == '1800' && sz[1] == '1000') || (sz[0] == '1' && sz[1] == '1')) { + raiFormat = 'skin' + } + }) + } if (bid.mediaTypes != undefined) { if (bid.mediaTypes.video != undefined) { raiFormat = 'video'; @@ -295,8 +307,8 @@ function raiGetFloor(bid, config) { raiFloor = bid.params.bidfloor; } else if (typeof bid.getFloor == 'function') { let floorSpec = bid.getFloor({ - currency: config.getConfig('currency.adServerCurrency'), - mediaType: bid.mediaType.banner ? 'banner' : 'video', + currency: config.getConfig('floors.data.currency') != null ? config.getConfig('floors.data.currency') : 'USD', + mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video', size: '*' }) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 7f84dbd3344..a8ea023d46a 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -1,17 +1,17 @@ -import { logWarn, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter } from '../src/utils.js'; +import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -const SUPPORTED_AD_TYPES = [VIDEO]; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'rise'; -const ADAPTER_VERSION = '5.0.0'; +const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const CURRENCY = 'USD'; const SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; const MODES = { - PRODUCTION: 'hb', - TEST: 'hb-test' + PRODUCTION: 'hb-multi', + TEST: 'hb-multi-test' } const SUPPORTED_SYNC_METHODS = { IFRAME: 'iframe', @@ -23,7 +23,7 @@ export const spec = { gvlid: 1043, version: ADAPTER_VERSION, supportedMediaTypes: SUPPORTED_AD_TYPES, - isBidRequestValid: function(bidRequest) { + isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to Rise adapter'); return false; @@ -36,54 +36,70 @@ export const spec = { return true; }, - buildRequests: function (bidRequests, bidderRequest) { - if (bidRequests.length === 0) { - return []; - } + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; - const requests = []; + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; - bidRequests.forEach(bid => { - requests.push(buildVideoRequest(bid, bidderRequest)); - }); + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - return requests; + return { + method: 'POST', + url: getEndpoint(testMode), + data: combinedRequestsObject + } }, - interpretResponse: function({body}) { + interpretResponse: function ({body}) { const bidResponses = []; - const bidResponse = { - requestId: body.requestId, - cpm: body.cpm, - width: body.width, - height: body.height, - creativeId: body.requestId, - currency: body.currency, - netRevenue: body.netRevenue, - ttl: body.ttl || TTL, - vastXml: body.vastXml, - mediaType: VIDEO - }; - - if (body.adomain && body.adomain.length) { - bidResponse.meta = {}; - bidResponse.meta.advertiserDomains = body.adomain + if (body.bids) { + body.bids.forEach(adUnit => { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.requestId, + netRevenue: adUnit.netRevenue || true, + nurl: adUnit.nurl, + mediaType: adUnit.mediaType, + meta: { + mediaType: adUnit.mediaType + } + }; + + if (adUnit.mediaType === VIDEO) { + bidResponse.vastXml = adUnit.vastXml; + } else if (adUnit.mediaType === BANNER) { + bidResponse.ad = adUnit.ad; + } + + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } + + bidResponses.push(bidResponse); + }); } - bidResponses.push(bidResponse); return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.userSyncURL) { + if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { syncs.push({ type: 'iframe', - url: response.body.userSyncURL + url: response.body.params.userSyncURL }); } - if (syncOptions.pixelEnabled && isArray(response.body.userSyncPixels)) { - const pixels = response.body.userSyncPixels.map(pixel => { + if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel @@ -93,6 +109,16 @@ export const spec = { } } return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } } }; @@ -103,46 +129,33 @@ registerBidder(spec); * @param bid {bid} * @returns {Number} */ -function getFloor(bid) { +function getFloor(bid, mediaType) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ currency: CURRENCY, - mediaType: VIDEO, + mediaType: mediaType, size: '*' }); return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; } /** - * Build the video request - * @param bid {bid} - * @param bidderRequest {bidderRequest} - * @returns {Object} - */ -function buildVideoRequest(bid, bidderRequest) { - const sellerParams = generateParameters(bid, bidderRequest); - const {params} = bid; - return { - method: 'GET', - url: getEndpoint(params.testMode), - data: sellerParams - }; -} - -/** - * Get the the ad size from the bid + * Get the the ad sizes array from the bid * @param bid {bid} * @returns {Array} */ -function getSizes(bid) { - if (deepAccess(bid, 'mediaTypes.video.sizes')) { - return bid.mediaTypes.video.sizes[0]; +function getSizesArray(bid, mediaType) { + let sizesArray = [] + + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - return bid.sizes[0]; + sizesArray = bid.sizes; } - return []; + + return sizesArray; } /** @@ -159,7 +172,7 @@ function getSupplyChain(schainObject) { scStr += '!'; scStr += `${getEncodedValIfNotEmpty(node.asi)},`; scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${getEncodedValIfNotEmpty(node.hp)},`; + scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; scStr += `${getEncodedValIfNotEmpty(node.rid)},`; scStr += `${getEncodedValIfNotEmpty(node.name)},`; scStr += `${getEncodedValIfNotEmpty(node.domain)}`; @@ -239,122 +252,180 @@ function getDeviceType(ua) { return '1'; } +function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + + return bidsArray; +} + /** - * Generate query parameters for the request - * @param bid {bid} - * @param bidderRequest {bidderRequest} - * @returns {Object} + * Generate bid specific parameters + * @param {bid} bid + * @param {bidderRequest} bidderRequest + * @returns {Object} bid specific params object */ -function generateParameters(bid, bidderRequest) { +function generateBidParameters(bid, bidderRequest) { const {params} = bid; - const timeout = config.getConfig('bidderTimeout'); - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const [width, height] = getSizes(bid); - const {bidderCode} = bidderRequest; - const domain = window.location.hostname; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); // fix floor price in case of NAN if (isNaN(params.floorPrice)) { params.floorPrice = 0; } - const requestParams = { + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: getBidIdParameter('transactionId', bid), + }; + + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; + } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + if (mediaType === VIDEO) { + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + // verify playbackMethod is of type integer array, or integer only. + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + } + + return bidObject; +} + +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +/** + * Generate params that are common between all bids + * @param {single bid object} generalObject + * @param {bidderRequest} bidderRequest + * @returns {object} the common params object + */ +function generateGeneralParams(generalObject, bidderRequest) { + const domain = window.location.hostname; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const {bidderCode} = bidderRequest; + const generalBidParams = generalObject.params; + const timeout = config.getConfig('bidderTimeout'); + + // these params are snake_case instead of camelCase to allow backwards compatability on the server. + // in the future, these will be converted to camelCase to match our convention. + const generalParams = { wrapper_type: 'prebidjs', wrapper_vendor: '$$PREBID_GLOBAL$$', wrapper_version: '$prebid.version$', adapter_version: ADAPTER_VERSION, auction_start: timestamp(), - ad_unit_code: getBidIdParameter('adUnitCode', bid), - tmax: timeout, - width: width, - height: height, - publisher_id: params.org, - floor_price: Math.max(getFloor(bid), params.floorPrice), - ua: navigator.userAgent, - bid_id: getBidIdParameter('bidId', bid), - bidder_request_id: getBidIdParameter('bidderRequestId', bid), - transaction_id: getBidIdParameter('transactionId', bid), - session_id: getBidIdParameter('auctionId', bid), + publisher_id: generalBidParams.org, publisher_name: domain, site_domain: domain, dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent) - }; + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + session_id: getBidIdParameter('auctionId', generalObject), + tmax: timeout + } - const userIdsParam = getBidIdParameter('userId', bid); + const userIdsParam = getBidIdParameter('userId', generalObject); if (userIdsParam) { - requestParams.userIds = JSON.stringify(userIdsParam); + generalParams.userIds = JSON.stringify(userIdsParam); } const ortb2Metadata = config.getConfig('ortb2') || {}; if (ortb2Metadata.site) { - requestParams.site_metadata = JSON.stringify(ortb2Metadata.site); + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); } if (ortb2Metadata.user) { - requestParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - const playbackMethod = deepAccess(bid, 'mediaTypes.video.playbackmethod'); - if (playbackMethod) { - requestParams.playback_method = playbackMethod; - } - const placement = deepAccess(bid, 'mediaTypes.video.placement'); - if (placement) { - requestParams.placement = placement; - } - const pos = deepAccess(bid, 'mediaTypes.video.pos'); - if (pos) { - requestParams.pos = pos; - } - const minduration = deepAccess(bid, 'mediaTypes.video.minduration'); - if (minduration) { - requestParams.min_duration = minduration; - } - const maxduration = deepAccess(bid, 'mediaTypes.video.maxduration'); - if (maxduration) { - requestParams.max_duration = maxduration; - } - const skip = deepAccess(bid, 'mediaTypes.video.skip'); - if (skip) { - requestParams.skip = skip; - } - const linearity = deepAccess(bid, 'mediaTypes.video.linearity'); - if (linearity) { - requestParams.linearity = linearity; - } - - if (params.placementId) { - requestParams.placement_id = params.placementId; + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); } if (syncEnabled) { const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); if (allowedSyncMethod) { - requestParams.cs_method = allowedSyncMethod; + generalParams.cs_method = allowedSyncMethod; } } if (bidderRequest.uspConsent) { - requestParams.us_privacy = bidderRequest.uspConsent; + generalParams.us_privacy = bidderRequest.uspConsent; } if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; } - if (params.ifa) { - requestParams.ifa = params.ifa; + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; } - if (bid.schain) { - requestParams.schain = getSupplyChain(bid.schain); + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); } if (bidderRequest && bidderRequest.refererInfo) { - requestParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); - requestParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); + generalParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); } - return requestParams; + return generalParams } diff --git a/modules/roxotAnalyticsAdapter.js b/modules/roxotAnalyticsAdapter.js index c9245d4ae08..b11898b9ea8 100644 --- a/modules/roxotAnalyticsAdapter.js +++ b/modules/roxotAnalyticsAdapter.js @@ -1,10 +1,10 @@ -import { deepClone, getParameterByName, logInfo, logError } from '../src/utils.js'; +import {deepClone, getParameterByName, logError, logInfo} from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {includes} from '../src/polyfill.js'; import {ajaxBuilder} from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; const storage = getStorageManager(); diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 2c562e5841a..b8436179a30 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -1,7 +1,7 @@ -import { isArray, deepAccess, getOrigin, logError } from '../src/utils.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {deepAccess, getOrigin, isArray, logError} from '../src/utils.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {includes} from '../src/polyfill.js'; const BIDDER_CODE = 'rtbhouse'; const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; @@ -9,6 +9,7 @@ const ENDPOINT_URL = 'creativecdn.com/bidder/prebid/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; +const GVLID = 16; // Codes defined by OpenRTB Native Ads 1.1 specification export const OPENRTB = { @@ -36,6 +37,7 @@ export const OPENRTB = { export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + gvlid: GVLID, isBidRequestValid: function (bid) { return !!(includes(REGIONS, bid.params.region) && bid.params.publisherId); @@ -165,16 +167,26 @@ function mapBanner(slot) { * @returns {object} Site by OpenRTB 2.5 §3.2.13 */ function mapSite(slot, bidderRequest) { - const pubId = slot && slot.length > 0 - ? slot[0].params.publisherId - : 'unknown'; - return { + let pubId = 'unknown'; + let channel = null; + if (slot && slot.length > 0) { + pubId = slot[0].params.publisherId; + channel = slot[0].params.channel && + slot[0].params.channel + .toString() + .slice(0, 50); + } + let siteData = { publisher: { id: pubId.toString(), }, page: bidderRequest.refererInfo.referer, name: getOrigin() + }; + if (channel) { + siteData.channel = channel; } + return siteData; } /** diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index c5242c71946..381059c68f7 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -36,6 +36,7 @@ * @param {string[]} adUnitsCodes * @param {SubmoduleConfig} config * @param {UserConsentData} userConsent + * @param {auction} auction */ /** @@ -98,6 +99,15 @@ * @param {UserConsentData} userConsent */ +/** + * @function? + * @summary on bid requested event + * @name RtdSubmodule#onBidRequestEvent + * @param {Object} data + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + /** * @interface ModuleConfig */ @@ -143,11 +153,11 @@ import {config} from '../../src/config.js'; import {module} from '../../src/hook.js'; -import { logError, logWarn } from '../../src/utils.js'; -import events from '../../src/events.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 find from 'core-js-pure/features/array/find.js'; +import {find} from '../../src/polyfill.js'; import {getGlobal} from '../../src/prebidGlobal.js'; /** @type {string} */ @@ -164,13 +174,51 @@ let _dataProviders = []; let _userConsent; /** - * enable submodule in User ID + * Register a RTD submodule. + * * @param {RtdSubmodule} submodule + * @returns {function()} a de-registration function that will unregister the module when called. */ export function attachRealTimeDataProvider(submodule) { registeredSubModules.push(submodule); + return function detach() { + const idx = registeredSubModules.indexOf(submodule) + if (idx >= 0) { + registeredSubModules.splice(idx, 1); + initSubModules(); + } + } } +/** + * call each sub module event function by config order + */ +const setEventsListeners = (function () { + let registered = false; + return function setEventsListeners() { + if (!registered) { + Object.entries({ + [CONSTANTS.EVENTS.AUCTION_INIT]: ['onAuctionInitEvent'], + [CONSTANTS.EVENTS.AUCTION_END]: ['onAuctionEndEvent', getAdUnitTargeting], + [CONSTANTS.EVENTS.BID_RESPONSE]: ['onBidResponseEvent'], + [CONSTANTS.EVENTS.BID_REQUESTED]: ['onBidRequestEvent'] + }).forEach(([ev, [handler, preprocess]]) => { + events.on(ev, (args) => { + preprocess && preprocess(args); + subModules.forEach(sm => { + try { + sm[handler] && sm[handler](args, sm.config, _userConsent) + } catch (e) { + logError(`RTD provider '${sm.name}': error in '${handler}':`, e); + } + }); + }) + }); + registered = true; + } + } +})(); + export function init(config) { const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { if (!realTimeData.dataProviders) { @@ -209,22 +257,7 @@ function initSubModules() { } }); subModules = subModulesByOrder; -} - -/** - * call each sub module event function by config order - */ -function setEventsListeners() { - events.on(CONSTANTS.EVENTS.AUCTION_INIT, (args) => { - subModules.forEach(sm => { sm.onAuctionInitEvent && sm.onAuctionInitEvent(args, sm.config, _userConsent) }) - }); - events.on(CONSTANTS.EVENTS.AUCTION_END, (args) => { - getAdUnitTargeting(args); - subModules.forEach(sm => { sm.onAuctionEndEvent && sm.onAuctionEndEvent(args, sm.config, _userConsent) }) - }); - events.on(CONSTANTS.EVENTS.BID_RESPONSE, (args) => { - subModules.forEach(sm => { sm.onBidResponseEvent && sm.onBidResponseEvent(args, sm.config, _userConsent) }) - }); + logInfo(`Real time data module enabled, using submodules: ${subModules.map((m) => m.name).join(', ')}`); } /** @@ -259,18 +292,12 @@ export function setBidRequestsData(fn, reqBidsConfigObj) { return exitHook(); } - if (shouldDelayAuction) { - waitTimeout = setTimeout(exitHook, _moduleConfig.auctionDelay); - } + waitTimeout = setTimeout(exitHook, shouldDelayAuction ? _moduleConfig.auctionDelay : 0); relevantSubModules.forEach(sm => { sm.getBidRequestData(reqBidsConfigObj, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent) }); - if (!shouldDelayAuction) { - return exitHook(); - } - function onGetBidRequestDataCallback() { if (isDone) { return; @@ -278,12 +305,15 @@ export function setBidRequestsData(fn, reqBidsConfigObj) { if (this.config && this.config.waitForIt) { callbacksExpected--; } - if (callbacksExpected <= 0) { - return exitHook(); + if (callbacksExpected === 0) { + setTimeout(exitHook, 0); } } function exitHook() { + if (isDone) { + return; + } isDone = true; clearTimeout(waitTimeout); fn.call(this, reqBidsConfigObj); @@ -310,7 +340,7 @@ export function getAdUnitTargeting(auction) { } let targeting = []; for (let i = relevantSubModules.length - 1; i >= 0; i--) { - const smTargeting = relevantSubModules[i].getTargetingData(adUnitCodes, relevantSubModules[i].config, _userConsent); + const smTargeting = relevantSubModules[i].getTargetingData(adUnitCodes, relevantSubModules[i].config, _userConsent, auction); if (smTargeting && typeof smTargeting === 'object') { targeting.push(smTargeting); } else { @@ -324,6 +354,7 @@ export function getAdUnitTargeting(auction) { if (!kv) { return } + logInfo('RTD set ad unit targeting of', kv, 'for', adUnit); adUnit[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = Object.assign(adUnit[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] || {}, kv); }); return auction.adUnits; diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 5b17048cbd3..69335ff33a8 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, isGptPubadsDefined, _each, deepSetValue } from '../src/utils.js'; +import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, isGptPubadsDefined, _each, deepSetValue, deepClone, logInfo } from '../src/utils.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; @@ -8,10 +8,11 @@ import { getGlobal } from '../src/prebidGlobal.js'; import { getStorageManager } from '../src/storageManager.js'; const RUBICON_GVL_ID = 52; -export const storage = getStorageManager(RUBICON_GVL_ID, 'rubicon'); +export const storage = getStorageManager({gvlid: RUBICON_GVL_ID, moduleName: 'rubicon'}); const COOKIE_NAME = 'rpaSession'; const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours +const MODULE_NAME = 'Rubicon Analytics'; const pbsErrorMap = { 1: 'timeout-error', @@ -31,7 +32,8 @@ const { BIDDER_DONE, BID_TIMEOUT, BID_WON, - SET_TARGETING + SET_TARGETING, + BILLABLE_EVENT }, STATUS: { GOOD, @@ -55,13 +57,19 @@ const cache = { targeting: {}, timeouts: {}, gpt: {}, + billing: {} }; const BID_REJECTED_IPF = 'rejected-ipf'; export let rubiConf = { pvid: generateUUID().slice(0, 8), - analyticsEventDelay: 0 + analyticsEventDelay: 0, + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + } }; // 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 @@ -76,7 +84,7 @@ export function getHostNameFromReferer(referer) { try { rubiconAdapter.referrerHostname = parseUrl(referer, { noDecodeWholeURL: true }).hostname; } catch (e) { - logError('Rubicon Analytics: Unable to parse hostname from supplied url: ', referer, e); + logError(`${MODULE_NAME}: Unable to parse hostname from supplied url: `, referer, e); rubiconAdapter.referrerHostname = ''; } return rubiconAdapter.referrerHostname @@ -115,6 +123,55 @@ function formatSource(src) { return src.toLowerCase(); } +function getBillingPayload(event) { + // for now we are mapping all events to type "general", later we will expand support for specific types + let billingEvent = deepClone(event); + billingEvent.type = 'general'; + billingEvent.accountId = accountId; + // mark as sent + deepSetValue(cache.billing, `${event.vendor}.${event.billingId}`, true); + return billingEvent; +} + +function sendBillingEvent(event) { + let message = getBasicEventDetails(undefined, 'soloBilling'); + message.billableEvents = [getBillingPayload(event)]; + ajax( + rubiconAdapter.getUrl(), + null, + JSON.stringify(message), + { + contentType: 'application/json' + } + ); +} + +function getBasicEventDetails(auctionId, trigger) { + let auctionCache = cache.auctions[auctionId]; + let referrer = config.getConfig('pageUrl') || pageReferer || (auctionCache && auctionCache.referrer); + let message = { + timestamps: { + prebidLoaded: rubiconAdapter.MODULE_INITIALIZED_TIME, + auctionEnded: auctionCache ? auctionCache.endTs : undefined, + eventTime: Date.now() + }, + trigger, + integration: rubiConf.int_type || DEFAULT_INTEGRATION, + version: '$prebid.version$', + referrerUri: referrer, + referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer), + channel: 'web', + }; + if (rubiConf.wrapperName) { + message.wrapper = { + name: rubiConf.wrapperName, + family: rubiConf.wrapperFamily, + rule: rubiConf.rule_name + } + } + return message; +} + function sendMessage(auctionId, bidWonId, trigger) { function formatBid(bid) { return pick(bid, [ @@ -160,28 +217,8 @@ function sendMessage(auctionId, bidWonId, trigger) { samplingFactor }); } + let message = getBasicEventDetails(auctionId, trigger); let auctionCache = cache.auctions[auctionId]; - let referrer = config.getConfig('pageUrl') || (auctionCache && auctionCache.referrer); - let message = { - timestamps: { - prebidLoaded: rubiconAdapter.MODULE_INITIALIZED_TIME, - auctionEnded: auctionCache.endTs, - eventTime: Date.now() - }, - trigger, - integration: rubiConf.int_type || DEFAULT_INTEGRATION, - version: '$prebid.version$', - referrerUri: referrer, - referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer), - channel: 'web', - }; - if (rubiConf.wrapperName) { - message.wrapper = { - name: rubiConf.wrapperName, - family: rubiConf.wrapperFamily, - rule: rubiConf.rule_name - } - } if (auctionCache && !auctionCache.sent) { let adUnitMap = Object.keys(auctionCache.bids).reduce((adUnits, bidId) => { let bid = auctionCache.bids[bidId]; @@ -195,6 +232,7 @@ function sendMessage(auctionId, bidWonId, trigger) { 'adserverTargeting', () => !isEmpty(cache.targeting[bid.adUnit.adUnitCode]) ? stringProperties(cache.targeting[bid.adUnit.adUnitCode]) : undefined, 'gam', gam => !isEmpty(gam) ? gam : undefined, 'pbAdSlot', + 'gpid', 'pattern' ]); adUnit.bids = []; @@ -234,6 +272,9 @@ function sendMessage(auctionId, bidWonId, trigger) { let auction = { clientTimeoutMillis: auctionCache.timeout, + auctionStart: auctionCache.timestamp, + auctionEnd: auctionCache.endTs, + bidderOrder: auctionCache.bidderOrder, samplingFactor, accountId, adUnits: Object.keys(adUnitMap).map(i => adUnitMap[i]), @@ -318,6 +359,12 @@ function sendMessage(auctionId, bidWonId, trigger) { ]; } + // if we have not sent any billingEvents send them + const pendingBillingEvents = getPendingBillingEvents(auctionCache); + if (pendingBillingEvents && pendingBillingEvents.length) { + message.billableEvents = pendingBillingEvents; + } + ajax( this.getUrl(), null, @@ -328,6 +375,17 @@ function sendMessage(auctionId, bidWonId, trigger) { ); } +function getPendingBillingEvents(auctionCache) { + if (auctionCache && auctionCache.billing && auctionCache.billing.length) { + return auctionCache.billing.reduce((accum, billingEvent) => { + if (deepAccess(cache.billing, `${billingEvent.vendor}.${billingEvent.billingId}`) === false) { + accum.push(getBillingPayload(billingEvent)); + } + return accum; + }, []); + } +} + function adUnitIsOnlyInstream(adUnit) { return adUnit.mediaTypes && Object.keys(adUnit.mediaTypes).length === 1 && deepAccess(adUnit, 'mediaTypes.video.context') === 'instream'; } @@ -356,7 +414,7 @@ function getBidPrice(bid) { try { return Number(prebidGlobal.convertCurrency(cpm, currency, 'USD')); } catch (err) { - logWarn('Rubicon Analytics Adapter: Could not determine the bidPriceUSD of the bid ', bid); + logWarn(`${MODULE_NAME}: Could not determine the bidPriceUSD of the bid `, bid); } } @@ -384,8 +442,9 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { 'floorRuleValue', () => deepAccess(bid, 'floorData.floorRuleValue'), 'floorRule', () => debugTurnedOn() ? deepAccess(bid, 'floorData.floorRule') : undefined, 'adomains', () => { - let adomains = deepAccess(bid, 'meta.advertiserDomains'); - return Array.isArray(adomains) && adomains.length > 0 ? adomains.slice(0, 10) : undefined + 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 } ]); } @@ -445,7 +504,7 @@ function getRpaCookie() { try { return JSON.parse(window.atob(encodedCookie)); } catch (e) { - logError(`Rubicon Analytics: Unable to decode ${COOKIE_NAME} value: `, e); + logError(`${MODULE_NAME}: Unable to decode ${COOKIE_NAME} value: `, e); } } return {}; @@ -455,7 +514,7 @@ function setRpaCookie(decodedCookie) { try { storage.setDataInLocalStorage(COOKIE_NAME, window.btoa(JSON.stringify(decodedCookie))); } catch (e) { - logError(`Rubicon Analytics: Unable to encode ${COOKIE_NAME} value: `, e); + logError(`${MODULE_NAME}: Unable to encode ${COOKIE_NAME} value: `, e); } } @@ -487,14 +546,20 @@ function subscribeToGamSlots() { window.googletag.pubads().addEventListener('slotRenderEnded', event => { const isMatchingAdSlot = isAdUnitCodeMatchingSlot(event.slot); // loop through auctions and adUnits and mark the info - Object.keys(cache.auctions).forEach(auctionId => { + // only mark first auction which finds a match + let hasMatch = false; + Object.keys(cache.auctions).find(auctionId => { (Object.keys(cache.auctions[auctionId].bids) || []).forEach(bidId => { let bid = cache.auctions[auctionId].bids[bidId]; // if this slot matches this bids adUnit, add the adUnit info - if (isMatchingAdSlot(bid.adUnit.adUnitCode)) { + // only mark it if it already has not been marked + if (!bid.adUnit.gamRendered && isMatchingAdSlot(bid.adUnit.adUnitCode)) { // mark this adUnit as having been rendered by gam cache.auctions[auctionId].gamHasRendered[bid.adUnit.adUnitCode] = true; + // this current auction has an adunit that matched the slot, so mark it as matched so next auciton is skipped + hasMatch = true; + bid.adUnit.gam = pick(event, [ // these come in as `null` from Gpt, which when stringified does not get removed // so set explicitly to undefined when not a number @@ -504,6 +569,9 @@ function subscribeToGamSlots() { 'adSlot', () => event.slot.getAdUnitPath(), 'isSlotEmpty', () => event.isEmpty || undefined ]); + + // this lets us know next iteration not to check this bids adunit + bid.adUnit.gamRendered = true; } }); // Now if all adUnits have gam rendered, send the payload @@ -516,10 +584,35 @@ function subscribeToGamSlots() { sendMessage.call(rubiconAdapter, auctionId, undefined, 'gam') } } + return hasMatch; }); }); } +let pageReferer; + +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 sendOrAddEventToQueue = event => { + // if any auction is not sent yet, then add it to the auction queue + const pendingAuction = Object.keys(cache.auctions).find(auctionId => !cache.auctions[auctionId].sent); + + if (rubiConf.dmBilling.waitForAuction && pendingAuction) { + cache.auctions[pendingAuction].billing = cache.auctions[pendingAuction].billing || []; + cache.auctions[pendingAuction].billing.push(event); + } else { + // send it + sendBillingEvent(event); + } +} + let baseAdapter = adapter({ analyticsType: 'endpoint' }); let rubiconAdapter = Object.assign({}, baseAdapter, { MODULE_INITIALIZED_TIME: Date.now(), @@ -535,7 +628,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { if (config.options.endpoint) { this.getUrl = () => config.options.endpoint; } else { - logError('required endpoint missing from rubicon analytics'); + logError(`${MODULE_NAME}: required endpoint missing`); error = true; } if (typeof config.options.sampling !== 'undefined') { @@ -543,7 +636,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { } if (typeof config.options.samplingFactor !== 'undefined') { if (typeof config.options.sampling !== 'undefined') { - logWarn('Both options.samplingFactor and options.sampling enabled in rubicon analytics, defaulting to samplingFactor'); + logWarn(`${MODULE_NAME}: Both options.samplingFactor and options.sampling enabled defaulting to samplingFactor`); } samplingFactor = parseFloat(config.options.samplingFactor); config.options.sampling = 1 / samplingFactor; @@ -553,10 +646,10 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { let validSamplingFactors = [1, 10, 20, 40, 100]; if (validSamplingFactors.indexOf(samplingFactor) === -1) { error = true; - logError('invalid samplingFactor for rubicon analytics: ' + samplingFactor + ', must be one of ' + validSamplingFactors.join(', ')); + logError(`${MODULE_NAME}: invalid samplingFactor ${samplingFactor} - must be one of ${validSamplingFactors.join(', ')}`); } else if (!accountId) { error = true; - logError('required accountId missing for rubicon analytics'); + logError(`${MODULE_NAME}: required accountId missing for rubicon analytics`); } if (!error) { @@ -568,6 +661,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { accountId = undefined; rubiConf = {}; cache.gpt.registered = false; + cache.billing = {}; baseAdapter.disableAnalytics.apply(this, arguments); }, track({ eventType, args }) { @@ -582,7 +676,8 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { cacheEntry.bids = {}; cacheEntry.bidsWon = {}; cacheEntry.gamHasRendered = {}; - cacheEntry.referrer = deepAccess(args, 'bidderRequests.0.refererInfo.referer'); + cacheEntry.referrer = pageReferer = deepAccess(args, 'bidderRequests.0.refererInfo.referer'); + cacheEntry.bidderOrder = []; const floorData = deepAccess(args, 'bidderRequests.0.bids.0.floorData'); if (floorData) { cacheEntry.floorData = { ...floorData }; @@ -607,6 +702,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { } break; case BID_REQUESTED: + cache.auctions[args.auctionId].bidderOrder.push(args.bidderCode); Object.assign(cache.auctions[args.auctionId].bids, args.bids.reduce((memo, bid) => { // mark adUnits we expect bidWon events for cache.auctions[args.auctionId].bidsWon[bid.adUnitCode] = false; @@ -685,7 +781,8 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { } }, 'pbAdSlot', () => deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'), - 'pattern', () => deepAccess(bid, 'ortb2Imp.ext.data.aupname') + 'pattern', () => deepAccess(bid, 'ortb2Imp.ext.data.aupname'), + 'gpid', () => deepAccess(bid, 'ortb2Imp.ext.gpid') ]) ]); return memo; @@ -710,7 +807,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { auctionEntry.floorData.enforcements = { ...args.floorData.enforcements }; } if (!bid) { - logError('Rubicon Anlytics Adapter Error: Could not find associated bid request for bid response with requestId: ', args.requestId); + logError(`${MODULE_NAME}: Could not find associated bid request for bid response with requestId: `, args.requestId); break; } bid.source = formatSource(bid.source || args.source); @@ -781,7 +878,12 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { break; case AUCTION_END: // see how long it takes for the payload to come fire - cache.auctions[args.auctionId].endTs = Date.now(); + let auctionData = cache.auctions[args.auctionId]; + // if for some reason the auction did not do its normal thing, this could be undefied so bail + if (!auctionData) { + break; + } + auctionData.endTs = Date.now(); const isOnlyInstreamAuction = args.adUnits && args.adUnits.every(adUnit => adUnitIsOnlyInstream(adUnit)); // If only instream, do not wait around, just send payload @@ -808,6 +910,14 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { } }); 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); + sendOrAddEventToQueue(args); + } else { + logInfo(`${MODULE_NAME}: Billing event ignored`, args); + } } } }); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 501ccc98e33..afb95d56d69 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,14 +1,28 @@ -import { mergeDeep, _each, logError, deepAccess, deepSetValue, isStr, isNumber, logWarn, convertTypes, isArray, parseSizesInput, logMessage } from '../src/utils.js'; +import { + _each, + convertTypes, + deepAccess, + deepSetValue, + formatQS, + isArray, + isNumber, + isStr, + logError, + logMessage, + logWarn, + mergeDeep, + parseSizesInput +} 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 find from 'core-js-pure/features/array/find.js'; -import { Renderer } from '../src/Renderer.js'; -import { getGlobal } from '../src/prebidGlobal.js'; +import {find} from '../src/polyfill.js'; +import {Renderer} from '../src/Renderer.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; -const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.0.0.js'; +const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.2.1.js'; // renderer code at https://github.com/rubicon-project/apex2 let rubiConf = {}; @@ -393,6 +407,7 @@ export const spec = { .concat([ 'tk_flint', 'x_source.tid', + 'l_pb_bid_id', 'x_source.pchain', 'p_screen_res', 'rp_floor', @@ -466,6 +481,7 @@ export const spec = { 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, 'x_source.tid': bidRequest.transactionId, + 'l_pb_bid_id': bidRequest.bidId, 'x_source.pchain': params.pchain, 'p_screen_res': _getScreenResolution(), 'tk_user_key': params.userId, @@ -765,21 +781,23 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { if (!hasSynced && syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint - let params = ''; + let params = {}; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - // add 'gdpr' only if 'gdprApplies' is defined + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `?gdpr_consent=${gdprConsent.consentString}`; + params['gdpr'] = Number(gdprConsent.gdprApplies); + } + if (typeof gdprConsent.consentString === 'string') { + params['gdpr_consent'] = gdprConsent.consentString; } } if (uspConsent) { - params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + params['us_privacy'] = encodeURIComponent(uspConsent); } + params = Object.keys(params).length ? `?${formatQS(params)}` : ''; + hasSynced = true; return { type: 'iframe', @@ -862,7 +880,7 @@ function renderBid(bid) { height: bid.height, vastUrl: bid.vastUrl, placement: { - attachTo: `#${bid.adUnitCode}`, + attachTo: adUnitElement, align: config.align || 'center', position: config.position || 'append' }, @@ -992,6 +1010,8 @@ function applyFPD(bidRequest, mediaType, data) { let fpd = mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); let impData = deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; + + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); const SEGTAX = {user: [4], site: [1, 2, 5, 6]}; const MAP = {user: 'tg_v.', site: 'tg_i.', adserver: 'tg_i.dfp_ad_unit_code', pbadslot: 'tg_i.pbadslot', keywords: 'kw'}; const validate = function(prop, key, parentName) { @@ -1020,16 +1040,6 @@ function applyFPD(bidRequest, mediaType, data) { data[loc] = (data[loc]) ? data[loc].concat(',', val) : val; } - Object.keys(impData).forEach((key) => { - if (key === 'adserver') { - ['name', 'adslot'].forEach(prop => { - if (impData[key][prop]) impData[key][prop] = impData[key][prop].toString().replace(/^\/+/, ''); - }); - } else if (key === 'pbadslot') { - impData[key] = impData[key].toString().replace(/^\/+/, ''); - } - }); - if (mediaType === BANNER) { ['site', 'user'].forEach(name => { Object.keys(fpd[name]).forEach((key) => { @@ -1045,12 +1055,30 @@ function applyFPD(bidRequest, mediaType, data) { }); }); Object.keys(impData).forEach((key) => { - (key === 'adserver') ? addBannerData(impData[key].adslot, name, key) : addBannerData(impData[key], 'site', key); + if (key !== 'adserver') { + addBannerData(impData[key], 'site', key); + } else if (impData[key].name === 'gam') { + addBannerData(impData[key].adslot, name, key) + } }); + + // add in gpid + if (gpid) { + data['p_gpid'] = gpid; + } + + // only send one of pbadslot or dfp adunit code (prefer pbadslot) + if (data['tg_i.pbadslot']) { + delete data['tg_i.dfp_ad_unit_code']; + } } else { if (Object.keys(impData).length) { mergeDeep(data.imp[0].ext, {data: impData}); } + // add in gpid + if (gpid) { + data.imp[0].ext.gpid = gpid; + } mergeDeep(data, fpd); } diff --git a/modules/s2sTesting.js b/modules/s2sTesting.js index 1f2bb473174..8e9628c8810 100644 --- a/modules/s2sTesting.js +++ b/modules/s2sTesting.js @@ -1,12 +1,12 @@ -import { setS2STestingModule } from '../src/adapterManager.js'; +import {PARTITIONS, partitionBidders, filterBidsForAdUnit, getS2SBidderSet} from '../src/adapterManager.js'; +import {find} from '../src/polyfill.js'; +import {getBidderCodes, logWarn} from '../src/utils.js'; -let s2sTesting = {}; - -const SERVER = 'server'; -const CLIENT = 'client'; - -s2sTesting.SERVER = SERVER; -s2sTesting.CLIENT = CLIENT; +const {CLIENT, SERVER} = PARTITIONS; +export const s2sTesting = { + ...PARTITIONS, + clientTestBidders: new Set() +}; s2sTesting.bidSource = {}; // store bidder sources determined from s2sConfig bidderControl s2sTesting.globalRand = Math.random(); // if 10% of bidderA and 10% of bidderB should be server-side, make it the same 10% @@ -40,7 +40,7 @@ s2sTesting.getSourceBidderMap = function(adUnits = [], allS2SBidders = []) { [SERVER]: Object.keys(sourceBidders[SERVER]), [CLIENT]: Object.keys(sourceBidders[CLIENT]) }; -}; +} /** * @function calculateBidSources determines the source for each s2s bidder based on bidderControl weightings. these can be overridden at the adUnit level @@ -53,7 +53,7 @@ s2sTesting.calculateBidSources = function(s2sConfig = {}) { (s2sConfig.bidders || []).forEach((bidder) => { s2sTesting.bidSource[bidder] = s2sTesting.getSource(bidderControl[bidder] && bidderControl[bidder].bidSource) || SERVER; // default to server }); -}; +} /** * @function getSource() gets a random source based on the given sourceWeights (export just for testing) @@ -76,10 +76,59 @@ s2sTesting.getSource = function(sourceWeights = {}, bidSources = [SERVER, CLIENT // choose the first source with an incremental weight > random weight if (rndWeight < srcIncWeight[source]) return source; } -}; +} + +function doingS2STesting(s2sConfig) { + return s2sConfig && s2sConfig.enabled && s2sConfig.testing; +} -// inject the s2sTesting module into the adapterManager rather than importing it -// importing it causes the packager to include it even when it's not explicitly included in the build -setS2STestingModule(s2sTesting); +function isTestingServerOnly(s2sConfig) { + return Boolean(doingS2STesting(s2sConfig) && s2sConfig.testServerOnly); +} + +const adUnitsContainServerRequests = (adUnits, s2sConfig) => Boolean( + find(adUnits, adUnit => find(adUnit.bids, bid => ( + bid.bidSource || + (s2sConfig.bidderControl && s2sConfig.bidderControl[bid.bidder]) + ) && bid.finalSource === SERVER)) +); + +partitionBidders.before(function (next, adUnits, s2sConfigs) { + const serverBidders = getS2SBidderSet(s2sConfigs); + let serverOnly = false; + + s2sConfigs.forEach((s2sConfig) => { + if (doingS2STesting(s2sConfig)) { + s2sTesting.calculateBidSources(s2sConfig); + const bidderMap = s2sTesting.getSourceBidderMap(adUnits, [...serverBidders]); + // get all adapters doing client testing + bidderMap[CLIENT].forEach((bidder) => s2sTesting.clientTestBidders.add(bidder)) + } + if (isTestingServerOnly(s2sConfig) && adUnitsContainServerRequests(adUnits, s2sConfig)) { + logWarn('testServerOnly: True. All client requests will be suppressed.'); + serverOnly = true; + } + }); + + next.bail(getBidderCodes(adUnits).reduce((memo, bidder) => { + if (serverBidders.has(bidder)) { + memo[SERVER].push(bidder); + } + if (!serverOnly && (!serverBidders.has(bidder) || s2sTesting.clientTestBidders.has(bidder))) { + memo[CLIENT].push(bidder); + } + return memo; + }, {[CLIENT]: [], [SERVER]: []})); +}); + +filterBidsForAdUnit.before(function(next, bids, s2sConfig) { + if (s2sConfig == null) { + next.bail(bids.filter((bid) => !s2sTesting.clientTestBidders.size || bid.finalSource !== SERVER)); + } else { + const serverBidders = getS2SBidderSet(s2sConfig); + next.bail(bids.filter((bid) => serverBidders.has(bid.bidder) && + (!doingS2STesting(s2sConfig) || bid.finalSource !== CLIENT))); + } +}); export default s2sTesting; diff --git a/modules/saambaaBidAdapter.js b/modules/saambaaBidAdapter.js new file mode 100644 index 00000000000..36ab50bfddd --- /dev/null +++ b/modules/saambaaBidAdapter.js @@ -0,0 +1,419 @@ +import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {find, includes} from '../src/polyfill.js'; + +const ADAPTER_VERSION = '1.0'; +const BIDDER_CODE = 'saambaa'; + +export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip', 'playerSize', 'context']; +export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; + +let pubid = ''; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid(bidRequest) { + if (typeof bidRequest != 'undefined') { + if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } + if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } + return true; + } else { return false; } + }, + + buildRequests(bids, bidderRequest) { + let requests = []; + let videoBids = bids.filter(bid => isVideoBidValid(bid)); + let bannerBids = bids.filter(bid => isBannerBidValid(bid)); + videoBids.forEach(bid => { + pubid = getVideoBidParam(bid, 'pubid'); + requests.push({ + method: 'POST', + url: VIDEO_ENDPOINT + pubid, + data: createVideoRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + + bannerBids.forEach(bid => { + pubid = getBannerBidParam(bid, 'pubid'); + + requests.push({ + method: 'POST', + url: BANNER_ENDPOINT + pubid, + data: createBannerRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + return requests; + }, + + interpretResponse(serverResponse, {bidRequest}) { + let response = serverResponse.body; + if (response !== null && isEmpty(response) == false) { + if (isVideoBid(bidRequest)) { + let bidResponse = { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: VIDEO, + netRevenue: true + } + + if (response.seatbid[0].bid[0].adm) { + bidResponse.vastXml = response.seatbid[0].bid[0].adm; + bidResponse.adResponse = { + content: response.seatbid[0].bid[0].adm + }; + } else { + bidResponse.vastUrl = response.seatbid[0].bid[0].nurl; + } + + return bidResponse; + } else { + return { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ad: response.seatbid[0].bid[0].adm, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: BANNER, + netRevenue: true + } + } + } + } +}; + +function isBannerBid(bid) { + return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function isVideoBid(bid) { + return deepAccess(bid, 'mediaTypes.video'); +} + +function getBannerBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; + return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); +} + +function getVideoBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; + return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); +} + +function isVideoBidValid(bid) { + return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); +} + +function isBannerBidValid(bid) { + return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); +} + +function getVideoBidParam(bid, key) { + return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); +} + +function getBannerBidParam(bid, key) { + return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); +} + +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 getDoNotTrack() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} + +function findAndFillParam(o, key, value) { + try { + if (typeof value === 'function') { + o[key] = value(); + } else { + o[key] = value; + } + } catch (ex) {} +} + +function getOsVersion() { + let clientStrings = [ + { s: 'Android', r: /Android/ }, + { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, + { s: 'Mac OS X', r: /Mac OS X/ }, + { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, + { s: 'Linux', r: /(Linux|X11)/ }, + { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, + { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, + { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, + { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, + { s: 'Windows Vista', r: /Windows NT 6.0/ }, + { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, + { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, + { s: 'UNIX', r: /UNIX/ }, + { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } + ]; + let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); + return cs ? cs.s : 'unknown'; +} + +function getFirstSize(sizes) { + return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; +} + +function parseSizes(sizes) { + return parseSizesInput(sizes).map(size => { + let [ width, height ] = size.split('x'); + return { + w: parseInt(width, 10) || undefined, + h: parseInt(height, 10) || undefined + }; + }); +} + +function getVideoSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +function getBannerSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + +function getVideoTargetingParams(bid) { + const result = {}; + const excludeProps = ['playerSize', 'context', 'w', 'h']; + Object.keys(Object(bid.mediaTypes.video)) + .filter(key => !includes(excludeProps, key)) + .forEach(key => { + result[ key ] = bid.mediaTypes.video[ key ]; + }); + Object.keys(Object(bid.params.video)) + .filter(key => includes(VIDEO_TARGETING, key)) + .forEach(key => { + result[ key ] = bid.params.video[ key ]; + }); + return result; +} + +function createVideoRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + let paramSize = getVideoBidParam(bid, 'size'); + let sizes = []; + let coppa = config.getConfig('coppa'); + + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getVideoSizes(bid); + } + const firstSize = getFirstSize(sizes); + let floor = (getVideoBidFloor(bid) == null || typeof getVideoBidFloor(bid) == 'undefined') ? 0.5 : getVideoBidFloor(bid); + let video = getVideoTargetingParams(bid); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1, + 'os': getOsVersion() + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getVideoBidParam(bid, 'placement'); + + for (let j = 0; j < sizes.length; j++) { + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'video': Object.assign({ + 'id': generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h, + 'mimes': DEFAULT_MIMES + }, video) + + }); + } + if (coppa) { + o.regs.ext = {'coppa': 1}; + } + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} + +function getTopWindowLocation(bidderRequest) { + let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); +} + +function createBannerRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + + let paramSize = getBannerBidParam(bid, 'size'); + let sizes = []; + let coppa = config.getConfig('coppa'); + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getBannerSizes(bid); + } + + let floor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 0.1 : getBannerBidFloor(bid); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1 + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getBannerBidParam(bid, 'placement'); + for (let j = 0; j < sizes.length; j++) { + let size = sizes[j]; + + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'banner': { + 'id': generateUUID(), + 'pos': 0, + 'w': size['w'], + 'h': size['h'] + } + }); + } + if (coppa) { + o.regs.ext = {'coppa': 1}; + } + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} +registerBidder(spec); diff --git a/modules/saambaaBidAdapter.md b/modules/saambaaBidAdapter.md index 2d391da7628..d58e3f0abfa 100755 --- a/modules/saambaaBidAdapter.md +++ b/modules/saambaaBidAdapter.md @@ -1,69 +1,66 @@ -# Overview - -``` -Module Name: Saambaa Bidder Adapter -Module Type: Bidder Adapter -Maintainer: matt.voigt@saambaa.com -``` - -# Description - -Connects to Saambaa exchange for bids. - -Saambaa bid adapter supports Banner and Video ads currently. - -For more informatio - -# Sample Display Ad Unit: For Publishers -```javascript - -var displayAdUnit = [ -{ - code: 'display', - mediaTypes: { - banner: { - sizes: [[300, 250],[320, 50]] - } - } - bids: [{ - bidder: 'saambaa', - params: { - pubid: '121ab139faf7ac67428a23f1d0a9a71b', - placement: 1234, - size: '320x50' - } - }] -}]; -``` - -# Sample Video Ad Unit: For Publishers -```javascript - -var videoAdUnit = { - code: 'video', - sizes: [320,480], - mediaTypes: { - video: { - playerSize : [[320, 480]], - context: 'instream' - } - }, - bids: [ - { - bidder: 'saambaa', - params: { - pubid: '121ab139faf7ac67428a23f1d0a9a71b', - placement: 1234, - size: "320x480", - video: { - id: 123, - skip: 1, - mimes : ['video/mp4', 'application/javascript'], - playbackmethod : [2,6], - maxduration: 30 - } - } - } - ] - }; +# Overview + +``` +Module Name: Saambaa Bidder Adapter +Module Type: Bidder Adapter +Maintainer: matt.voigt@saambaa.com +``` + +# Description + +Connects to Saambaa exchange for bids. + +Saambaa bid adapter supports Banner and Video ads currently. + +For more informatio + +# Sample Display Ad Unit: For Publishers +```javascript + +var displayAdUnit = [ +{ + code: 'display', + mediaTypes: { + banner: { + sizes: [[300, 250],[320, 50]] + } + } + bids: [{ + bidder: 'saambaa', + params: { + pubid: '121ab139faf7ac67428a23f1d0a9a71b', + placement: 1234, + size: '320x50' + } + }] +}]; +``` + +# Sample Video Ad Unit: For Publishers +```javascript + +var videoAdUnit = { + code: 'video', + sizes: [320,480], + mediaTypes: { + video: { + playerSize : [[320, 480]], + context: 'instream', + skip: 1, + mimes : ['video/mp4', 'application/javascript'], + playbackmethod : [2,6], + maxduration: 30 + } + }, + bids: [ + { + bidder: 'saambaa', + params: { + pubid: '121ab139faf7ac67428a23f1d0a9a71b', + placement: 1234, + size: "320x480" + } + } + ] + }; ``` \ No newline at end of file diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index 00f3b64fb44..05dcf15909a 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -152,10 +152,10 @@ export const spec = { const { seatbid, cur } = serverResponse.body; - const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + const bidResponses = (typeof seatbid != 'undefined') ? flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { result[bid.impid - 1] = bid; return result; - }, []); + }, []) : []; return bids .map((bid, id) => { @@ -167,7 +167,7 @@ export const spec = { cpm: bidResponse.price, creativeId: bidResponse.crid, ttl: 1000, - netRevenue: bid.netRevenue === 'net', + netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), currency: cur, mediaType: NATIVE, bidderCode: BIDDER_CODE, @@ -189,12 +189,12 @@ function parseNative(bid) { if (link.clicktrackers) { link.clicktrackers.forEach(function (clicktracker, index) { - link.clicktrackers[index] = clicktracker.replace(/\$\{AUCTION_PRICE\}/, bid.price); + link.clicktrackers[index] = clicktracker.replace(/\$\{AUCTION_PRICE\}/g, bid.price); }); } if (imptrackers) { imptrackers.forEach(function (imptracker, index) { - imptrackers[index] = imptracker.replace(/\$\{AUCTION_PRICE\}/, bid.price); + imptrackers[index] = imptracker.replace(/\$\{AUCTION_PRICE\}/g, bid.price); }); } diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index cb646fe10c3..2f61e0bc56a 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -13,6 +13,11 @@ const ALLOWED_PLACEMENTS = { banner: true, video: true } + +// Global Vendor List Id +// https://iabeurope.eu/vendor-list-tcf-v2-0/ +const GVLID = 157; + const mediaTypesMap = { [BANNER]: 'display', [VIDEO]: 'video' @@ -149,15 +154,19 @@ export function getTimeoutUrl (data) { isArray(data[0].params) && data[0].params[0] ) { const params = data[0].params[0]; + const timeout = data[0].timeout + queryParams = '?publisherToken=' + params.publisherId + - '&adUnitId=' + params.adUnitId; + '&adUnitId=' + params.adUnitId + + '&timeout=' + timeout; } return SEEDTAG_SSP_ONTIMEOUT_ENDPOINT + queryParams; } export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: [SEEDTAG_ALIAS], supportedMediaTypes: [BANNER, VIDEO], /** diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 0c25a1747f6..656b62815c7 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -11,7 +11,7 @@ import { coppaDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; const GVLID = 887; -const storage = getStorageManager(GVLID, 'pubCommonId'); +export const storage = getStorageManager({gvlid: GVLID, moduleName: 'pubCommonId'}); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const OPTOUT_NAME = '_pubcid_optout'; @@ -171,7 +171,33 @@ export const sharedIdSystemSubmodule = { return {id: storedId}; } } + }, + + domainOverride: function () { + const domainElements = document.domain.split('.'); + const cookieName = `_gd${Date.now()}`; + for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { + const nextDomain = domainElements.slice(i).join('.'); + + // write test cookie + storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); + + // read test cookie to verify domain was valid + testCookie = storage.getCookie(cookieName); + + // delete test cookie + storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + + if (testCookie === '1') { + // cookie was written successfully using test domain so the topDomain is updated + topDomain = nextDomain; + } else { + // cookie failed to write using test domain so exit by returning the topDomain + return topDomain; + } + } } + }; submodule('userId', sharedIdSystemSubmodule); diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 36d1a94e93d..1dd95812e12 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,10 +1,10 @@ -import { generateUUID, deepAccess, inIframe } from '../src/utils.js'; +import { deepAccess, generateUUID, inIframe } 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 { createEidsArray } from './userId/eids.js'; -const VERSION = '4.0.1'; +const VERSION = '4.1.0'; const BIDDER_CODE = 'sharethrough'; const SUPPLY_ID = 'WYu2BXv1'; @@ -18,11 +18,12 @@ export const sharethroughInternal = { export const sharethroughAdapterSpec = { code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], - + gvlid: 80, isBidRequestValid: bid => !!bid.params.pkey && bid.bidder === BIDDER_CODE, buildRequests: (bidRequests, bidderRequest) => { const timeout = config.getConfig('bidderTimeout'); + const firstPartyData = config.getConfig('ortb2') || {}; const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; const secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1); @@ -35,12 +36,8 @@ export const sharethroughAdapterSpec = { site: { domain: window.location.hostname, page: window.location.href, - ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null, - }, - user: { - ext: { - eids: userIdAsEids(bidRequests[0]), - }, + ref: deepAccess(bidderRequest, 'refererInfo.referer'), + ...firstPartyData.site, }, device: { ua: navigator.userAgent, @@ -66,6 +63,10 @@ export const sharethroughAdapterSpec = { test: 0, }; + req.user = nullish(firstPartyData.user, {}); + if (!req.user.ext) req.user.ext = {}; + req.user.ext.eids = userIdAsEids(bidRequests[0]); + if (bidderRequest.gdprConsent) { const gdprApplies = bidderRequest.gdprConsent.gdprApplies === true; req.regs.ext.gdpr = gdprApplies ? 1 : 0; @@ -86,15 +87,9 @@ export const sharethroughAdapterSpec = { impression.ext = { gpid: gpid }; } - // if request is for video, we only support instream - if (bidReq.mediaTypes && bidReq.mediaTypes.video && bidReq.mediaTypes.video.context === 'outstream') { - // return null so we can easily remove this imp from the array of imps that we send to adserver - return null; - } - - if (bidReq.mediaTypes && bidReq.mediaTypes.video) { - const videoRequest = bidReq.mediaTypes.video; + const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); + if (videoRequest) { // default playerSize, only change this if we know width and height are properly defined in the request let [w, h] = [640, 360]; if (videoRequest.playerSize && videoRequest.playerSize[0] && videoRequest.playerSize[1]) { @@ -117,9 +112,9 @@ export const sharethroughAdapterSpec = { startdelay: nullish(videoRequest.startdelay, 0), skipmin: nullish(videoRequest.skipmin, 0), skipafter: nullish(videoRequest.skipafter, 0), + placement: videoRequest.context === 'instream' ? 1 : +deepAccess(videoRequest, 'placement', 4), }; - if (videoRequest.placement) impression.video.placement = videoRequest.placement; if (videoRequest.delivery) impression.video.delivery = videoRequest.delivery; if (videoRequest.companiontype) impression.video.companiontype = videoRequest.companiontype; if (videoRequest.companionad) impression.video.companionad = videoRequest.companionad; diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index 99378b494df..4c8fb812edc 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -61,15 +61,24 @@ export const spec = { } } + const consentData = bidderRequest.gdprConsent || {}; + + const gdprConsent = { + apiVersion: consentData.apiVersion || 2, + gdprApplies: consentData.gdprApplies || 0, + consentString: consentData.consentString || '', + } + return { type: streamType, + adUnitCode: bid.adUnitCode, bidId: bid.bidId, mediaType: type, context: context, playerId: getBidIdParameter('playerId', bid.params), auctionId: bidderRequest.auctionId, bidderCode: BIDDER_CODE, - gdprConsent: bidderRequest.gdprConsent, + gdprConsent: gdprConsent, start: +new Date(), timeout: 3000, size: { @@ -159,6 +168,7 @@ function createBids(bidRes, reqData) { let bidUnit = {}; bidUnit.cpm = bid.cpm; bidUnit.requestId = bid.bidId; + bidUnit.adUnitCode = reqBid.adUnitCode; bidUnit.currency = bid.currency; bidUnit.mediaType = bid.mediaType || VIDEO; bidUnit.ttl = TTL; @@ -183,7 +193,8 @@ function createBids(bidRes, reqData) { } else if (bid.context === 'outstream') { const renderer = Renderer.install({ id: bid.bidId, - url: '//', + url: 'https://static.showheroes.com/renderer.js', + adUnitCode: reqBid.adUnitCode, config: { playerId: reqBid.playerId, width: bid.size.width, diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js index da0ca9e38e5..a0521bd5297 100644 --- a/modules/sigmoidAnalyticsAdapter.js +++ b/modules/sigmoidAnalyticsAdapter.js @@ -1,11 +1,11 @@ /* Sigmoid Analytics Adapter for prebid.js v1.1.0-pre Updated : 2018-03-28 */ -import includes from 'core-js-pure/features/array/includes.js'; +import {includes} from '../src/polyfill.js'; import adapter from '../src/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { generateUUID, logInfo, logError } from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {generateUUID, logError, logInfo} from '../src/utils.js'; const storage = getStorageManager(); diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 344357bcb62..182ff384fef 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -7,12 +7,12 @@ * @requires module:modules/realTimeData */ import {getGlobal} from '../src/prebidGlobal.js'; -import { deepAccess, logError, deepEqual, deepSetValue, isEmpty, mergeDeep } from '../src/utils.js'; +import {deepAccess, deepEqual, deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; -import findIndex from 'core-js-pure/features/array/find-index.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { config } from '../src/config.js'; +import {findIndex} from '../src/polyfill.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {config} from '../src/config.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 95f0eea4075..405799813eb 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -4,12 +4,19 @@ * rendering. Read full API documentation on Prebid.org, http://prebid.org/dev-docs/modules/sizeMappingV2.html */ -import { isArray, logError, isArrayOfNums, deepClone, logWarn, getWindowTop, deepEqual, logInfo, isValidMediaTypes, deepAccess, getDefinedParams, getUniqueIdentifierStr, flatten } from '../src/utils.js'; -import { processNativeAdUnitParams } from '../src/native.js'; -import { adunitCounter } from '../src/adUnits.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import { getHook } from '../src/hook.js'; -import { adUnitSetupChecks } from '../src/prebid.js'; +import { + deepClone, + getWindowTop, + isArray, + isArrayOfNums, + isValidMediaTypes, + logError, + logInfo, + logWarn +} from '../src/utils.js'; +import {includes} from '../src/polyfill.js'; +import {getHook} from '../src/hook.js'; +import {adUnitSetupChecks} from '../src/prebid.js'; // Allows for stubbing of these functions while writing unit tests. export const internal = { @@ -21,61 +28,33 @@ export const internal = { isLabelActivated }; -/* - 'sizeMappingInternalStore' contains information on, whether a particular auction is using size mapping V2 (the new size mapping spec), - and it also contains additional information on each adUnit, such as, mediaTypes, activeViewport, etc. This information is required by - the 'getBids' function. -*/ - -export const sizeMappingInternalStore = createSizeMappingInternalStore(); - -function createSizeMappingInternalStore() { - const sizeMappingInternalStore = {}; - - return { - initializeStore: function (auctionId, isUsingSizeMappingBool) { - sizeMappingInternalStore[auctionId] = { - usingSizeMappingV2: isUsingSizeMappingBool, - adUnits: [] - }; - }, - getAuctionDetail: function (auctionId) { - return sizeMappingInternalStore[auctionId]; - }, - setAuctionDetail: function (auctionId, adUnitDetail) { - sizeMappingInternalStore[auctionId].adUnits.push(adUnitDetail); - } - } -} +const V2_ADUNITS = new WeakMap(); /* Returns "true" if at least one of the adUnits in the adUnits array is using an Ad Unit and/or Bidder level sizeConfig, otherwise, returns "false." */ export function isUsingNewSizeMapping(adUnits) { - let isUsingSizeMappingBool = false; - adUnits.forEach(adUnit => { + return !!adUnits.find(adUnit => { + if (V2_ADUNITS.has(adUnit)) return V2_ADUNITS.get(adUnit); if (adUnit.mediaTypes) { // checks for the presence of sizeConfig property at the adUnit.mediaTypes object - Object.keys(adUnit.mediaTypes).forEach(mediaType => { + for (let mediaType of Object.keys(adUnit.mediaTypes)) { if (adUnit.mediaTypes[mediaType].sizeConfig) { - if (isUsingSizeMappingBool === false) { - isUsingSizeMappingBool = true; - } + V2_ADUNITS.set(adUnit, true); + return true; } - }); - - // checks for the presence of sizeConfig property at the adUnit.bids[].bidder object - adUnit.bids && isArray(adUnit.bids) && adUnit.bids.forEach(bidder => { - if (bidder.sizeConfig) { - if (isUsingSizeMappingBool === false) { - isUsingSizeMappingBool = true; - } + } + for (let bid of adUnit.bids && isArray(adUnit.bids) ? adUnit.bids : []) { + if (bid.sizeConfig) { + V2_ADUNITS.set(adUnit, true); + return true; } - }); + } + V2_ADUNITS.set(adUnit, false); + return false; } }); - return isUsingSizeMappingBool; } /** @@ -168,19 +147,12 @@ export function checkAdUnitSetupHook(adUnits) { } const validatedAdUnits = []; adUnits.forEach(adUnit => { - const bids = adUnit.bids; + adUnit = adUnitSetupChecks.validateAdUnit(adUnit); + if (adUnit == null) return; + const mediaTypes = adUnit.mediaTypes; let validatedBanner, validatedVideo, validatedNative; - if (!bids || !isArray(bids)) { - logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`); - return; - } - - if (!mediaTypes || Object.keys(mediaTypes).length === 0) { - logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); - return; - } if (mediaTypes.banner) { if (mediaTypes.banner.sizes) { // Ad unit is using 'mediaTypes.banner.sizes' instead of the new property 'sizeConfig'. Apply the old checks! @@ -290,23 +262,11 @@ export function checkBidderSizeConfigFormat(sizeConfig) { return didCheckPass; } -getHook('getBids').before(function (fn, bidderInfo) { - // check if the adUnit is using sizeMappingV2 specs and store the result in _sizeMappingUsageMap. - if (typeof sizeMappingInternalStore.getAuctionDetail(bidderInfo.auctionId) === 'undefined') { - const isUsingSizeMappingBool = isUsingNewSizeMapping(bidderInfo.adUnits); - - // initialize sizeMappingInternalStore for the first time for a particular auction - sizeMappingInternalStore.initializeStore(bidderInfo.auctionId, isUsingSizeMappingBool); - } - if (sizeMappingInternalStore.getAuctionDetail(bidderInfo.auctionId).usingSizeMappingV2) { - // if adUnit is found using sizeMappingV2 specs, run the getBids function which processes the sizeConfig object - // and returns the bids array for a particular bidder. - - const bids = getBids(bidderInfo); - return fn.bail(bids); +getHook('setupAdUnitMediaTypes').before(function (fn, adUnits, labels) { + if (isUsingNewSizeMapping(adUnits)) { + return fn.bail(setupAdUnitMediaTypes(adUnits, labels)); } else { - // if not using sizeMappingV2, default back to the getBids function defined in adapterManager. - return fn.call(this, bidderInfo); + return fn.call(this, adUnits, labels); } }); @@ -424,8 +384,8 @@ export function getFilteredMediaTypes(mediaTypes) { return sizeBucketToSizeMap; }, {}); - return { mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes }; -}; + return { sizeBucketToSizeMap, activeViewport, transformedMediaTypes }; +} /** * Evaluates the given sizeConfig object and checks for various properties to determine if the sizeConfig is active or not. For example, @@ -476,126 +436,87 @@ export function getActiveSizeBucket(sizeConfig, activeViewport) { } export function getRelevantMediaTypesForBidder(sizeConfig, activeViewport) { + const mediaTypes = new Set(); if (internal.checkBidderSizeConfigFormat(sizeConfig)) { const activeSizeBucket = internal.getActiveSizeBucket(sizeConfig, activeViewport); - return sizeConfig.filter(config => config.minViewPort === activeSizeBucket)[0]['relevantMediaTypes']; + sizeConfig.filter(config => config.minViewPort === activeSizeBucket)[0]['relevantMediaTypes'].forEach((mt) => mediaTypes.add(mt)); } - return []; + return mediaTypes; } -// sets sizeMappingInternalStore for a given auctionId with relevant adUnit information returned from the call to 'getFilteredMediaTypes' function -// returns adUnit details object. -export function getAdUnitDetail(auctionId, adUnit, labels) { - // fetch all adUnits for an auction from the sizeMappingInternalStore - const adUnitsForAuction = sizeMappingInternalStore.getAuctionDetail(auctionId).adUnits; - - // check if the adUnit exists already in the sizeMappingInterStore (check for equivalence of 'code' && 'mediaTypes' properties) - const adUnitDetail = adUnitsForAuction.filter(adUnitDetail => adUnitDetail.adUnitCode === adUnit.code && deepEqual(adUnitDetail.mediaTypes, adUnit.mediaTypes)); - - if (adUnitDetail.length > 0) { - adUnitDetail[0].cacheHits++; - return adUnitDetail[0]; - } else { - const identicalAdUnit = adUnitsForAuction.filter(adUnitDetail => adUnitDetail.adUnitCode === adUnit.code); - const adUnitInstance = identicalAdUnit.length > 0 && typeof identicalAdUnit[0].instance === 'number' ? identicalAdUnit[identicalAdUnit.length - 1].instance + 1 : 1; - const isLabelActivated = internal.isLabelActivated(adUnit, labels, adUnit.code, adUnitInstance); - const { mediaTypes = adUnit.mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = isLabelActivated && internal.getFilteredMediaTypes(adUnit.mediaTypes); - - const adUnitDetail = { - adUnitCode: adUnit.code, - mediaTypes, - sizeBucketToSizeMap, - activeViewport, - transformedMediaTypes, - instance: adUnitInstance, - isLabelActivated, - cacheHits: 0 - }; - - // set adUnitDetail in sizeMappingInternalStore against the correct 'auctionId'. - sizeMappingInternalStore.setAuctionDetail(auctionId, adUnitDetail); - isLabelActivated && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Active size buckets after filtration: `, sizeBucketToSizeMap); - - return adUnitDetail; - } +export function getAdUnitDetail(adUnit, labels, adUnitInstance) { + const isLabelActivated = internal.isLabelActivated(adUnit, labels, adUnit.code, adUnitInstance); + const { sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = isLabelActivated && internal.getFilteredMediaTypes(adUnit.mediaTypes); + isLabelActivated && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Active size buckets after filtration: `, sizeBucketToSizeMap); + return { + activeViewport, + transformedMediaTypes, + isLabelActivated, + }; } -export function getBids({ bidderCode, auctionId, bidderRequestId, adUnits, labels, src }) { +export function setupAdUnitMediaTypes(adUnits, labels) { + const duplCounter = {}; return adUnits.reduce((result, adUnit) => { + const instance = (() => { + if (!duplCounter.hasOwnProperty(adUnit.code)) { + duplCounter[adUnit.code] = 1; + } + return duplCounter[adUnit.code]++; + })(); if (adUnit.mediaTypes && isValidMediaTypes(adUnit.mediaTypes)) { - const { activeViewport, transformedMediaTypes, instance: adUnitInstance, isLabelActivated, cacheHits } = internal.getAdUnitDetail(auctionId, adUnit, labels); + const { activeViewport, transformedMediaTypes, isLabelActivated } = internal.getAdUnitDetail(adUnit, labels, instance); if (isLabelActivated) { - // check if adUnit has any active media types remaining, if not drop the adUnit from auction, - // else proceed to evaluate the bids object. if (Object.keys(transformedMediaTypes).length === 0) { - cacheHits === 0 && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); - return result; - } - result - .push(adUnit.bids.filter(bid => bid.bidder === bidderCode) - .reduce((bids, bid) => { - if (internal.isLabelActivated(bid, labels, adUnit.code, adUnitInstance)) { - // handle native params - const nativeParams = adUnit.nativeParams || deepAccess(adUnit, 'mediaTypes.native'); - if (nativeParams) { - bid = Object.assign({}, bid, { - nativeParams: processNativeAdUnitParams(nativeParams) - }); - } - - bid = Object.assign({}, bid, getDefinedParams(adUnit, ['mediaType', 'renderer'])); - - if (bid.sizeConfig) { - const relevantMediaTypes = internal.getRelevantMediaTypesForBidder(bid.sizeConfig, activeViewport); - if (relevantMediaTypes.length === 0) { - logError(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bidderCode} => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remail active.`); - bid = Object.assign({}, bid); - } else if (relevantMediaTypes[0] !== 'none') { - const bidderMediaTypes = Object - .keys(transformedMediaTypes) - .filter(mt => relevantMediaTypes.indexOf(mt) > -1) - .reduce((mediaTypes, mediaType) => { - mediaTypes[mediaType] = transformedMediaTypes[mediaType]; - return mediaTypes; - }, {}); - - if (Object.keys(bidderMediaTypes).length > 0) { - bid = Object.assign({}, bid, { mediaTypes: bidderMediaTypes }); - } else { - logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`); - return bids; + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); + } else { + adUnit.mediaTypes = transformedMediaTypes; + adUnit.bids = adUnit.bids.reduce((bids, bid) => { + if (internal.isLabelActivated(bid, labels, adUnit.code, instance)) { + if (bid.sizeConfig) { + const relevantMediaTypes = internal.getRelevantMediaTypesForBidder(bid.sizeConfig, activeViewport); + if (relevantMediaTypes.size === 0) { + logError(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}), Bidder: ${bid.bidder} => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remain active.`); + bids.push(bid); + } else if (!relevantMediaTypes.has('none')) { + let modified = false; + const bidderMediaTypes = Object.fromEntries( + Object.entries(transformedMediaTypes) + .filter(([key, val]) => { + if (!relevantMediaTypes.has(key)) { + modified = true; + return false; + } + return true; + }) + ); + if (Object.keys(bidderMediaTypes).length > 0) { + if (modified) { + bid.mediaTypes = bidderMediaTypes; } + bids.push(bid); } else { - logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' is set to 'none' in sizeConfig for current viewport size. This bidder is disabled.`); - return bids; + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`); } + } else { + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}), Bidder: ${bid.bidder} => 'relevantMediaTypes' is set to 'none' in sizeConfig for current viewport size. This bidder is disabled.`); } - bids.push(Object.assign({}, bid, { - adUnitCode: adUnit.code, - transactionId: adUnit.transactionId, - sizes: deepAccess(transformedMediaTypes, 'banner.sizes') || deepAccess(transformedMediaTypes, 'video.playerSize') || [], - mediaTypes: bid.mediaTypes || transformedMediaTypes, - bidId: bid.bid_id || getUniqueIdentifierStr(), - bidderRequestId, - auctionId, - src, - bidRequestsCount: adunitCounter.getRequestsCounter(adUnit.code), - bidderRequestsCount: adunitCounter.getBidderRequestsCounter(adUnit.code, bid.bidder), - bidderWinsCount: adunitCounter.getBidderWinsCounter(adUnit.code, bid.bidder) - })); - return bids; } else { - logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}), Bidder: ${bid.bidder} => Label check for this bidder has failed. This bidder is disabled.`); - return bids; + bids.push(bid); } - }, [])); + } else { + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}), Bidder: ${bid.bidder} => Label check for this bidder has failed. This bidder is disabled.`); + } + return bids; + }, []); + result.push(adUnit); + } } else { - cacheHits === 0 && logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${adUnitInstance}) => Ad unit is disabled due to failing label check.`); + logInfo(`Size Mapping V2:: Ad Unit: ${adUnit.code}(${instance}) => Ad unit is disabled due to failing label check.`); } } else { logWarn(`Size Mapping V2:: Ad Unit: ${adUnit.code} => Ad unit has declared invalid 'mediaTypes' or has not declared a 'mediaTypes' property`); - return result; } return result; - }, []).reduce(flatten, []).filter(val => val !== ''); + }, []) } diff --git a/modules/slimcutBidAdapter.js b/modules/slimcutBidAdapter.js index c2592137fd8..2d35e09d777 100644 --- a/modules/slimcutBidAdapter.js +++ b/modules/slimcutBidAdapter.js @@ -9,7 +9,8 @@ const BIDDER_CODE = 'slimcut'; const ENDPOINT_URL = 'https://sb.freeskreen.com/pbr'; export const spec = { code: BIDDER_CODE, - aliases: ['scm'], + gvlid: 52, + aliases: [{ code: 'scm', gvlid: 52 }], supportedMediaTypes: ['video', 'banner'], /** * Determines whether or not the given bid request is valid. diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index dd389b42098..b792983534d 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -5,7 +5,7 @@ import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.4' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.6' const CURRENCY = 'USD'; const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { @@ -16,9 +16,6 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { tmax: bidderRequest.timeout, site: { id: window.location.hostname, - publisher: { - id: deepAccess(bidRequest, 'params.publisherId') - }, domain: window.location.hostname, page: window.location.href, ref: bidderRequest.refererInfo.referer @@ -37,6 +34,11 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { user: { ext: {} }, + source: { + ext: { + schain: bidRequest.schain + } + }, ext: { client: SMAATO_CLIENT } @@ -46,6 +48,8 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { Object.assign(requestTemplate.user, ortb2.user); Object.assign(requestTemplate.site, ortb2.site); + deepSetValue(requestTemplate, 'site.publisher.id', deepAccess(bidRequest, 'params.publisherId')); + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { deepSetValue(requestTemplate, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); deepSetValue(requestTemplate, 'user.ext.consent', bidderRequest.gdprConsent.consentString); @@ -105,6 +109,7 @@ const buildServerRequest = (validBidRequest, data) => { export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], + gvlid: 82, /** * Determines whether or not the given bid request is valid. @@ -299,6 +304,7 @@ function createBannerImp(bidRequest) { id: bidRequest.bidId, tagid: deepAccess(bidRequest, 'params.adspaceId'), bidfloor: getBidFloor(bidRequest, BANNER, adUnitSizes), + instl: deepAccess(bidRequest.ortb2Imp, 'instl'), banner: { w: sizes[0].w, h: sizes[0].h, @@ -314,6 +320,7 @@ function createVideoImp(bidRequest, videoMediaType) { id: bidRequest.bidId, tagid: deepAccess(bidRequest, 'params.adspaceId'), bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), + instl: deepAccess(bidRequest.ortb2Imp, 'instl'), video: { mimes: videoMediaType.mimes, minduration: videoMediaType.minduration, @@ -341,6 +348,7 @@ function createAdPodImp(bidRequest, videoMediaType) { id: bidRequest.bidId, tagid: tagid, bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), + instl: deepAccess(bidRequest.ortb2Imp, 'instl'), video: { w: videoMediaType.playerSize[0][0], h: videoMediaType.playerSize[0][1], diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js new file mode 100644 index 00000000000..a94ed972b2e --- /dev/null +++ b/modules/smarthubBidAdapter.js @@ -0,0 +1,187 @@ +import {deepAccess, isFn, logError, logMessage} from '../src/utils.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 = 'smarthub'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency || !bid.hasOwnProperty('netRevenue')) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.width && bid.height && (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 { partnerName, seat, token, iabCat, minBidfloor, pos } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + partnerName: partnerName.toLowerCase(), + seat, + token, + iabCat, + minBidfloor, + pos, + bidId, + schain, + bidfloor + }; + + 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 (e) { + logError(e); + return 0; + } +} + +function buildRequestParams(bidderRequest = {}, placements = []) { + 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.referer; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + 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; + return { + 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') + }; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && params.partnerName && params.seat && params.token); + + 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 = {}) => { + const tempObj = {}; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const data = getPlacementReqData(bid); + tempObj[data.partnerName] = tempObj[data.partnerName] || []; + tempObj[data.partnerName].push(data); + } + + return Object.keys(tempObj).map(key => { + const request = buildRequestParams(bidderRequest, tempObj[key]); + return { + method: 'POST', + url: `https://${key}-prebid.smart-hub.io/pbjs`, + 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/smarthubBidAdapter.md b/modules/smarthubBidAdapter.md new file mode 100644 index 00000000000..c09855303e2 --- /dev/null +++ b/modules/smarthubBidAdapter.md @@ -0,0 +1,94 @@ +# Overview + +``` +Module Name: SmartHub Bidder Adapter +Module Type: SmartHub Bidder Adapter +Maintainer: support@smart-hub.io +``` + +# Description + +Connects to SmartHub exchange for bids. + +SmartHub 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: 'smarthub', + params: { + partnerName: 'pbjstest', + seat: 'testSeat', + token: 'testBanner', + iabCat: ['IAB1-1', 'IAB3-1', 'IAB4-3'], + minBidfloor: 10, + pos: 1, + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'smarthub', + params: { + partnerName: 'pbjstest', + seat: 'testSeat', + token: 'testVideo', + iabCat: ['IAB1-1', 'IAB3-1', 'IAB4-3'], + minBidfloor: 10, + pos: 1, + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'smarthub', + params: { + partnerName: 'pbjstest', + seat: 'testSeat', + token: 'testNative', + iabCat: ['IAB1-1', 'IAB3-1', 'IAB4-3'], + minBidfloor: 10, + pos: 1, + } + } + ] + } + ]; +``` diff --git a/modules/smarticoBidAdapter.js b/modules/smarticoBidAdapter.js index 2399a12f932..edb774f812f 100644 --- a/modules/smarticoBidAdapter.js +++ b/modules/smarticoBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {find} from '../src/polyfill.js'; const SMARTICO_CONFIG = { bidRequestUrl: 'https://trmads.eu/preBidRequest', diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index da63331cd0f..00c962445d9 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -161,11 +161,20 @@ export const spec = { domain: domain, publisher: { id: publisherId + }, + content: { + ext: { + prebid: { + name: 'pbjs', + version: '$prebid.version$' + } + } } }, device: device, at: at, - cur: cur + cur: cur, + ext: {} }; const userExt = {}; @@ -194,6 +203,8 @@ export const spec = { }; } + // requestPayload.user.ext.ver = pbjs.version; + // Targeting if (getBidIdParameter('data', bid.params.user)) { var targetingarr = []; @@ -336,6 +347,7 @@ function createOutstreamConfig(bid) { let confTitle = getBidIdParameter('title', bid.renderer.config.outstream_options); let confSkipOffset = getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); let confDesiredBitrate = getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); + let confVisibilityThreshold = getBidIdParameter('visibilityThreshold', bid.renderer.config.outstream_options); let elementId = getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); @@ -384,6 +396,10 @@ function createOutstreamConfig(bid) { smartPlayObj.desiredBitrate = confDesiredBitrate; } + if (confVisibilityThreshold) { + smartPlayObj.visibilityThreshold = confVisibilityThreshold; + } + smartPlayObj.adResponse = bid.vastContent; const divID = '[id="' + elementId + '"]'; diff --git a/modules/sortableAnalyticsAdapter.js b/modules/sortableAnalyticsAdapter.js index 76d3ca63d69..4580ce6dbb8 100644 --- a/modules/sortableAnalyticsAdapter.js +++ b/modules/sortableAnalyticsAdapter.js @@ -5,6 +5,7 @@ import adapterManager from '../src/adapterManager.js'; import {ajax} from '../src/ajax.js'; import {getGlobal} from '../src/prebidGlobal.js'; import { config } from '../src/config.js'; +import {bidderSettings} from '../src/bidderSettings.js'; const DEFAULT_PROTOCOL = 'https'; const DEFAULT_HOST = 'pa.deployads.com'; @@ -182,11 +183,11 @@ function getFactor(bidder) { } function getBiddersFactors() { - const pb = getGlobal(); const result = {}; - if (pb && pb.bidderSettings) { - Object.keys(pb.bidderSettings).forEach(bidderKey => { - const bidder = pb.bidderSettings[bidderKey]; + const settings = bidderSettings.getSettings(); + if (settings) { + Object.keys(settings).forEach(bidderKey => { + const bidder = settings[bidderKey]; const factor = getFactor(bidder); if (factor !== null) { result[bidderKey] = factor; diff --git a/modules/sovrnAnalyticsAdapter.js b/modules/sovrnAnalyticsAdapter.js index aee7ddd2690..065cfaa58bc 100644 --- a/modules/sovrnAnalyticsAdapter.js +++ b/modules/sovrnAnalyticsAdapter.js @@ -1,11 +1,10 @@ -import { logError, timestamp } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js' -import adaptermanager from '../src/adapterManager.js' -import CONSTANTS from '../src/constants.json' -import {ajaxBuilder} from '../src/ajax.js' -import {config} from '../src/config.js' -import find from 'core-js-pure/features/array/find.js' -import includes from 'core-js-pure/features/array/includes.js' +import {logError, timestamp} from '../src/utils.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adaptermanager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import {ajaxBuilder} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {find, includes} from '../src/polyfill.js'; const ajax = ajaxBuilder(0) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 38788036ce0..4ca8f03c6b4 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -1,12 +1,38 @@ -import { _each, getBidIdParameter, isArray, deepClone, parseUrl, getUniqueIdentifierStr, deepSetValue, logError, deepAccess } from '../src/utils.js'; +import { _each, getBidIdParameter, isArray, deepClone, parseUrl, getUniqueIdentifierStr, deepSetValue, logError, deepAccess, isInteger, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' -import { BANNER } from '../src/mediaTypes.js' -import { createEidsArray } from './userId/eids.js'; -import {config} from '../src/config.js'; +import { ADPOD, BANNER, VIDEO } from '../src/mediaTypes.js' +import { createEidsArray } from './userId/eids.js' +import {config} from '../src/config.js' + +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 => v >= 1 && v <= 10), + 'w': (value) => isInteger(value), + 'h': (value) => isInteger(value), + 'startdelay': (value) => isInteger(value), + 'placement': (value) => isInteger(value) && value >= 1 && value <= 5, + 'linearity': (value) => [1, 2].indexOf(value) !== -1, + 'skip': (value) => [0, 1].indexOf(value) !== -1, + 'skipmin': (value) => isInteger(value), + 'skipafter': (value) => isInteger(value), + 'sequence': (value) => isInteger(value), + 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17), + '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 => v >= 1 && v <= 6), + 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, + 'delivery': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 3), + 'pos': (value) => isInteger(value) && value >= 1 && value <= 7, + 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) +} export const spec = { code: 'sovrn', - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], gvlid: 13, /** @@ -15,7 +41,12 @@ export const spec = { * @return boolean for whether or not a bid is valid */ isBidRequestValid: function(bid) { - return !!(bid.params.tagid && !isNaN(parseFloat(bid.params.tagid)) && isFinite(bid.params.tagid)) + return !!( + bid.params.tagid && + !isNaN(parseFloat(bid.params.tagid)) && + isFinite(bid.params.tagid) && + deepAccess(bid, 'mediaTypes.video.context') !== ADPOD + ) }, /** @@ -50,13 +81,9 @@ export const spec = { } iv = iv || getBidIdParameter('iv', bid.params) - let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && 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 floorInfo = (bid.getFloor && typeof bid.getFloor === 'function') ? bid.getFloor({ currency: 'USD', - mediaType: 'banner', + mediaType: bid.mediaTypes && bid.mediaTypes.banner ? 'banner' : 'video', size: '*' }) : {} floorInfo.floor = floorInfo.floor || getBidIdParameter('bidfloor', bid.params) @@ -64,13 +91,24 @@ export const spec = { const imp = { adunitcode: bid.adUnitCode, id: bid.bidId, - banner: { + tagid: String(getBidIdParameter('tagid', bid.params)), + bidfloor: floorInfo.floor + } + + if (deepAccess(bid, 'mediaTypes.banner')) { + let bidSizes = deepAccess(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)})) + + imp.banner = { format: processedSizes, w: 1, h: 1, - }, - tagid: String(getBidIdParameter('tagid', bid.params)), - bidfloor: floorInfo.floor + }; + } + if (deepAccess(bid, 'mediaTypes.video')) { + imp.video = _buildVideoRequestObj(bid); } imp.ext = getBidIdParameter('ext', bid.ortb2Imp) || undefined @@ -149,7 +187,7 @@ export const spec = { seatbid[0].bid && seatbid[0].bid.length > 0) { seatbid[0].bid.map(sovrnBid => { - sovrnBidResponses.push({ + const bid = { requestId: sovrnBid.impid, cpm: parseFloat(sovrnBid.price), width: parseInt(sovrnBid.w), @@ -158,11 +196,18 @@ export const spec = { dealId: sovrnBid.dealid || null, currency: 'USD', netRevenue: true, - mediaType: BANNER, - ad: decodeURIComponent(`${sovrnBid.adm}`), ttl: sovrnBid.ext ? (sovrnBid.ext.ttl || 90) : 90, meta: { advertiserDomains: sovrnBid && sovrnBid.adomain ? sovrnBid.adomain : [] } - }); + } + + if (!sovrnBid.nurl) { + bid.mediaType = VIDEO + bid.vastXml = decodeURIComponent(sovrnBid.adm) + } else { + bid.mediaType = BANNER + bid.ad = decodeURIComponent(`${sovrnBid.adm}`) + } + sovrnBidResponses.push(bid); }); } return sovrnBidResponses @@ -209,4 +254,34 @@ export const spec = { }, } -registerBidder(spec); +function _buildVideoRequestObj(bid) { + const videoObj = {} + const videoAdUnitParams = deepAccess(bid, 'mediaTypes.video', {}) + const videoBidderParams = deepAccess(bid, 'params.video', {}) + const computedParams = {} + + if (Array.isArray(videoAdUnitParams.playerSize)) { + const sizes = (Array.isArray(videoAdUnitParams.playerSize[0])) ? videoAdUnitParams.playerSize[0] : videoAdUnitParams.playerSize + computedParams.w = sizes[0] + computedParams.h = sizes[1] + } + + const videoParams = { + ...computedParams, + ...videoAdUnitParams, + ...videoBidderParams + }; + + Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => { + if (videoParams.hasOwnProperty(paramName)) { + if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) { + videoObj[paramName] = videoParams[paramName] + } else { + logWarn(`The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`); + } + } + }) + return videoObj +} + +registerBidder(spec) diff --git a/modules/sovrnBidAdapter.md b/modules/sovrnBidAdapter.md index 2b5d21d5515..53e3158024d 100644 --- a/modules/sovrnBidAdapter.md +++ b/modules/sovrnBidAdapter.md @@ -10,9 +10,9 @@ Maintainer: jrosendahl@sovrn.com Sovrn's adapter integration to the Prebid library. Posts plain-text JSON to the /rtb/bid endpoint. -# Test Parameters +# Banner Test Parameters -``` +```js var adUnits = [ { code: 'test-leaderboard', @@ -45,3 +45,81 @@ var adUnits = [ } ] ``` + +# Video Test Parameters +### Instream +```js +var videoAdUnit = { + code: 'video1', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + }, + }, + bids: [ + { + bidder: 'sovrn', + // Prebid Server Bidder Params https://docs.prebid.org/dev-docs/pbs-bidders.html#sovrn + params: { + tagid: '315045', + bidfloor: '0.04', + }, + }, + ], +} +``` +### Outstream +```js +var adUnits = [ + { + code: videoId, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 360], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + }, + }, + bids: [ + { + bidder: 'sovrn', + // Prebid Server Bidder Params https://docs.prebid.org/dev-docs/pbs-bidders.html#sovrn + params: { + tagid: '315045', + bidfloor: '0.04', + }, + }, + ], + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + adResponse = { + ad: { + video: { + content: bid.vastXml, + player_height: bid.height, + player_width: bid.width, + }, + }, + } + // push to render queue because ANOutstreamVideo may not be loaded yet. + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: adResponse, + }) + }) + }, + }, + }, +] +``` diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 7d5865684a7..2fd403058d1 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -11,8 +11,7 @@ export const GOOGLE_CONSENT = { consented_providers: ['3', '7', '11', '12', '15' export const spec = { code: BIDDER_CODE, - gvlid: 165, - aliases: ['spotx'], + gvlid: 52, supportedMediaTypes: [VIDEO], /** diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index b4caf1db1d0..e46073a63ba 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -1,19 +1,23 @@ -import { isArray, deepAccess, logWarn, parseUrl } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import strIncludes from 'core-js-pure/features/string/includes.js'; +import {deepAccess, isArray, logWarn, parseUrl} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {includes as strIncludes} from '../src/polyfill.js'; const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; +const TRACKER_URL = 'https://bdr.wpcdn.pl/tag/jstracker.js'; +const GVLID = 676; const TMAX = 450; -const BIDDER_VERSION = '5.2'; +const BIDDER_VERSION = '5.41'; const W = window; const { navigator } = W; const oneCodeDetection = {}; +const adUnitsCalled = {}; const adSizesCalled = {}; +const pageView = {}; var consentApiVersion; /** @@ -27,8 +31,8 @@ const getNotificationPayload = bidData => { const result = { requestId: undefined, siteId: [], - adUnit: [], slotId: [], + tagid: [], } bids.forEach(bid => { let params = isArray(bid.params) ? bid.params[0] : bid.params; @@ -53,7 +57,7 @@ const getNotificationPayload = bidData => { result.adomain = meta.advertiserDomains && meta.advertiserDomains[0]; result.networkName = meta.networkName; } - result.adUnit.push(bid.adUnitCode) + result.tagid.push(bid.adUnitCode); result.requestId = bid.auctionId || result.requestId; result.timeout = bid.timeout || result.timeout; }) @@ -70,6 +74,7 @@ const cookieSupport = () => { }; const applyClientHints = ortbRequest => { + const { location } = document; const { connection = {}, deviceMemory, userAgentData = {} } = navigator; const viewport = W.visualViewport || false; const segments = []; @@ -85,6 +90,16 @@ const applyClientHints = ortbRequest => { 'CH-isMobile': userAgentData.mobile, }; + /** + Check / generate page view id + Should be generated dureing first call to applyClientHints(), + and re-generated if pathname has changed + */ + if (!pageView.id || location.pathname !== pageView.path) { + pageView.path = location.pathname; + pageView.id = Math.floor(1E20 * Math.random()); + } + Object.keys(hints).forEach(key => { const hint = hints[key]; @@ -100,9 +115,26 @@ const applyClientHints = ortbRequest => { id: '12', name: 'NetInfo', segment: segments, + }, { + id: '7', + name: 'pvid', + segment: [ + { + value: `${pageView.id}` + } + ] }]; - ortbRequest.user = Object.assign(ortbRequest.user, { data }); + const ch = { data }; + ortbRequest.user = { ...ortbRequest.user, ...ch }; +}; + +const applyUserIds = (validBidRequest, ortbRequest) => { + const eids = validBidRequest.userIdAsEids + if (eids && eids.length) { + const ids = { eids }; + ortbRequest.user = { ...ortbRequest.user, ...ids }; + } }; /** @@ -114,8 +146,8 @@ const applyGdpr = (bidderRequest, ortbRequest) => { if (gdprConsent) { const { apiVersion, gdprApplies, consentString } = gdprConsent; consentApiVersion = apiVersion; - ortbRequest.regs = Object.assign(ortbRequest.regs, { '[ortb_extensions.gdpr]': gdprApplies ? 1 : 0 }); - ortbRequest.user = Object.assign(ortbRequest.user, { '[ortb_extensions.consent]': consentString }); + ortbRequest.regs = Object.assign(ortbRequest.regs, { 'gdpr': gdprApplies ? 1 : 0 }); + ortbRequest.user = Object.assign(ortbRequest.user, { 'consent': consentString }); } } @@ -129,6 +161,7 @@ const setOnAny = (collection, key) => collection.reduce((prev, next) => prev || */ const sendNotification = payload => { ajax(NOTIFY_URL, null, JSON.stringify(payload), { + contentType: 'application/json', withCredentials: false, method: 'POST', crossOrigin: true @@ -267,11 +300,17 @@ const mapImpression = slot => { send this info as ext.pbsize */ const slotSize = slot.sizes.length ? slot.sizes.reduce((prev, next) => prev[0] * prev[1] <= next[0] * next[1] ? next : prev).join('x') : '1x1'; - adSizesCalled[slotSize] = adSizesCalled[slotSize] ? adSizesCalled[slotSize] += 1 : 1; - ext.data = Object.assign({ pbsize: `${slotSize}_${adSizesCalled[slotSize]}` }, ext.data); + + if (!adUnitsCalled[adUnitCode]) { + // this is a new adunit - assign & save pbsize + adSizesCalled[slotSize] = adSizesCalled[slotSize] ? adSizesCalled[slotSize] += 1 : 1; + adUnitsCalled[adUnitCode] = `${slotSize}_${adSizesCalled[slotSize]}` + } + + ext.data = Object.assign({ pbsize: adUnitsCalled[adUnitCode] }, ext.data); const imp = { - id: id && siteId ? id : 'bidid-' + bidId, + id: id && siteId ? id.padStart(3, '0') : 'bidid-' + bidId, banner: mapBanner(slot), native: mapNative(slot), video: mapVideo(slot), @@ -314,7 +353,7 @@ const isVideoAd = bid => { const isNativeAd = bid => { const xmlTester = new RegExp(/^{['"]native['"]/); - return bid.adm && bid.adm.match(xmlTester); + return bid.admNative || (bid.adm && bid.adm.match(xmlTester)); } const parseNative = nativeData => { @@ -325,6 +364,9 @@ const parseNative = nativeData => { case 0: result.title = asset.title.text; break; + case 1: + result.cta = asset.data.value; + break; case 2: result.icon = { url: asset.img.url, @@ -419,12 +461,10 @@ const renderCreative = (site, auctionId, bid, seat, request) => { window.gdpr = ${JSON.stringify(request.gdprConsent)}; window.page = "${site.page}"; window.ref = "${site.ref}"; + window.adlabel = "${site.adLabel ? site.adLabel : ''}"; + window.pubid = "${site.publisherId ? site.publisherId : ''}"; `; - if (gam) { - adcode += `window.gam = ${JSON.stringify(gam)};`; - } - adcode += ` @@ -438,6 +478,7 @@ const renderCreative = (site, auctionId, bid, seat, request) => { const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: [], supportedMediaTypes: [BANNER, NATIVE, VIDEO], isBidRequestValid(bid) { @@ -482,6 +523,7 @@ const spec = { applyGdpr(bidderRequest, payload); applyClientHints(payload); + applyUserIds(validBidRequests[0], payload); return { method: 'POST', @@ -527,10 +569,14 @@ const spec = { /* bid response might include ext object containing siteId / slotId, as detected by OneCode update site / slot data in this case + + ext also might contain publisherId and custom ad label */ - const { siteid, slotid } = ext; + const { siteid, slotid, pubid, adlabel } = ext; site.id = siteid || site.id; site.slot = slotid || site.slot; + site.publisherId = pubid; + site.adLabel = adlabel; } if (bidRequest && site.id && !strIncludes(site.id, 'bidid')) { @@ -567,10 +613,28 @@ const spec = { bid.mediaType = 'native'; // check native object try { - const nativeData = JSON.parse(serverBid.adm).native; + const nativeData = serverBid.admNative || JSON.parse(serverBid.adm).native; bid.native = parseNative(nativeData); bid.width = 1; bid.height = 1; + + // append viewability tracker + const jsData = { + rid: bidRequest.auctionId, + crid: bid.creativeId, + adunit: bidRequest.adUnitCode, + url: bid.native.clickUrl, + vendor: seat, + site: site.id, + slot: site.slot, + cpm: bid.cpm.toPrecision(4), + } + const jsTracker = ' + + + `; +} + +registerBidder(spec); diff --git a/modules/targetVideoBidAdapter.md b/modules/targetVideoBidAdapter.md new file mode 100644 index 00000000000..557c9f94410 --- /dev/null +++ b/modules/targetVideoBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Target Video Bid Adapter +Module Type: Bidder Adapter +Maintainer: grajzer@gmail.com +``` + +# Description + +Connects to Appnexus exchange for bids. + +TargetVideo bid adapter supports Banner. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[640, 480], [300, 250]], + } + }, + bids: [{ + bidder: 'targetVideo', + params: { + placementId: 13232361 + } + }] + } +]; +``` diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index ea581905883..a8902c896f6 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -12,7 +12,7 @@ const gdprStatus = { CMP_NOT_FOUND_OR_ERROR: 22 }; const FP_TEADS_ID_COOKIE_NAME = '_tfpvi'; -export const storage = getStorageManager(GVL_ID, BIDDER_CODE); +export const storage = getStorageManager({gvlid: GVL_ID, bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, @@ -190,8 +190,7 @@ function buildRequestObject(bid) { const reqObj = {}; let placementId = getValue(bid.params, 'placementId'); let pageId = getValue(bid.params, 'pageId'); - const impressionData = deepAccess(bid, 'ortb2Imp.ext.data'); - const gpid = deepAccess(impressionData, 'pbadslot') || deepAccess(impressionData, 'adserver.adslot'); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid'); reqObj.sizes = getSizes(bid); reqObj.bidId = getBidIdParameter('bidId', bid); diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index 560bf762394..42913414cbc 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -1,8 +1,6 @@ import { logError, isEmpty, deepAccess, triggerPixel, logWarn, isArray } from '../src/utils.js'; -import {createBid as createBidFactory} from '../src/bidfactory.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {VIDEO} from '../src/mediaTypes.js'; -import {STATUS} from '../src/constants.json'; const BIDDER_CODE = 'telaria'; const DOMAIN = 'tremorhub.com'; @@ -11,7 +9,11 @@ const EVENTS_ENDPOINT = `events.${DOMAIN}/diag`; export const spec = { code: BIDDER_CODE, - aliases: ['tremor', 'tremorvideo'], + gvlid: 52, + aliases: [ + { code: 'tremor', gvlid: 52 }, + { code: 'tremorvideo', gvlid: 52 } + ], supportedMediaTypes: [VIDEO], /** * Determines if the request is valid @@ -86,7 +88,9 @@ export const spec = { logError(errorMessage); } else if (!isEmpty(bidResult.seatbid)) { bidResult.seatbid[0].bid.forEach(tag => { - bids.push(createBid(STATUS.GOOD, bidderRequest, tag, width, height, BIDDER_CODE)); + if (tag) { + bids.push(createBid(bidderRequest, tag, width, height)); + } }); } @@ -253,38 +257,31 @@ function generateUrl(bid, bidderRequest) { } /** - * Create and return a bid object based on status and tag - * @param status + * Create and return a bid response * @param reqBid * @param response * @param width * @param height - * @param bidderCode */ -function createBid(status, reqBid, response, width, height, bidderCode) { - let bid = createBidFactory(status, reqBid); - +function createBid(reqBid, response, width, height) { // TTL 5 mins by default, future support for extended imp wait time - if (response) { - Object.assign(bid, { - requestId: reqBid.bidId, - cpm: response.price, - creativeId: response.crid || '-1', - vastXml: response.adm, - vastUrl: reqBid.vastUrl, - mediaType: 'video', - width: width, - height: height, - bidderCode: bidderCode, - currency: 'USD', - netRevenue: true, - ttl: 300, - ad: response.adm - }); - } - - bid.meta = bid.meta || {}; - if (response && response.adomain && response.adomain.length > 0) { + const bid = { + requestId: reqBid.bidId, + cpm: response.price, + creativeId: response.crid || '-1', + vastXml: response.adm, + vastUrl: reqBid.vastUrl, + mediaType: 'video', + width: width, + height: height, + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: response.adm, + meta: {} + }; + + if (response.adomain && response.adomain.length > 0) { bid.meta.advertiserDomains = response.adomain; } diff --git a/modules/trionBidAdapter.js b/modules/trionBidAdapter.js index dd1624f90d7..5750406116b 100644 --- a/modules/trionBidAdapter.js +++ b/modules/trionBidAdapter.js @@ -2,12 +2,11 @@ import { getBidIdParameter, parseSizesInput, tryAppendQueryString } from '../src import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); - const BID_REQUEST_BASE_URL = 'https://in-appadvertising.com/api/bidRequest'; const USER_SYNC_URL = 'https://in-appadvertising.com/api/userSync.html'; const BIDDER_CODE = 'trion'; const BASE_KEY = '_trion_'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/trustpidSystem.js b/modules/trustpidSystem.js new file mode 100644 index 00000000000..051775fa777 --- /dev/null +++ b/modules/trustpidSystem.js @@ -0,0 +1,197 @@ +/** + * This module adds TrustPid provided by Vodafone Sales and Services Limited to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/trustpidSystem + * @requires module:modules/userId + */ +import { logInfo, logError } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'trustpid'; +const LOG_PREFIX = 'Trustpid module' +let mnoAcronym = ''; +let mnoDomain = ''; + +export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); + +/** + * Handle an event for an iframe. + * Takes the body.url parameter from event and returns the string domain. + * i.e.: "fc.vodafone.de" + * @param event + */ +function messageHandler(event) { + let msg; + try { + if (event && event.data && typeof event.data === 'string' && event.data) { + msg = JSON.parse(event.data); + if (msg.msgType === 'MNOSELECTOR' && msg.body && msg.body.url) { + let URL = msg.body.url.split('//'); + let domainURL = URL[1].split('/'); + mnoDomain = domainURL[0]; + logInfo(`${LOG_PREFIX}: Message handler set domain to ${mnoDomain}`); + getDomainAcronym(mnoDomain); + } + } + } catch (e) { + logError(e); + } +} + +/** + * Properly sets the trustpid acronym depending on the domain value. + * @param domain + */ +function getDomainAcronym(domain) { + let acronym = ''; + const prefix = '-'; + switch (domain) { + case 'tmi.mno.link': + acronym = 'ndye'; + break; + case 'tmi.vodafone.de': + acronym = 'pqnx'; + break; + case 'tmi.telekom.de': + acronym = 'avgw'; + break; + case 'tmi.tmid.es': + acronym = 'kjws'; + break; + case 'uat.mno.link': + acronym = 'xxxx'; + break; + case 'es.tmiservice.orange.com': + acronym = 'aplw'; + break; + default: + return 'none'; + } + return mnoAcronym = prefix + acronym; +} + +// Set a listener to handle the iframe response message. +window.addEventListener('message', messageHandler, false); + +/** + * Get the "umid" from html5 local storage to make it available to the UserId module. + * @param config + * @returns {{trustpid: (*|string), acr: (string)}} + */ +function getTrustpidFromStorage() { + // Get the domain either from localStorage or global + let domain = JSON.parse(storage.getDataFromLocalStorage('fcIdConnectDomain')) || mnoDomain; + logInfo(`${LOG_PREFIX}: Local storage domain: ${domain}`); + + if (!domain) { + logInfo(`${LOG_PREFIX}: Local storage domain not found, returning null`); + return { + trustpid: null, + acr: null, + }; + } + + // Get the acronym from global + let acronym = mnoAcronym; + // if acronym is empty, but "domain" is available, get the acronym from domain + if (!acronym) { + getDomainAcronym(domain); + acronym = mnoAcronym; + } + + logInfo(`${LOG_PREFIX}: Domain acronym found: ${acronym}`); + + // Domain is correct in both local storage and idGraph, but no acronym is existing for the domain + if (domain && !acronym) { + return { + trustpid: null, + acr: null + } + } + + let fcIdConnectObject; + let fcIdConnectData = JSON.parse(storage.getDataFromLocalStorage('fcIdConnectData')); + logInfo(`${LOG_PREFIX}: Local storage fcIdConnectData: ${JSON.stringify(fcIdConnectData)}`); + + if (fcIdConnectData && + fcIdConnectData.connectId && + Array.isArray(fcIdConnectData.connectId.idGraph) && + fcIdConnectData.connectId.idGraph.length > 0) { + fcIdConnectObject = fcIdConnectData.connectId.idGraph.find(item => { + return item.domain === domain; + }); + } + logInfo(`${LOG_PREFIX}: Local storage fcIdConnectObject for domain: ${JSON.stringify(fcIdConnectObject)}`); + + return { + trustpid: (fcIdConnectObject && fcIdConnectObject.umid) + ? fcIdConnectObject.umid + : null, + acr: acronym, + }; +} + +/** @type {Submodule} */ +export const trustpidSubmodule = { + /** + * Used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * Decodes the stored id value for passing to bid requests. + * @function + * @returns {{trustpid: string} | null} + */ + decode(bidId) { + logInfo(`${LOG_PREFIX}: Decoded ID value ${JSON.stringify(bidId)}`); + return bidId.trustpid ? bidId : null; + }, + /** + * Get the id from helper function and initiate a new user sync. + * @param config + * @returns {{callback: result}|{id: {trustpid: string}}} + */ + getId: function(config) { + const data = getTrustpidFromStorage(); + if (data.trustpid) { + logInfo(`${LOG_PREFIX}: Local storage ID value ${JSON.stringify(data)}`); + return {id: {trustpid: data.trustpid + data.acr}}; + } else { + if (!config) { + config = {}; + } + if (!config.params) { + config.params = {}; + } + if (typeof config.params.maxDelayTime === 'undefined' || config.params.maxDelayTime === null) { + config.params.maxDelayTime = 1000; + } + // Current delay and delay step in milliseconds + let currentDelay = 0; + const delayStep = 50; + const result = (callback) => { + const data = getTrustpidFromStorage(); + if (!data.trustpid) { + if (currentDelay > config.params.maxDelayTime) { + logInfo(`${LOG_PREFIX}: No trustpid value set after ${config.params.maxDelayTime} max allowed delay time`); + callback(null); + } else { + currentDelay += delayStep; + setTimeout(() => { + result(callback); + }, delayStep); + } + } else { + const dataToReturn = { trustpid: data.trustpid + data.acr }; + logInfo(`${LOG_PREFIX}: Returning ID value data of ${JSON.stringify(dataToReturn)}`); + callback(dataToReturn); + } + }; + return { callback: result }; + } + }, +}; + +submodule('userId', trustpidSubmodule); diff --git a/modules/trustpidSystem.md b/modules/trustpidSystem.md new file mode 100644 index 00000000000..c4309c9d807 --- /dev/null +++ b/modules/trustpidSystem.md @@ -0,0 +1,45 @@ +## trustpid User Id Submodule + +trustpid User Id Module. + +First, make sure to add the trustpid submodule to your Prebid.js package with: + +``` +gulp build --modules=userId,adfBidAdapter,trustpidSystem +``` + +The following configuration parameters are available: + +``` +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: 'trustpid', + params: { + maxDelayTime: 1000, + }, + bidders: ["adf"], + storage: { + type: "html5", + name: "trustpid", + expires: 1, //days + }, + } + ], + } +}); +``` + +## Parameter Descriptions + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of the module | `"trustpid"` +| params | Required | Object | Object with configuration parameters for trustpid User Id submodule | - | +| params.maxDelayTime | Required | Integer | Max amount of time (in seconds) before looking into storage for data | 2500 | +| bidders | Required | Array of Strings | An array of bidder codes to which this user ID may be sent. Currently required and supporting AdformOpenRTB | `["adf"]` | +| storage | Required | Object | Local storage configuration object | - | +| storage.type | Required | String | Type of the storage that would be used to store user ID. Must be `"html5"` to utilise HTML5 local storage. | `"html5"` | +| storage.name | Required | String | The name of the key in local storage where the user ID will be stored. | `"trustpid"` | +| storage.expires | Required | Integer | How long (in days) the user ID information will be stored. For safety reasons, this information is required.| `1` | \ No newline at end of file diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index 9907d1b2ff4..7d40a0b0452 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -1,4 +1,4 @@ -import { isEmpty, deepAccess, logError, logWarn, parseGPTSingleSizeArrayToRtbSize } from '../src/utils.js'; +import {isEmpty, deepAccess, logError, logWarn, parseGPTSingleSizeArrayToRtbSize, mergeDeep} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; @@ -6,9 +6,8 @@ import { config } from '../src/config.js'; const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; -const ADDITIONAL_SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; -const ADAPTER_SYNC_URL = 'https://sofia.trustx.org/push_sync'; +const ADAPTER_SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; const LOG_ERROR_MESS = { @@ -22,6 +21,7 @@ const LOG_ERROR_MESS = { hasEmptySeatbidArray: 'Response has empty seatbid array', hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' }; + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ BANNER, VIDEO ], @@ -76,7 +76,7 @@ export const spec = { if (!userIdAsEids) { userIdAsEids = bid.userIdAsEids; } - const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp} = bid; bidsMap[bidId] = bid; const bidFloor = _getFloor(mediaTypes || {}, bid); if (rtd) { @@ -102,6 +102,20 @@ export const spec = { } }; + if (ortb2Imp) { + if (ortb2Imp.instl) { + impObj.instl = ortb2Imp.instl; + } + if (ortb2Imp.ext && ortb2Imp.ext.data) { + impObj.ext.data = ortb2Imp.ext.data; + if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { + impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); + } else { + impObj.ext.gpid = ortb2Imp.ext.data.pbadslot && ortb2Imp.ext.data.pbadslot.toString(); + } + } + } + if (!isEmpty(keywords)) { if (!pageKeywords) { pageKeywords = keywords; @@ -160,16 +174,25 @@ export const spec = { request.site.content = content; } - const userData = []; - addSegments('iow_labs_pub_data', 'jwpseg', jwpseg, userData); - addSegments('permutive', 'p_standard', permutiveseg, userData, 'permutive.com'); - - if (userData.length) { + if (jwpseg && jwpseg.length) { user = { - data: userData + data: [{ + name: 'iow_labs_pub_data', + segment: segmentProcessing(jwpseg, 'jwpseg'), + }] }; } + const ortb2UserData = config.getConfig('ortb2.user.data'); + if (ortb2UserData && ortb2UserData.length) { + if (!user) { + user = { data: [] }; + } + user = mergeDeep(user, { + data: [...ortb2UserData] + }); + } + if (gdprConsent && gdprConsent.consentString) { userExt = {consent: gdprConsent.consentString}; } @@ -272,12 +295,12 @@ export const spec = { }, getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.pixelEnabled) { - const syncsPerBidder = config.getConfig('userSync.syncsPerBidder'); let params = []; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - params.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); - } else { + params.push(`gdpr=${Number(gdprConsent.gdprApplies)}`); + } + if (typeof gdprConsent.consentString === 'string') { params.push(`gdpr_consent=${gdprConsent.consentString}`); } } @@ -285,17 +308,10 @@ export const spec = { params.push(`us_privacy=${uspConsent}`); } const stringParams = params.join('&'); - const syncs = [{ + return { type: 'image', - url: ADAPTER_SYNC_URL + (stringParams ? `?${stringParams}` : '') - }]; - if (syncsPerBidder > 1) { - syncs.push({ - type: 'image', - url: ADDITIONAL_SYNC_URL + (stringParams ? `&${stringParams}` : '') - }); - } - return syncs; + url: ADAPTER_SYNC_URL + stringParams + }; } } } @@ -425,34 +441,20 @@ function createBannerRequest(bid, mediaType) { return result; } -function addSegments(name, segName, segments, data, bidConfigName) { - if (segments && segments.length) { - data.push({ - name: name, - segment: segments - .map((seg) => seg && (seg.id || seg)) - .filter((seg) => seg && (typeof seg === 'string' || typeof seg === 'number')) - .map((seg) => ({ name: segName, value: seg.toString() })) - }); - } else if (bidConfigName) { - const configData = config.getConfig('ortb2.user.data'); - let segData = null; - configData && configData.some(({name, segment}) => { - if (name === bidConfigName) { - segData = segment; - return true; +function segmentProcessing(segment, forceSegName) { + return segment + .map((seg) => { + const value = seg && (seg.value || seg.id || seg); + if (typeof value === 'string' || typeof value === 'number') { + return { + value: value.toString(), + ...(forceSegName && { name: forceSegName }), + ...(seg.name && { name: seg.name }), + }; } - }); - if (segData && segData.length) { - data.push({ - name: name, - segment: segData - .map((seg) => seg && (seg.id || seg)) - .filter((seg) => seg && (typeof seg === 'string' || typeof seg === 'number')) - .map((seg) => ({ name: segName, value: seg.toString() })) - }); - } - } + return null; + }) + .filter((seg) => !!seg); } function reformatKeywords(pageKeywords) { diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js new file mode 100644 index 00000000000..4919442336f --- /dev/null +++ b/modules/ttdBidAdapter.js @@ -0,0 +1,519 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { createEidsArray } from './userId/eids.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDADAPTERVERSION = 'TTD-PREBID-2022.02.18'; +const BIDDER_CODE = 'ttd'; +const BIDDER_CODE_LONG = 'thetradedesk'; +const BIDDER_ENDPOINT = 'https://direct.adsrvr.org/bid/bidder/'; +const USER_SYNC_ENDPOINT = 'https://match.adsrvr.org'; + +const MEDIA_TYPE = { + BANNER: 1, + VIDEO: 2 +}; + +function getExt(firstPartyData) { + const ext = { + ver: BIDADAPTERVERSION, + pbjs: '$prebid.version$', + keywords: firstPartyData.site?.keywords ? firstPartyData.site.keywords.split(',').map(k => k.trim()) : [] + } + return { + ttdprebid: ext + }; +} + +function getRegs(bidderRequest) { + let regs = {}; + + if (bidderRequest.gdprConsent && typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + utils.deepSetValue(regs, 'ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(regs, 'ext.us_privacy', bidderRequest.uspConsent); + } + if (config.getConfig('coppa') === true) { + regs.coppa = 1; + } + return regs; +} + +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + +function getSource(validBidRequests) { + let source = {}; + if (validBidRequests[0].schain) { + utils.deepSetValue(source, 'ext.schain', validBidRequests[0].schain); + } + return source; +} + +function getDevice() { + const language = navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage; + let device = { + ua: navigator.userAgent, + dnt: utils.getDNT() ? 1 : 0, + language: language, + connectiontype: getConnectionType() + }; + + return device; +}; + +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': + case '2g': + return 4; + case '3g': + return 5; + case '4g': + return 6; + default: + return 3; + } + default: + return 0; + } +} + +function getUser(bidderRequest) { + let user = {}; + if (bidderRequest.gdprConsent) { + utils.deepSetValue(user, 'ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (utils.isStr(utils.deepAccess(bidderRequest, 'bids.0.userId.tdid'))) { + user.buyeruid = bidderRequest.bids[0].userId.tdid; + } + + var eids = createEidsArray(utils.deepAccess(bidderRequest, 'bids.0.userId')) + if (eids.length) { + utils.deepSetValue(user, 'ext.eids', eids); + } + + return user; +} + +function getSite(bidderRequest, firstPartyData) { + var site = { + id: utils.deepAccess(bidderRequest, 'bids.0.params.siteId'), + page: utils.deepAccess(bidderRequest, 'refererInfo.referer'), + publisher: { + id: utils.deepAccess(bidderRequest, 'bids.0.params.publisherId'), + }, + ...firstPartyData.site + }; + + var publisherDomain = config.getConfig('publisherDomain'); + if (publisherDomain) { + utils.deepSetValue(site, 'publisher.domain', publisherDomain); + } + return site; +} + +function getImpression(bidRequest) { + let impression = { + id: bidRequest.bidId, + tagid: bidRequest.params.placementId + }; + + let gpid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + if (gpid) { + impression.ext = { + gpid: gpid + } + } + + const mediaTypesVideo = utils.deepAccess(bidRequest, 'mediaTypes.video'); + const mediaTypesBanner = utils.deepAccess(bidRequest, 'mediaTypes.banner'); + + let mediaTypes = {}; + if (mediaTypesBanner) { + mediaTypes[BANNER] = banner(bidRequest); + } + if (mediaTypesVideo) { + mediaTypes[VIDEO] = video(bidRequest); + } + + Object.assign(impression, mediaTypes); + + let bidfloor = getBidFloor(bidRequest); + if (bidfloor) { + impression.bidfloor = parseFloat(bidfloor); + impression.bidfloorcur = 'USD'; + } + + return impression; +} + +function getSizes(sizes) { + const sizeStructs = utils.parseSizesInput(sizes) + .filter(x => x) // sizes that don't conform are returned as null, which we want to ignore + .map(x => x.split('x')) + .map(size => { + return { + width: parseInt(size[0]), + height: parseInt(size[1]), + } + }); + + return sizeStructs; +} + +function banner(bid) { + const sizes = getSizes(bid.mediaTypes.banner.sizes).map(x => { + return { + w: x.width, + h: x.height, + } + }); + const pos = parseInt(utils.deepAccess(bid, 'mediaTypes.banner.pos')); + const expdir = utils.deepAccess(bid, 'params.banner.expdir'); + let optionalParams = {}; + if (pos) { + optionalParams.pos = pos; + } + if (expdir && Array.isArray(expdir)) { + optionalParams.expdir = expdir; + } + + const banner = Object.assign( + { + w: sizes[0].w, + h: sizes[0].h, + format: sizes, + }, + optionalParams); + return banner; +} + +function video(bid) { + let minduration = utils.deepAccess(bid, 'mediaTypes.video.minduration'); + const maxduration = utils.deepAccess(bid, 'mediaTypes.video.maxduration'); + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const api = utils.deepAccess(bid, 'mediaTypes.video.api'); + const mimes = utils.deepAccess(bid, 'mediaTypes.video.mimes'); + const placement = utils.deepAccess(bid, 'mediaTypes.video.placement'); + const protocols = utils.deepAccess(bid, 'mediaTypes.video.protocols'); + const playbackmethod = utils.deepAccess(bid, 'mediaTypes.video.playbackmethod'); + const pos = utils.deepAccess(bid, 'mediaTypes.video.pos'); + const startdelay = utils.deepAccess(bid, 'mediaTypes.video.startdelay'); + const skip = utils.deepAccess(bid, 'mediaTypes.video.skip'); + const skipmin = utils.deepAccess(bid, 'mediaTypes.video.skipmin'); + const skipafter = utils.deepAccess(bid, 'mediaTypes.video.skipafter'); + const minbitrate = utils.deepAccess(bid, 'mediaTypes.video.minbitrate'); + const maxbitrate = utils.deepAccess(bid, 'mediaTypes.video.maxbitrate'); + + if (!minduration || !utils.isInteger(minduration)) { + minduration = 0 + } + let video = { + minduration: minduration, + maxduration: maxduration, + api: api, + mimes: mimes, + placement: placement, + protocols: protocols + }; + + if (typeof playerSize !== 'undefined') { + if (utils.isArray(playerSize[0])) { + video.w = parseInt(playerSize[0][0]); + video.h = parseInt(playerSize[0][1]); + } else if (utils.isNumber(playerSize[0])) { + video.w = parseInt(playerSize[0]); + video.h = parseInt(playerSize[1]); + } + } + + if (playbackmethod) { + video.playbackmethod = playbackmethod; + } + if (pos) { + video.pos = pos; + } + if (startdelay && utils.isInteger(startdelay)) { + video.startdelay = startdelay; + } + if (skip && (skip === 0 || skip === 1)) { + video.skip = skip; + } + if (skipmin && utils.isInteger(skipmin)) { + video.skipmin = skipmin; + } + if (skipafter && utils.isInteger(skipafter)) { + video.skipafter = skipafter; + } + if (minbitrate && utils.isInteger(minbitrate)) { + video.minbitrate = minbitrate; + } + if (maxbitrate && utils.isInteger(maxbitrate)) { + video.maxbitrate = maxbitrate; + } + + return video; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: 21, + aliases: [BIDDER_CODE_LONG], + supportedMediaTypes: [BANNER, 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) { + const alphaRegex = /^[\w+]+$/; + + // required parameters + if (!bid || !bid.params) { + utils.logWarn(BIDDER_CODE + ': Missing bid parameters'); + return false; + } + if (!bid.params.supplySourceId) { + utils.logWarn(BIDDER_CODE + ': Missing required parameter params.supplySourceId'); + return false; + } + if (!alphaRegex.test(bid.params.supplySourceId)) { + utils.logWarn(BIDDER_CODE + ': supplySourceId must only contain alphabetic characters'); + return false; + } + if (!bid.params.publisherId) { + utils.logWarn(BIDDER_CODE + ': Missing required parameter params.publisherId'); + return false; + } + if (bid.params.publisherId.length > 32) { + utils.logWarn(BIDDER_CODE + ': params.publisherId must be 32 characters or less'); + return false; + } + if (!bid.params.siteId) { + utils.logWarn(BIDDER_CODE + ': Missing required parameter params.siteId'); + return false; + } + if (bid.params.siteId.length > 50) { + utils.logWarn(BIDDER_CODE + ': params.siteId must be 50 characters or less'); + return false; + } + if (!bid.params.placementId) { + utils.logWarn(BIDDER_CODE + ': Missing required parameter params.placementId'); + return false; + } + if (bid.params.placementId.length > 128) { + utils.logWarn(BIDDER_CODE + ': params.placementId must be 128 characters or less'); + return false; + } + + const mediaTypesBanner = utils.deepAccess(bid, 'mediaTypes.banner'); + const mediaTypesVideo = utils.deepAccess(bid, 'mediaTypes.video'); + + if (!mediaTypesBanner && !mediaTypesVideo) { + utils.logWarn(BIDDER_CODE + ': one of mediaTypes.banner or mediaTypes.video must be passed'); + return false; + } + + if (mediaTypesVideo) { + if (!mediaTypesVideo.maxduration || !utils.isInteger(mediaTypesVideo.maxduration)) { + utils.logWarn(BIDDER_CODE + ': mediaTypes.video.maxduration must be set to the maximum video ad duration in seconds'); + return false; + } + if (!mediaTypesVideo.api || mediaTypesVideo.api.length === 0) { + utils.logWarn(BIDDER_CODE + ': mediaTypes.video.api should be an array of supported api frameworks. See the Open RTB v2.5 spec for valid values'); + return false; + } + if (!mediaTypesVideo.mimes || mediaTypesVideo.mimes.length === 0) { + utils.logWarn(BIDDER_CODE + ': mediaTypes.video.mimes should be an array of supported mime types'); + return false; + } + if (!mediaTypesVideo.protocols) { + utils.logWarn(BIDDER_CODE + ': mediaTypes.video.protocols should be an array of supported protocols. See the Open RTB v2.5 spec for valid values') + return false; + } + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} an array of validBidRequests + * @param {*} bidderRequest + * @return {ServerRequest} Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const firstPartyData = config.getConfig('ortb2') || {}; + let topLevel = { + id: bidderRequest.auctionId, + imp: validBidRequests.map(bidRequest => getImpression(bidRequest)), + site: getSite(bidderRequest, firstPartyData), + device: getDevice(), + user: getUser(bidderRequest), + at: 1, + cur: ['USD'], + regs: getRegs(bidderRequest), + source: getSource(validBidRequests), + ext: getExt(firstPartyData) + } + + let url = BIDDER_ENDPOINT + bidderRequest.bids[0].params.supplySourceId; + + let serverRequest = { + method: 'POST', + url: url, + data: topLevel, + options: { + withCredentials: true + } + }; + + return serverRequest; + }, + + /** + * Format responses as Prebid bid responses + * + * Each bid can have the following elements: + * - requestId (required) + * - cpm (required) + * - width (required) + * - height (required) + * - ad (required) + * - ttl (required) + * - creativeId (required) + * - netRevenue (required) + * - currency (required) + * - vastUrl + * - vastImpUrl + * - vastXml + * - dealId + * + * @param {ttdResponseObj} bidResponse A successful response from ttd. + * @param {ServerRequest} serverRequest The result of buildRequests() that lead to this response. + * @return {Bid[]} An array of formatted bids. + */ + interpretResponse: function (response, serverRequest) { + let seatBidsInResponse = utils.deepAccess(response, 'body.seatbid'); + const currency = utils.deepAccess(response, 'body.cur'); + if (!seatBidsInResponse || seatBidsInResponse.length === 0) { + return []; + } + let bidResponses = []; + let requestedImpressions = utils.deepAccess(serverRequest, 'data.imp'); + + seatBidsInResponse.forEach(seatBid => { + seatBid.bid.forEach(bid => { + let matchingRequestedImpression = requestedImpressions.find(imp => imp.id === bid.impid); + + const cpm = bid.price || 0; + let bidResponse = { + requestId: bid.impid, + cpm: cpm, + creativeId: bid.crid, + dealId: bid.dealid || null, + currency: currency || 'USD', + netRevenue: true, + ttl: bid.ttl || 360, + meta: {}, + }; + + if (bid.adomain && bid.adomain.length > 0) { + bidResponse.meta.advertiserDomains = bid.adomain; + } + + if (bid.ext.mediatype === MEDIA_TYPE.BANNER) { + Object.assign( + bidResponse, + { + width: bid.w, + height: bid.h, + ad: utils.replaceAuctionPrice(bid.adm, cpm), + mediaType: BANNER + } + ); + } else if (bid.ext.mediatype === MEDIA_TYPE.VIDEO) { + Object.assign( + bidResponse, + { + width: matchingRequestedImpression.video.w, + height: matchingRequestedImpression.video.h, + mediaType: VIDEO + } + ); + if (bid.nurl) { + bidResponse.vastUrl = utils.replaceAuctionPrice(bid.nurl, cpm); + } else { + bidResponse.vastXml = utils.replaceAuctionPrice(bid.adm, cpm); + } + } + + bidResponses.push(bidResponse); + }); + }); + + return bidResponses; + }, + + /** + * 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. + * @param {gdprConsent} gdprConsent GDPR consent object + * @param {uspConsent} uspConsent USP consent object + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { + const syncs = []; + + let gdprParams = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(gdprConsent.consentString)}`; + + let url = `${USER_SYNC_ENDPOINT}/track/usersync?us_privacy=${encodeURIComponent(uspConsent)}${gdprParams}`; + + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: url + '&ust=image' + }); + } else if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: url + '&ust=iframe' + }); + } + return syncs; + }, +}; + +registerBidder(spec) diff --git a/modules/ttdBidAdapter.md b/modules/ttdBidAdapter.md new file mode 100644 index 00000000000..9c67f3267cb --- /dev/null +++ b/modules/ttdBidAdapter.md @@ -0,0 +1,122 @@ +# Overview + +``` +Module Name: The Trade Desk Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid-maintainers@thetradedesk.com +``` + +# Description + +Module that connects to The Trade Desk's demand sources to fetch bids. + +The Trade Desk bid adapter supports Banner and Video. + +# Test Parameters + +```js + var adUnits = [ + // Banner adUnit with only required parameters + { + code: 'test-div-minimal', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'ttd', + params: { + supplySourceId: 'supplier', + publisherId: '1427ab10f2e448057ed3b422', + siteId: 'site-123', + placementId: 'footer1' + } + } + ] + }, + // Banner adUnit with all optional parameters provided + { + code: 'test-div-banner-optional-params', + mediaTypes: { + banner: { + sizes: [[728, 90]], + pos: 1 + } + }, + bids: [ + { + bidder: 'ttd', + params: { + supplySourceId: 'supplier', + publisherId: '1427ab10f2e448057ed3b422', + siteId: 'site-123', + placementId: 'footer1', + banner: { + expdir: [1, 3] + }, + } + } + ] + }, + // Video adUnit with only required parameters + { + code: 'test-div-video-minimal', + mediaTypes: { + video: { + maxduration: 30, + api: [1, 3], + mimes: ['video/mp4'], + placement: 3, + protocols: [2,3,5,6] + } + }, + bids: [ + { + bidder: 'ttd', + params: { + supplySourceId: 'supplier', + publisherId: '1427ab10f2e448057ed3b422', + siteId: 'site-123', + placementId: 'footer1' + } + } + ] + }, + // Video adUnit with all optional parameters provided + { + code: 'test-div-video-full', + mediaTypes: { + video: { + minduration: 1, + maxduration: 10, + playerSize: [640, 480], + api: [1, 3], + mimes: ['video/mp4'], + placement: 3, + protocols: [2, 3, 5, 6], + startdelay: 1, + playbackmethod: [1], + pos: 1, + minbitrate: 100, + maxbitrate: 500, + skip: 1, + skipmin: 5, + skipafter: 10 + } + }, + bids: [ + { + bidder: 'ttd', + params: { + supplySourceId: 'supplier', + publisherId: '1427ab10f2e448057ed3b422', + siteId: 'site-123', + placementId: 'footer1' + } + } + ] + } + ]; +``` diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 8b85f1ebad3..ec087d005d6 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { config } from '../src/config.js'; -const storage = getStorageManager(); + const COOKIE_NAME = 'ucf_uid'; const VER = 'ADGENT_PREBID-2018011501'; const BIDDER_CODE = 'ucfunnel'; @@ -13,6 +13,7 @@ const VIDEO_CONTEXT = { INSTREAM: 0, OUSTREAM: 2 } +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index c0cd9166784..23656639532 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -23,7 +23,7 @@ function readFromLocalStorage() { } function getStorage() { - return getStorageManager(GVLID, MODULE_NAME); + return getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); } const storage = getStorage(); diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index d9c9f84e050..f86faf3fe4d 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -12,6 +12,20 @@ const FRAME_USER_SYNC = 'https://cdn.undertone.com/js/usersync.html'; const PIXEL_USER_SYNC_1 = 'https://usr.undertone.com/userPixel/syncOne?id=1&of=2'; const PIXEL_USER_SYNC_2 = 'https://usr.undertone.com/userPixel/syncOne?id=2&of=2'; +function getBidFloor(bidRequest, mediaType) { + if (typeof bidRequest.getFloor !== 'function') { + return 0; + } + + const floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + + return (floor && floor.currency === 'USD' && floor.floor) || 0; +} + function getCanonicalUrl() { try { let doc = window.top.document; @@ -98,9 +112,16 @@ export const spec = { 'commons': commons }; const referer = bidderRequest.refererInfo.referer; + const canonicalUrl = getCanonicalUrl(); + if (referer) { + commons.referrer = referer; + } + if (canonicalUrl) { + commons.canonicalUrl = canonicalUrl; + } const hostname = parseUrl(referer).hostname; let domain = extractDomainFromHost(hostname); - const pageUrl = getCanonicalUrl() || referer; + const pageUrl = canonicalUrl || referer; const pubid = validBidRequests[0].params.publisherId; let reqUrl = `${URL}?pid=${pubid}&domain=${domain}`; @@ -127,6 +148,9 @@ export const spec = { params: bidReq.params }; const videoMediaType = deepAccess(bidReq, 'mediaTypes.video'); + const mediaType = videoMediaType ? VIDEO : BANNER; + bid.mediaType = mediaType; + bid.bidfloor = getBidFloor(bidReq, mediaType); if (videoMediaType) { bid.video = { playerSize: deepAccess(bidReq, 'mediaTypes.video.playerSize') || null, @@ -135,7 +159,6 @@ export const spec = { maxDuration: deepAccess(bidReq, 'params.video.maxDuration') || null, skippable: deepAccess(bidReq, 'params.video.skippable') || null }; - bid.mediaType = 'video'; } payload['x-ut-hb-params'].push(bid); }); diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js index 0209c808979..977e694acf7 100644 --- a/modules/unicornBidAdapter.js +++ b/modules/unicornBidAdapter.js @@ -3,12 +3,12 @@ import {BANNER} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; -const storage = getStorageManager(); const BIDDER_CODE = 'unicorn'; const UNICORN_ENDPOINT = 'https://ds.uncn.jp/pb/0/bid.json'; const UNICORN_DEFAULT_CURRENCY = 'JPY'; const UNICORN_PB_COOKIE_KEY = '__pb_unicorn_aud'; const UNICORN_PB_VERSION = '1.1'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * Placement ID and Account ID are required. diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index 99fbe63aeb4..77160bb01e6 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -193,6 +193,7 @@ const isBannerMediaTypeValid = (mediaTypeBannerData) => { export const adapter = { code: 'unruly', supportedMediaTypes: [VIDEO, BANNER], + gvlid: 36, isBidRequestValid: function (bid) { let siteId = deepAccess(bid, 'params.siteId'); let isBidValid = siteId && isMediaTypesValid(bid); diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 87b6ecc1f1c..5d138795bd8 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -1,10 +1,19 @@ import { pick, isFn, isStr, isPlainObject, deepAccess } from '../../src/utils.js'; // Each user-id sub-module is expected to mention respective config here -const USER_IDS_CONFIG = { +export const USER_IDS_CONFIG = { // key-name : {config} + // trustpid + 'trustpid': { + source: 'trustpid.com', + atype: 1, + getValue: function (data) { + return data; + }, + }, + // intentIqId 'intentIqId': { source: 'intentiq.com', @@ -17,6 +26,12 @@ const USER_IDS_CONFIG = { atype: 1 }, + // justId + 'justId': { + source: 'justtag.com', + atype: 1 + }, + // pubCommonId 'pubcid': { source: 'pubcid.org', @@ -48,6 +63,20 @@ const USER_IDS_CONFIG = { } }, + // ftrack + 'ftrackId': { + source: 'flashtalking.com', + atype: 1, + getValue: function(data) { + return data.uid + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + // parrableId 'parrableId': { source: 'parrable.com', @@ -146,7 +175,13 @@ const USER_IDS_CONFIG = { atype: 1 }, - // haloId + // hadronId + 'hadronId': { + source: 'audigent.com', + atype: 1 + }, + + // haloId (deprecated in 7.0, use hadronId) 'haloId': { source: 'audigent.com', atype: 1 @@ -257,7 +292,19 @@ const USER_IDS_CONFIG = { 'connectId': { source: 'yahoo.com', atype: 3 - } + }, + + // Adquery ID + 'qid': { + source: 'adquery.io', + atype: 1 + }, + + // DAC ID + 'dacId': { + source: 'impact-ad.jp', + atype: 1 + }, }; // this function will create an eid object for the given UserId sub-module diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 679bf5ffe27..a61ef66c56c 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -2,6 +2,14 @@ ``` userIdAsEids = [ + { + source: 'trustpid.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { source: 'pubcid.org', uids: [{ @@ -28,7 +36,15 @@ userIdAsEids = [ atype: 1 }] }, - + + { + source: 'justtag.com', + uids: [{ + id: 'justId', + atype: 1 + }] + }, + { source: 'neustar.biz', uids: [{ @@ -49,6 +65,13 @@ userIdAsEids = [ }] }, + { + source: 'flashtalking.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }, + { source: 'parrable.com', uids: [{ @@ -203,7 +226,7 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 3 }] - }, + }, { source: 'kpuid.com', uids: [{ diff --git a/modules/userId/index.js b/modules/userId/index.js index b0293a9c26a..e656673befb 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -130,20 +130,32 @@ * @property {(string[]|undefined)} submoduleNames - submodules to refresh */ -import find from 'core-js-pure/features/array/find.js'; -import { config } from '../../src/config.js'; -import events from '../../src/events.js'; -import { getGlobal } from '../../src/prebidGlobal.js'; -import { gdprDataHandler } from '../../src/adapterManager.js'; +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 CONSTANTS from '../../src/constants.json'; -import { module, hook } from '../../src/hook.js'; -import { createEidsArray, buildEidPermissions } from './eids.js'; -import { getCoreStorageManager } from '../../src/storageManager.js'; +import {hook, module} from '../../src/hook.js'; +import {buildEidPermissions, createEidsArray, USER_IDS_CONFIG} from './eids.js'; +import {getCoreStorageManager} from '../../src/storageManager.js'; import { - getPrebidInternal, isPlainObject, logError, isArray, cyrb53Hash, deepAccess, timestamp, delayExecution, logInfo, isFn, - logWarn, isEmptyStr, isNumber + cyrb53Hash, + deepAccess, + delayExecution, + getPrebidInternal, + isArray, + isEmptyStr, + isFn, + isGptPubadsDefined, + isNumber, + isPlainObject, + logError, + logInfo, + logWarn, + timestamp, + isEmpty } from '../../src/utils.js'; -import includes from 'core-js-pure/features/array/includes.js'; const MODULE_NAME = 'User ID'; const COOKIE = 'cookie'; @@ -184,6 +196,9 @@ export let syncDelay; /** @type {(number|undefined)} */ export let auctionDelay; +/** @type {(string|undefined)} */ +let ppidSource; + /** @param {Submodule[]} submodules */ export function setSubmoduleRegistry(submodules) { submoduleRegistry = submodules; @@ -448,6 +463,20 @@ function getCombinedSubmoduleIds(submodules) { return combinedSubmoduleIds; } +/** + * This function will return a submodule ID object for particular source name + * @param {SubmoduleContainer[]} submodules + * @param {string} sourceName + */ +function getSubmoduleId(submodules, sourceName) { + if (!Array.isArray(submodules) || !submodules.length) { + return {}; + } + const submodule = submodules.filter(sub => isPlainObject(sub.idObj) && + Object.keys(sub.idObj).length && USER_IDS_CONFIG[Object.keys(sub.idObj)[0]].source === sourceName) + return !isEmpty(submodule) ? submodule[0].idObj : [] +} + /** * This function will create a combined object for bidder with allowed subModule Ids * @param {SubmoduleContainer[]} submodules @@ -557,6 +586,26 @@ export function requestBidsHook(fn, reqBidsConfigObj) { initializeSubmodulesAndExecuteCallbacks(function () { // pass available user id data to bid adapters addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules); + + // 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); + 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) { + if (isGptPubadsDefined()) { + window.googletag.pubads().setPublisherProvidedId(ppidValue); + } else { + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(function() { + window.googletag.pubads().setPublisherProvidedId(ppidValue); + }); + } + } else { + logWarn(`User ID - Googletag Publisher Provided ID for ${ppidSource} is not between 32 and 150 characters - ${ppidValue}`); + } + } + // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); }); @@ -582,6 +631,79 @@ function getUserIdsAsEids() { return createEidsArray(getCombinedSubmoduleIds(initializedSubmodules)); } +/** + * This function will be exposed in global-name-space so that userIds stored by Prebid UserId module can be used by external codes as well. + * Simple use case will be passing these UserIds to A9 wrapper solution + */ + +function getUserIdsAsEidBySource(sourceName) { + initializeSubmodulesAndExecuteCallbacks(); + return createEidsArray(getSubmoduleId(initializedSubmodules, sourceName))[0]; +}; + +/** + * This function will be exposed in global-name-space so that userIds for a source can be exposed + * Sample use case is exposing this function to ESP + */ +function getEncryptedEidsForSource(source, encrypt, customFunction) { + let eidsSignals = {}; + + if (isFn(customFunction)) { + logInfo(`${MODULE_NAME} - Getting encrypted signal from custom function : ${customFunction.name} & source : ${source} `); + // Publishers are expected to define a common function which will be proxy for signal function. + const customSignals = customFunction(source); + eidsSignals[source] = customSignals ? encryptSignals(customSignals) : null; // by default encrypt using base64 to avoid JSON errors + } else { + // initialize signal with eids by default + const eid = getUserIdsAsEidBySource(source); + logInfo(`${MODULE_NAME} - Getting encrypted signal for eids :${JSON.stringify(eid)}`); + if (!isEmpty(eid)) { + eidsSignals[eid.source] = encrypt === true ? encryptSignals(eid) : eid.uids[0].id; // If encryption is enabled append version (1||) and encrypt entire object + } + } + const promise = Promise.resolve(eidsSignals[source]); + logInfo(`${MODULE_NAME} - Fetching encrypted eids: ${eidsSignals[source]}`); + return promise; +} + +function encryptSignals(signals, version = 1) { + let encryptedSig = ''; + switch (version) { + case 1: // Base64 Encryption + encryptedSig = typeof signals === 'object' ? window.btoa(JSON.stringify(signals)) : window.btoa(signals); // Test encryption. To be replaced with better algo + break; + default: + break; + } + return `${version}||${encryptedSig}`; +} + +/** +* This function will be exposed in the global-name-space so that publisher can register the signals-ESP. +*/ +function registerSignalSources() { + if (!isGptPubadsDefined()) { + return; + } + window.googletag.encryptedSignalProviders = window.googletag.encryptedSignalProviders || []; + const encryptedSignalSources = config.getConfig('userSync.encryptedSignalSources'); + if (encryptedSignalSources) { + const registerDelay = encryptedSignalSources.registerDelay || 0; + setTimeout(() => { + encryptedSignalSources['sources'] && encryptedSignalSources['sources'].forEach(({ source, encrypt, customFunc }) => { + source.forEach((src) => { + window.googletag.encryptedSignalProviders.push({ + id: src, + collectorFunction: () => getEncryptedEidsForSource(src, encrypt, customFunc) + }); + }); + }) + }, registerDelay) + } else { + logWarn(`${MODULE_NAME} - ESP : encryptedSignalSources config not defined under userSync Object`); + } +} + /** * This function will be exposed in the global-name-space so that userIds can be refreshed after initialization. * @param {RefreshUserIdsOptions} options @@ -817,6 +939,7 @@ export function attachIdSystem(submodule) { * @param {{getConfig:function}} config */ export function init(config) { + ppidSource = undefined; submodules = []; configRegistry = []; addedUserIdHook = false; @@ -839,9 +962,10 @@ export function init(config) { } // listen for config userSyncs to be set - config.getConfig(conf => { + config.getConfig('userSync', conf => { // Note: support for 'usersync' was dropped as part of Prebid.js 4.0 const userSync = conf.userSync; + ppidSource = userSync.ppid; if (userSync && userSync.userIds) { configRegistry = userSync.userIds; syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; @@ -853,7 +977,10 @@ export function init(config) { // exposing getUserIds function in global-name-space so that userIds stored in Prebid can be used by external codes. (getGlobal()).getUserIds = getUserIds; (getGlobal()).getUserIdsAsEids = getUserIdsAsEids; + (getGlobal()).getEncryptedEidsForSource = getEncryptedEidsForSource; + (getGlobal()).registerSignalSources = registerSignalSources; (getGlobal()).refreshUserIds = refreshUserIds; + (getGlobal()).getUserIdsAsEidBySource = getUserIdsAsEidBySource; } // init config update listener to start the application diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 095685aba3d..ee49211e4cb 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -1,6 +1,7 @@ ## User ID Example Configuration Example showing `cookie` storage for user id data for each of the submodules + ``` pbjs.setConfig({ userSync: { @@ -44,6 +45,17 @@ pbjs.setConfig({ expires: 90, // Expiration in days refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' }, + }, { + name: "ftrackId", + storage: { + type: "html5", + name: "ftrackId", + expires: 90, + refreshInSeconds: 8*3600 + }, + params: { + url: 'https://d9.flashtalking.com/d9core', // required, if not populated ftrack will not run + } }, { name: 'parrableId', params: { @@ -139,6 +151,9 @@ pbjs.setConfig({ name: "knssoId", expires: 30 }, + { + name: "dacId" + } ], syncDelay: 5000, auctionDelay: 1000 @@ -147,10 +162,23 @@ pbjs.setConfig({ ``` Example showing `localStorage` for user id data for some submodules + ``` pbjs.setConfig({ userSync: { - userIds: [{ + userIds: [ + { + name: 'trustpid', + params: { + maxDelayTime: 2500 + }, + bidders: ['adform'], + storage: { + type: 'html5', + name: 'trustpid', + expires: 60 + } + }, { name: "unifiedId", params: { partner: "prebid", @@ -278,7 +306,7 @@ pbjs.setConfig({ name: "knssoId", expires: 30 }, - } + } }, { name: 'imuid', @@ -297,6 +325,14 @@ pbjs.setConfig({ type: 'html5', expires: 15 } + } + { + name: "qid", + storage: { + type: "html5", + name: "qid", + expires: 365 + } }], syncDelay: 5000 } @@ -304,6 +340,7 @@ pbjs.setConfig({ ``` Example showing how to configure a `value` object to pass directly to bid adapters + ``` pbjs.setConfig({ userSync: { diff --git a/modules/userIdTargeting.js b/modules/userIdTargeting.js index e15c9ddaca2..b7fd137779b 100644 --- a/modules/userIdTargeting.js +++ b/modules/userIdTargeting.js @@ -1,7 +1,7 @@ import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; import CONSTANTS from '../src/constants.json'; -import events from '../src/events.js'; +import * as events from '../src/events.js'; import { isStr, isPlainObject, isBoolean, isFn, hasOwn, logInfo } from '../src/utils.js'; const MODULE_NAME = 'userIdTargeting'; diff --git a/modules/ventes.md b/modules/ventes.md new file mode 100644 index 00000000000..5f0f571ecd8 --- /dev/null +++ b/modules/ventes.md @@ -0,0 +1,71 @@ +--- +layout: bidder +title: ventes +description: Prebid ventes Bidder Adapter +pbjs: false +biddercode: ventes +gdpr_supported: false +usp_supported: false +media_types: banner +coppa_supported: false +schain_supported: false +dchain_supported: false +prebid_member: false +--- + +### BidParams +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +|-----------------|----------|-----------------------------------------------------------|----------------------------------------------|---------------| +| `placementId` | required | Placement ID from Ventes Avenues | `'VA-062-0013-0183'` | `string` | +| `publisherId` | required | Publisher ID from Ventes Avenues | `'VA-062'` | `string` | +| `user` | optional | Object that specifies information about an external user. | `user: { age: 25, gender: 0, dnt: true}` | `object` | +| `app` | optional | Object containing mobile app parameters. | `app : { id: 'app-id'}` | `object` | + +#### User Object + +{: .table .table-bordered .table-striped } +| Name | Description | Example | Type | +|-------------------|-------------------------------------------------------------------------------------------|-----------------------|-----------------------| +| `age` | The age of the user. | `35` | `integer` | +| `externalUid` | Specifies a string that corresponds to an external user ID for this user. | `'1234567890abcdefg'` | `string` | +| `segments` | Specifies the segments to which the user belongs. | `[1, 2]` | `Array` | +| `gender` | Specifies the gender of the user. Allowed values: Unknown: `0`; Male: `1`; Female: `2` | `1` | `integer` | +| `dnt` | Do not track flag. Indicates if tracking cookies should be disabled for this auction | `true` | `boolean` | +| `language` | Two-letter ANSI code for this user's language. | `EN` | `string` | + + +### Ad Unit Setup for Banner +```javascript +var adUnits = [ +{ + code: 'test-hb-ad-11111-1', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'ventes', + params: { + placementId: 'VA-062-0013-0183', + publisherId: '5cebea3c9eea646c7b623d5e', + IABCategories: "['IAB1', 'IAB5']", + device:{ + ip: '123.145.167.189', + ifa:"AEBE52E7-03EE-455A-B3C4-E57283966239", + }, + app: { + id: "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", + name: "Yahoo Weather", + bundle: 'com.kiloo.subwaysurf', + storeurl: 'https://play.google.com/store/apps/details?id=com.kiloo.subwaysurf&hl=en', + domain: 'somoaudience.com', + } + } + }] + } +] +``` diff --git a/modules/ventesBidAdapter.js b/modules/ventesBidAdapter.js index 7a2b60d2ee2..42292ddaed3 100644 --- a/modules/ventesBidAdapter.js +++ b/modules/ventesBidAdapter.js @@ -1,17 +1,11 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {convertCamelToUnderscore, isStr, isArray, isNumber, isPlainObject, replaceAuctionPrice} from '../src/utils.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {convertCamelToUnderscore, isArray, isNumber, isPlainObject, isStr, replaceAuctionPrice} from '../src/utils.js'; +import {find} from '../src/polyfill.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; const BID_METHOD = 'POST'; const BIDDER_URL = 'http://13.234.201.146:8088/va/ad'; -const FIRST_PRICE = 1; -const NET_REVENUE = true; -const TTL = 10; -const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; -const DEVICE_PARAMS = ['ua', 'geo', 'dnt', 'lmt', 'ip', 'ipv6', 'devicetype']; -const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately + const DOMAIN_REGEX = new RegExp('//([^/]*)'); function groupBy(values, key) { @@ -26,7 +20,11 @@ function groupBy(values, key) { return Object .keys(groups) - .map(id => ({id, key, values: groups[id]})); + .map(id => ({ + id, + key, + values: groups[id] + })); } function validateMediaTypes(mediaTypes, allowedMediaTypes) { @@ -45,22 +43,22 @@ function isBanner(mediaTypes) { function validateBanner(banner) { return isPlainObject(banner) && - isArray(banner.sizes) && - (banner.sizes.length > 0) && - banner.sizes.every(validateMediaSizes); + isArray(banner.sizes) && + (banner.sizes.length > 0) && + banner.sizes.every(validateMediaSizes); } function validateMediaSizes(mediaSize) { return isArray(mediaSize) && - (mediaSize.length === 2) && - mediaSize.every(size => (isNumber(size) && size >= 0)); + (mediaSize.length === 2) && + mediaSize.every(size => (isNumber(size) && size >= 0)); } function hasUserInfo(bid) { return !!bid.params.user; } -function validateParameters(parameters, adUnit) { +function validateParameters(parameters) { if (!(parameters.placementId)) { return false; } @@ -101,8 +99,8 @@ function generateSiteFromAdUnitContext(bidRequests, adUnitContext) { function validateServerRequest(serverRequest) { return isPlainObject(serverRequest) && - isPlainObject(serverRequest.data) && - isArray(serverRequest.data.imp) + isPlainObject(serverRequest.data) && + isArray(serverRequest.data.imp) } function createServerRequestFromAdUnits(adUnits, bidRequestId, adUnitContext) { @@ -122,14 +120,15 @@ function generateBidRequestsFromAdUnits(bidRequests, bidRequestId, adUnitContext let userObj = {}; if (userObjBid) { Object.keys(userObjBid.params.user) - .filter(param => includes(USER_PARAMS, param)) .forEach((param) => { let uparam = convertCamelToUnderscore(param); if (param === 'segments' && isArray(userObjBid.params.user[param])) { let segs = []; userObjBid.params.user[param].forEach(val => { if (isNumber(val)) { - segs.push({'id': val}); + segs.push({ + 'id': val + }); } else if (isPlainObject(val)) { segs.push(val); } @@ -146,7 +145,6 @@ function generateBidRequestsFromAdUnits(bidRequests, bidRequestId, adUnitContext if (deviceObjBid && deviceObjBid.params && deviceObjBid.params.device) { deviceObj = {}; Object.keys(deviceObjBid.params.device) - .filter(param => includes(DEVICE_PARAMS, param)) .forEach(param => deviceObj[param] = deviceObjBid.params.device[param]); if (!deviceObjBid.hasOwnProperty('ua')) { deviceObj.ua = navigator.userAgent; @@ -159,37 +157,41 @@ function generateBidRequestsFromAdUnits(bidRequests, bidRequestId, adUnitContext deviceObj.ua = navigator.userAgent; deviceObj.language = navigator.language; } - const appDeviceObjBid = find(bidRequests, hasAppInfo); - let appIdObj; - if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { - Object.keys(appDeviceObjBid.params.app) - .filter(param => includes(APP_DEVICE_PARAMS, param)) - .forEach(param => appDeviceObjBid[param] = appDeviceObjBid.params.app[param]); - } const payload = {} payload.id = bidRequestId - payload.at = FIRST_PRICE + payload.at = 1 payload.cur = ['USD'] payload.imp = bidRequests.reduce(generateImpressionsFromAdUnit, []) - payload.site = generateSiteFromAdUnitContext(bidRequests, adUnitContext) - payload.device = deviceObj - if (appDeviceObjBid && payload.site != null) { + const appDeviceObjBid = find(bidRequests, hasAppInfo); + if (!appDeviceObjBid) { + payload.site = generateSiteFromAdUnitContext(bidRequests, adUnitContext) + } else { + let appIdObj; + if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = {}; + Object.keys(appDeviceObjBid.params.app) + .forEach(param => appIdObj[param] = appDeviceObjBid.params.app[param]); + } payload.app = appIdObj; } + payload.device = deviceObj; payload.user = userObj - // payload.regs = getRegulationFromAdUnitContext(adUnitContext) - // payload.ext = generateBidRequestExtension() - return payload } function generateImpressionsFromAdUnit(acc, adUnit) { - const {bidId, mediaTypes, params} = adUnit; - const {placementId} = params; + const { + bidId, + mediaTypes, + params + } = adUnit; + const { + placementId + } = params; const pmp = {}; - if (placementId) pmp.deals = [{id: placementId}] + if (placementId) pmp.deals = [{ id: placementId }] const imps = Object .keys(mediaTypes) @@ -204,21 +206,40 @@ function generateImpressionsFromAdUnit(acc, adUnit) { } function generateBannerFromAdUnit(impId, data, params) { - const {position, placementId} = params; + const { + position, + placementId + } = params; const pos = position || 0; const pmp = {}; - const ext = {placementId}; - - if (placementId) pmp.deals = [{id: placementId}] + const ext = { + placementId + }; - return data.sizes.map(([w, h]) => ({id: `${impId}`, banner: {format: [{w, h}], w, h, pos}, pmp, ext, tagid: placementId})); + if (placementId) pmp.deals = [{ id: placementId }] + + return data.sizes.map(([w, h]) => ({ + id: `${impId}`, + banner: { + format: [{ + w, + h + }], + w, + h, + pos + }, + pmp, + ext, + tagid: placementId + })); } function validateServerResponse(serverResponse) { return isPlainObject(serverResponse) && - isPlainObject(serverResponse.body) && - isStr(serverResponse.body.cur) && - isArray(serverResponse.body.seatbid); + isPlainObject(serverResponse.body) && + isStr(serverResponse.body.cur) && + isArray(serverResponse.body.seatbid); } function seatBidsToAds(seatBid, bidResponse, serverRequest) { @@ -241,10 +262,8 @@ function validateBids(bid) { return true; } -const VAST_REGEXP = /VAST\s+version/; - function getMediaType(adm) { - const videoRegex = new RegExp(VAST_REGEXP); + const videoRegex = new RegExp(/VAST\s+version/); if (videoRegex.test(adm)) { return VIDEO; @@ -273,14 +292,16 @@ function generateAdFromBid(bid, bidResponse) { requestId: bid.impid, cpm: bid.price, currency: bidResponse.cur, - ttl: TTL, + ttl: 10, creativeId: bid.crid, mediaType: mediaType, - netRevenue: NET_REVENUE + netRevenue: true }; if (bid.adomain) { - base.meta = { advertiserDomains: bid.adomain }; + base.meta = { + advertiserDomains: bid.adomain + }; } const size = getSizeFromBid(bid); @@ -292,17 +313,21 @@ function generateAdFromBid(bid, bidResponse) { width: size.width, ad: creative.markup, adUrl: creative.markupUrl, - // vastXml: isVideo && !isStr(creative.markupUrl) ? creative.markup : null, - // vastUrl: isVideo && isStr(creative.markupUrl) ? creative.markupUrl : null, renderer: creative.renderer }; } function getSizeFromBid(bid) { if (isNumber(bid.w) && isNumber(bid.h)) { - return { width: bid.w, height: bid.h }; + return { + width: bid.w, + height: bid.h + }; } - return { width: null, height: null }; + return { + width: null, + height: null + }; } function getCreativeFromBid(bid) { @@ -333,12 +358,12 @@ const venavenBidderSpec = { const allowedBidderCodes = [this.code]; return isPlainObject(adUnit) && - allowedBidderCodes.indexOf(adUnit.bidder) !== -1 && - isStr(adUnit.adUnitCode) && - isStr(adUnit.bidderRequestId) && - isStr(adUnit.bidId) && - validateMediaTypes(adUnit.mediaTypes, this.supportedMediaTypes) && - validateParameters(adUnit.params, adUnit); + allowedBidderCodes.indexOf(adUnit.bidder) !== -1 && + isStr(adUnit.adUnitCode) && + isStr(adUnit.bidderRequestId) && + isStr(adUnit.bidId) && + validateMediaTypes(adUnit.mediaTypes, this.supportedMediaTypes) && + validateParameters(adUnit.params); }, buildRequests(bidRequests, bidderRequest) { if (!bidRequests) return null; @@ -367,4 +392,6 @@ const venavenBidderSpec = { registerBidder(venavenBidderSpec); -export {venavenBidderSpec as spec}; +export { + venavenBidderSpec as spec +}; diff --git a/modules/ventesBidAdapter.md b/modules/ventesBidAdapter.md index 479f6dd2898..c79ef080cd1 100644 --- a/modules/ventesBidAdapter.md +++ b/modules/ventesBidAdapter.md @@ -55,7 +55,6 @@ var adUnits = [ publisherId: '5cebea3c9eea646c7b623d5e', IABCategories: "['IAB1', 'IAB5']", device:{ - ip: '123.145.167.189', ifa:"AEBE52E7-03EE-455A-B3C4-E57283966239", }, app: { diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index 280a6c47894..27577ad0de4 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -7,8 +7,8 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; -import { logError, formatQS } from '../src/utils.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {formatQS, logError} from '../src/utils.js'; +import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'verizonMediaId'; const VENDOR_ID = 25; diff --git a/modules/vibrantmediaBidAdapter.js b/modules/vibrantmediaBidAdapter.js new file mode 100644 index 00000000000..b6fe51c43bc --- /dev/null +++ b/modules/vibrantmediaBidAdapter.js @@ -0,0 +1,220 @@ +/* + * Vibrant Media Ltd. + * + * Prebid Adapter for sending bid requests to the prebid server and bid responses back to the client + * + * Note: Only BANNER and VIDEO are currently supported by the prebid server. + */ + +import {logError, logInfo} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {OUTSTREAM} from '../src/video.js'; + +const BIDDER_CODE = 'vibrantmedia'; +const VIBRANT_MEDIA_PREBID_URL = 'https://prebid.intellitxt.com/prebid'; +const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; + +/** + * Returns whether the given bid request contains at least one supported media request, which has valid data. (We can + * ignore invalid/unsupported ones, as they will be filtered out by the prebid server.) + * + * @param {*} bidRequest the bid requests sent by the Prebid API. + * + * @return {boolean} true if the given bid request contains at least one supported media request with valid details, + * otherwise false. + */ +const areValidSupportedMediaTypesPresent = function(bidRequest) { + const mediaTypes = Object.keys(bidRequest.mediaTypes); + + return mediaTypes.some(function(mediaType) { + if (mediaType === BANNER) { + return true; + } else if (mediaType === VIDEO) { + return (bidRequest.mediaTypes[VIDEO].context === OUTSTREAM); + } else if (mediaType === NATIVE) { + return !!bidRequest.mediaTypes[NATIVE].image; + } + + return false; + }); +}; + +/** + * Returns whether the given URL contains just a domain, and not (for example) a subdirectory or query parameters. + * @param {string} url the URL to check. + * @returns {boolean} whether the URL contains just a domain. + */ +const isBaseUrl = function(url) { + const urlMinusScheme = url.substring(url.indexOf('://') + 3); + const endOfDomain = urlMinusScheme.indexOf('/'); + return (endOfDomain === -1) || (endOfDomain === (urlMinusScheme.length - 1)); +}; + +/** + * Returns transformed bid requests that are in a format native to the prebid server. + * + * @param {*[]} bidRequests the bid requests sent by the Prebid API. + * + * @returns {*[]} the transformed bid requests. + */ +const transformBidRequests = function(bidRequests) { + const transformedBidRequests = []; + + bidRequests.forEach(function(bidRequest) { + const params = bidRequest.params || {}; + const transformedBidRequest = { + code: bidRequest.adUnitCode || bidRequest.code, + id: bidRequest.placementId || params.placementId || params.invCode, + requestId: bidRequest.bidId || bidRequest.transactionId, + bidder: bidRequest.bidder, + mediaTypes: bidRequest.mediaTypes, + bids: bidRequest.bids, + sizes: bidRequest.sizes + }; + + transformedBidRequests.push(transformedBidRequest); + }); + + return transformedBidRequests; +}; + +/** @type {BidderSpec} */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + /** + * Transforms the 'raw' bid params into ones that this adapter can use, prior to creating the bid request. + * + * @param {object} bidParams the params to transform. + * + * @returns {object} the bid params. + */ + transformBidParams: function(bidParams) { + return bidParams; + }, + + /** + * Determines whether or not the given bid request is valid. For all bid requests passed to the buildRequests + * function, each will have been passed to this function and this function will have returned true. + * + * @param {object} bid the bid params to validate. + * + * @return {boolean} true if this is a valid bid, otherwise false. + * @see SUPPORTED_MEDIA_TYPES + */ + isBidRequestValid: function(bid) { + const areBidRequestParamsValid = !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); + return areBidRequestParamsValid && areValidSupportedMediaTypesPresent(bid); + }, + + /** + * Return a prebid server request from the list of bid requests. + * + * @param {BidRequest[]} validBidRequests an array of bids validated via the isBidRequestValid function. + * @param {BidderRequest} bidderRequest an object with data common to all bid requests. + * + * @return ServerRequest Info describing the request to the prebid server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const transformedBidRequests = transformBidRequests(validBidRequests); + + var url = window.parent.location.href; + + if ((window.self === window.top) && (!url || (url.substr(0, 4) !== 'http') || isBaseUrl(url))) { + url = document.URL; + } + + url = encodeURIComponent(url); + + const prebidData = { + url, + gdpr: bidderRequest.gdprConsent, + usp: bidderRequest.uspConsent, + window: { + width: window.innerWidth, + height: window.innerHeight, + }, + biddata: transformedBidRequests, + }; + + return { + method: 'POST', + url: VIBRANT_MEDIA_PREBID_URL, + data: JSON.stringify(prebidData) + }; + }, + + /** + * Translate the Kormorant prebid server response into a list of bids. + * + * @param {ServerResponse} serverResponse a successful response from the prebid server. + * @param {BidRequest} bidRequest the original bid request associated with this response. + * + * @return {Bid[]} an array of bids returned by the prebid server, translated into the expected Prebid.js format. + */ + interpretResponse: function(serverResponse, bidRequest) { + const bids = serverResponse.body; + + bids.forEach(function(bid) { + bid.adResponse = serverResponse; + }); + + return bids; + }, + + /** + * Called if the Prebid API gives up waiting for a prebid server response. + * + * Example timeout data: + * + * [{ + * "bidder": "example", + * "bidId": "51ef8751f9aead", + * "params": { + * ... + * }, + * "adUnitCode": "div-gpt-ad-1460505748561-0", + * "timeout": 3000, + * "auctionId": "18fd8b8b0bd757" + * }] + * + * @param {{}} timeoutData data relating to the timeout. + */ + onTimeout: function(timeoutData) { + logError('Timed out waiting for bids: ' + JSON.stringify(timeoutData)); + }, + + /** + * Called when a bid returned by the prebid server is successful. + * + * Example bid won data: + * + * { + * "bidder": "example", + * "width": 300, + * "height": 250, + * "adId": "330a22bdea4cac", + * "mediaType": "banner", + * "cpm": 0.28 + * "ad": "...", + * "requestId": "418b37f85e772c", + * "adUnitCode": "div-gpt-ad-1460505748561-0", + * "size": "350x250", + * "adserverTargeting": { + * "hb_bidder": "example", + * "hb_adid": "330a22bdea4cac", + * "hb_pb": "0.20", + * "hb_size": "350x250" + * } + * } + * + * @param {*} bidData the data associated with the won bid. See example above for data format. + */ + onBidWon: function(bidData) { + logInfo('Bid won: ' + JSON.stringify(bidData)); + } +}; + +registerBidder(spec); diff --git a/modules/vibrantmediaBidAdapter.md b/modules/vibrantmediaBidAdapter.md new file mode 100644 index 00000000000..ce5db42fdb5 --- /dev/null +++ b/modules/vibrantmediaBidAdapter.md @@ -0,0 +1,92 @@ +## Overview + +**Module Name:** Vibrant Media Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** kormorant@vibrantmedia.com + +## Description + +Module that allows Vibrant Media to provide ad bids for banner, native and video (outstream only). + +## Test Parameters + +```javascript +var adUnits = [ + // Banner ad unit + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'vibrantmedia', + params: { + placementId: 12345 + } + }] + }, + + // Video (outstream) ad unit + { + code: 'test-video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + minduration: 1, // Minimum ad duration, in seconds + maxduration: 60, // Maximum ad duration, in seconds + skip: 0, // 1 - true, 0 - false + skipafter: 5, // Number of seconds before the video can be skipped + playbackmethod: [2], // Auto-play without sound + protocols: [1, 2, 3] // VAST 1.0, 2.0 and 3.0 + } + }, + bids: [ + { + bidder: 'vibrantmedia', + params: { + placementId: 67890, + video: { + skippable: true, + playback_method: 'auto_play_sound_off' + } + } + } + ] + }, + + // Native ad unit + { + code: 'test-native', + mediaTypes: { + native: { + image: { + required: true, + sizes: [300, 250] + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + } + }, + bids: [{ + bidder: 'vibrantmedia', + params: { + placementId: 13579, + allowSmallerSizes: true + } + }] + } +]; +``` diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 0e7b4ee63b2..cf252bda2dc 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -24,7 +24,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'pubcid': 1, 'tdid': 1, }; -const storage = getStorageManager(GVLID); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.cootlogix.com`; @@ -48,7 +48,7 @@ function isBidRequestValid(bid) { } function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode } = bid; + const { params, bidId, userId, adUnitCode, schain } = bid; const { bidFloor, ext } = params; const hashUrl = hashCode(topWindowUrl); const dealId = getNextDealId(hashUrl); @@ -71,7 +71,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { uniqueDealId: uniqueDealId, bidderVersion: BIDDER_VERSION, prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}` + res: `${screen.width}x${screen.height}`, + schain: schain }; appendUserIdsToRequestPayload(data, userId); diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index e69386f264a..d268f7a9d64 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -1,4 +1,4 @@ -import { logError, deepAccess } from '../src/utils.js'; +import { logError, deepAccess, parseSizesInput } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -9,7 +9,17 @@ const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; const BIDDER_CODE = 'vidoomy'; const GVLID = 380; -const COOKIE_SYNC_JSON = 'https://vpaid.vidoomy.com/sync/urls.json'; +const COOKIE_SYNC_FALLBACK_URLS = [ + 'https://x.bidswitch.net/sync?ssp=vidoomy', + 'https://ib.adnxs.com/getuid?https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', + 'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D', + 'https://sync.1rx.io/usersync2/vidoomy?redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DUN%26uid%3D%5BRX_UUID%5D', + 'https://rtb.openx.net/sync/prebid?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&r=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dopenx%26uid%3D$%7BUID%7D', + 'https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=&predirect=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D', + 'https://cm.adform.net/cookie?redirect_url=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', + 'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}', + 'https://ap.lijit.com/pixel?redir=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID' +]; const isBidRequestValid = bid => { if (!bid.params) { @@ -32,18 +42,23 @@ const isBidRequestValid = bid => { return false; } + if (bid.params.bidfloor && (isNaN(bid.params.bidfloor) || bid.params.bidfloor < 0)) { + logError(BIDDER_CODE + ': bid.params.bidfloor should be a number equal or greater than zero'); + return false; + } + return true; }; const isBidResponseValid = bid => { - if (!bid.requestId || !bid.cpm || !bid.ttl || !bid.currency) { + if (!bid || !bid.requestId || !bid.cpm || !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); + return Boolean(bid.vastUrl || bid.vastXml); default: return false; } @@ -52,50 +67,53 @@ const isBidResponseValid = bid => { const buildRequests = (validBidRequests, bidderRequest) => { const serverRequests = validBidRequests.map(bid => { let adType = BANNER; - let w, h; + let sizes; if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { - [w, h] = bid.mediaTypes[BANNER].sizes[0]; + sizes = bid.mediaTypes[BANNER].sizes; adType = BANNER; } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { - [w, h] = bid.mediaTypes[VIDEO].playerSize; + sizes = bid.mediaTypes[VIDEO].playerSize; adType = VIDEO; } + const [w, h] = (parseSizesInput(sizes)[0] || '0x0').split('x'); const aElement = document.createElement('a'); aElement.href = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || top.location.href; const hostname = aElement.hostname const videoContext = deepAccess(bid, 'mediaTypes.video.context'); + const bidfloor = deepAccess(bid, `params.bidfloor`, 0); + + const queryParams = { + id: bid.params.id, + adtype: adType, + auc: bid.adUnitCode, + w, + h, + pos: parseInt(bid.params.position) || 1, + ua: navigator.userAgent, + l: navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '', + dt: /Mobi/.test(navigator.userAgent) ? 2 : 1, + pid: bid.params.pid, + requestId: bid.bidId, + schain: bid.schain || '', + bidfloor, + d: getDomainWithoutSubdomain(hostname), // 'vidoomy.com', + sp: encodeURIComponent(aElement.href), + usp: bidderRequest.uspConsent || '', + coppa: !!config.getConfig('coppa'), + videoContext: videoContext || '' + }; - const queryParams = []; - queryParams.push(['id', bid.params.id]); - queryParams.push(['adtype', adType]); - queryParams.push(['w', w]); - queryParams.push(['h', h]); - queryParams.push(['pos', parseInt(bid.params.position) || 1]); - queryParams.push(['ua', navigator.userAgent]); - queryParams.push(['l', navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '']); - queryParams.push(['dt', /Mobi/.test(navigator.userAgent) ? 2 : 1]); - queryParams.push(['pid', bid.params.pid]); - queryParams.push(['requestId', bid.bidId]); - queryParams.push(['d', getDomainWithoutSubdomain(hostname)]); - queryParams.push(['sp', encodeURIComponent(aElement.href)]); if (bidderRequest.gdprConsent) { - queryParams.push(['gdpr', bidderRequest.gdprConsent.gdprApplies]); - queryParams.push(['gdprcs', bidderRequest.gdprConsent.consentString]); + queryParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + queryParams.gdprcs = bidderRequest.gdprConsent.consentString; } - queryParams.push(['usp', bidderRequest.uspConsent || '']); - queryParams.push(['coppa', !!config.getConfig('coppa')]); - - const rawQueryParams = queryParams.map(qp => qp.join('=')).join('&'); - - cookieSync(bidderRequest) - const url = `${ENDPOINT}?${rawQueryParams}`; return { method: 'GET', - url: url, - data: {videoContext} + url: ENDPOINT, + data: queryParams } }); return serverRequests; @@ -117,8 +135,9 @@ const render = (bid) => { const interpretResponse = (serverResponse, bidRequest) => { try { let responseBody = serverResponse.body; + if (!responseBody) return; if (responseBody.mediaType === 'video') { - responseBody.ad = responseBody.vastUrl; + responseBody.ad = responseBody.vastUrl || responseBody.vastXml; const videoContext = bidRequest.data.videoContext; if (videoContext === OUTSTREAM) { @@ -134,13 +153,12 @@ const interpretResponse = (serverResponse, bidRequest) => { responseBody.renderer = renderer; } catch (e) { - responseBody.ad = responseBody.vastUrl; + responseBody.ad = responseBody.vastUrl || responseBody.vastXml; logError(BIDDER_CODE + ': error while installing renderer to show outstream ad'); } } } const bid = { - vastUrl: responseBody.vastUrl, ad: responseBody.ad, renderer: responseBody.renderer, mediaType: responseBody.mediaType, @@ -169,6 +187,11 @@ const interpretResponse = (serverResponse, bidRequest) => { secondaryCatIds: responseBody.meta.secondaryCatIds } }; + if (responseBody.vastUrl) { + bid.vastUrl = responseBody.vastUrl; + } else if (responseBody.vastXml) { + bid.vastXml = responseBody.vastXml; + } const bids = []; @@ -185,6 +208,21 @@ const interpretResponse = (serverResponse, bidRequest) => { } }; +function getUserSyncs (syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + const pixelType = syncOptions.pixelEnabled ? 'image' : 'iframe'; + const urls = deepAccess(responses, '0.body.pixels') || COOKIE_SYNC_FALLBACK_URLS; + + return [].concat(urls).map(url => ({ + type: pixelType, + url: url + .replace('{{GDPR}}', gdprConsent ? (gdprConsent.gdprApplies ? '1' : '0') : '0') + .replace('{{GDPR_CONSENT}}', gdprConsent ? encodeURIComponent(gdprConsent.consentString) : '') + .replace('{{USP_CONSENT}}', uspConsent ? encodeURIComponent(uspConsent) : '') + })); + } +}; + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], @@ -192,84 +230,11 @@ export const spec = { buildRequests, interpretResponse, gvlid: GVLID, + getUserSyncs, }; registerBidder(spec); -let cookieSynced = false; -function cookieSync(bidderRequest) { - if (cookieSynced) return; - const xhr = new XMLHttpRequest(); - xhr.open('GET', COOKIE_SYNC_JSON) - xhr.addEventListener('load', function () { - const macro = Macro({ - gpdr: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : '0', - gpdr_consent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : '', - }); - JSON.parse(this.responseText).filter(Boolean).forEach(url => { - firePixel(macro.replace(url)) - }) - }) - xhr.send() - cookieSynced = true; -} - -function firePixel(url) { - const img = document.createElement('img'); - img.width = 1; - img.height = 1; - img.src = url; - document.body.appendChild(img); - setTimeout(() => { - img.remove(); - }, 10000) -} - -function normalizeKey (x) { - return x.replace(/_/g, '').toLowerCase(); -} - -function Macro (obj) { - const macros = {}; - for (const key in obj) { - macros[normalizeKey(key)] = obj[key]; - } - - const set = (key, value) => { - macros[normalizeKey(key)] = typeof value === 'function' ? value : String(value); - }; - - return { - set, - setAll (obj) { - for (const key in obj) { - macros[normalizeKey(key)] = set(obj[key]); - } - }, - replace (string, extraMacros = {}) { - const allMacros = { - ...macros, - ...extraMacros, - }; - const regexes = [ - /{{\s*([a-zA-Z0-9_]+)\s*}}/g, - /\$\$\s*([a-zA-Z0-9_]+)\s*\$\$/g, - /\[\s*([a-zA-Z0-9_]+)\s*\]/g, - /\{\s*([a-zA-Z0-9_]+)\s*\}/g, - ]; - regexes.forEach(regex => { - string = string.replace(regex, (str, x) => { - x = normalizeKey(x); - let value = allMacros[x]; - value = typeof value === 'function' ? value(allMacros) : value; - return !value && value !== 0 ? '' : value; - }); - }); - return string; - }, - }; -} - function getDomainWithoutSubdomain (hostname) { const parts = hostname.split('.'); const newParts = []; diff --git a/modules/vidoomyBidAdapter.md b/modules/vidoomyBidAdapter.md index 11e0ad40dbb..d91ace5a7b9 100644 --- a/modules/vidoomyBidAdapter.md +++ b/modules/vidoomyBidAdapter.md @@ -26,7 +26,8 @@ var adUnits = [ bidder: 'vidoomy', params: { id: '123123', - pid: '123123' + pid: '123123', + bidfloor: 0.5 // This is optional } } ] @@ -50,7 +51,8 @@ var adUnits = [ bidder: 'vidoomy', params: { id: '123123', - pid: '123123' + pid: '123123', + bidfloor: 0.5 // This is optional } } ] diff --git a/modules/viewability.js b/modules/viewability.js new file mode 100644 index 00000000000..39b2ee3da16 --- /dev/null +++ b/modules/viewability.js @@ -0,0 +1,177 @@ +import {insertHtmlIntoIframe, isFn, isStr, logInfo, logWarn, triggerPixel} from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {find} from '../src/polyfill.js'; + +export const MODULE_NAME = 'viewability'; + +export function init() { + (getGlobal()).viewability = { + startMeasurement: startMeasurement, + stopMeasurement: stopMeasurement, + }; + + listenMessagesFromCreative(); +} + +const observers = {}; + +function isValid(vid, element, tracker, criteria) { + if (!element) { + logWarn(`${MODULE_NAME}: no html element provided`); + return false; + } + + let validTracker = tracker && + ((tracker.method === 'img' && isStr(tracker.value)) || + (tracker.method === 'js' && isStr(tracker.value)) || + (tracker.method === 'callback' && isFn(tracker.value))); + + if (!validTracker) { + logWarn(`${MODULE_NAME}: invalid tracker`, tracker); + return false; + } + + if (!criteria || !criteria.inViewThreshold || !criteria.timeInView) { + logWarn(`${MODULE_NAME}: missing criteria`, criteria); + return false; + } + + if (!vid || observers[vid]) { + logWarn(`${MODULE_NAME}: must provide an unregistered vid`, vid); + return false; + } + + return true; +} + +function stopObserving(observer, vid, element) { + observer.unobserve(element); + observers[vid].done = true; +} + +function fireViewabilityTracker(element, tracker) { + switch (tracker.method) { + case 'img': + triggerPixel(tracker.value, () => { + logInfo(`${MODULE_NAME}: viewability pixel fired`, tracker.value); + }); + break; + case 'js': + insertHtmlIntoIframe(``); + break; + case 'callback': + tracker.value(element); + break; + } +} + +function viewabilityCriteriaMet(observer, vid, element, tracker) { + stopObserving(observer, vid, element); + fireViewabilityTracker(element, tracker); +} + +/** + * Start measuring viewability of an element + * @typedef {{ method: string='img','js','callback', value: string|function }} ViewabilityTracker { method: 'img', value: 'http://my.tracker/123' } + * @typedef {{ inViewThreshold: number, timeInView: number }} ViewabilityCriteria { inViewThreshold: 0.5, timeInView: 1000 } + * @param {string} vid unique viewability identifier + * @param {HTMLElement} element + * @param {ViewabilityTracker} tracker + * @param {ViewabilityCriteria} criteria + */ +export function startMeasurement(vid, element, tracker, criteria) { + if (!isValid(vid, element, tracker, criteria)) { + return; + } + + const options = { + root: null, + rootMargin: '0px', + threshold: criteria.inViewThreshold, + }; + + let observer; + let viewable = false; + let stateChange = (entries) => { + viewable = entries[0].isIntersecting; + + if (viewable) { + observers[vid].timeoutId = window.setTimeout(() => { + viewabilityCriteriaMet(observer, vid, element, tracker); + }, criteria.timeInView); + } else if (observers[vid].timeoutId) { + window.clearTimeout(observers[vid].timeoutId); + } + }; + + observer = new IntersectionObserver(stateChange, options); + observers[vid] = { + observer: observer, + element: element, + timeoutId: null, + done: false, + }; + + observer.observe(element); + + logInfo(`${MODULE_NAME}: startMeasurement called with:`, arguments); +} + +/** + * Stop measuring viewability of an element + * @param {string} vid unique viewability identifier + */ +export function stopMeasurement(vid) { + if (!vid || !observers[vid]) { + logWarn(`${MODULE_NAME}: must provide a registered vid`, vid); + return; + } + + observers[vid].observer.unobserve(observers[vid].element); + if (observers[vid].timeoutId) { + window.clearTimeout(observers[vid].timeoutId); + } + + // allow the observer under this vid to be created again + if (!observers[vid].done) { + delete observers[vid]; + } +} + +function listenMessagesFromCreative() { + window.addEventListener('message', receiveMessage, false); +} + +/** + * Recieve messages from creatives + * @param {MessageEvent} evt + */ +export function receiveMessage(evt) { + var key = evt.message ? 'message' : 'data'; + var data = {}; + try { + data = JSON.parse(evt[key]); + } catch (e) { + return; + } + + if (!data || data.message !== 'Prebid Viewability') { + return; + } + + switch (data.action) { + case 'startMeasurement': + let element = data.elementId && document.getElementById(data.elementId); + if (!element) { + element = find(document.getElementsByTagName('IFRAME'), iframe => (iframe.contentWindow || iframe.contentDocument.defaultView) == evt.source); + } + + startMeasurement(data.vid, element, data.tracker, data.criteria); + break; + case 'stopMeasurement': + stopMeasurement(data.vid); + break; + } +} + +init(); diff --git a/modules/viewability.md b/modules/viewability.md new file mode 100644 index 00000000000..df93b5c40db --- /dev/null +++ b/modules/viewability.md @@ -0,0 +1,87 @@ +# Overview + +Module Name: Viewability + +Purpose: Track when a given HTML element becomes viewable + +Maintainer: atrajkovic@magnite.com + +# Configuration + +Module does not need any configuration, as long as you include it in your PBJS bundle. +Viewability module has only two functions `startMeasurement` and `stopMeasurement` which can be used to enable more complex viewability measurements. Since it allows tracking from within creative (possibly inside a safe frame) this module registers a message listener, for messages with a format that is described bellow. + +## `startMeasurement` + +| startMeasurement Arg Object | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| vid | Required | String | Unique viewability identifier, used to reference particular observer | `"ae0f9"` | +| element | Required | HTMLElement | Reference to an HTML element that needs to be tracked | `document.getElementById('test_div')` | +| tracker | Required | ViewabilityTracker | How viewaility event is communicated back to the parties of interest | `{ method: 'img', value: 'http://my.tracker/123' }` | +| criteria | Required | ViewabilityCriteria| Defines custom viewability criteria using the threshold and duration provided | `{ inViewThreshold: 0.5, timeInView: 1000 }` | + +| ViewabilityTracker | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| method | Required | String | Type of method for Tracker | `'img' OR 'js' OR 'callback'` | +| value | Required | String | URL string for 'img' and 'js' Trackers, or a function for 'callback' Tracker | `'http://my.tracker/123'` | + +| ViewabilityCriteria | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| inViewThreshold | Required | Number | Represents a percentage threshold for the Element to be registered as in view | `0.5` | +| timeInView | Required | Number | Number of milliseconds that a given element needs to be in view continuously, above the threshold | `1000` | + +## Please Note: +- `vid` allows for multiple trackers, with different criteria to be registered for a given HTML element, independently. It's not autogenerated by `startMeasurement()`, it needs to be provided by the caller so that it doesn't have to be posted back to the source iframe (in case viewability is started from within the creative). +- In case of 'callback' method, HTML element is being passed back to the callback function. +- When a tracker needs to be started, without direct access to pbjs, postMessage mechanism can be used to invoke `startMeasurement`, with a following payload: `vid`, `tracker` and `criteria` as described above, but also with `message: 'Prebid Viewability'` and `action: 'startMeasurement'`. Optionally payload can provide `elementId`, if available at that time (for ad servers where name of the iframe is known, or adservers that render outside an iframe). If `elementId` is not provided, viewability module will try to find the iframe that corresponds to the message source. + + +## `stopMeasurement` + +| stopMeasurement Arg Object | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| vid | Required | String | Unique viewability identifier, referencing an already started viewability tracker. | `"ae0f9"` | + +## Please Note: +- When a tracker needs to be stopped, without direct access to pbjs, postMessage mechanism can be used here as well. To invoke `stopMeasurement`, you provide the payload with `vid`, `message: 'Prebid Viewability'` and `action: 'stopMeasurement`. Check the example bellow. + +# Examples + +## Example of starting a viewability measurement, when you have direct access to pbjs +``` +pbjs.viewability.startMeasurement( + 'ae0f9', + document.getElementById('test_div'), + { method: 'img', value: 'http://my.tracker/123' }, + { inViewThreshold: 0.5, timeInView: 1000 } +); +``` + +## Example of starting a viewability measurement from within a rendered creative +``` +let viewabilityRecord = { + vid: 'ae0f9', + tracker: { method: 'img', value: 'http://my.tracker/123'}, + criteria: { inViewThreshold: 0.5, timeInView: 1000 }, + message: 'Prebid Viewability', + action: 'startMeasurement' +} + +window.parent.postMessage(JSON.stringify(viewabilityRecord), '*'); +``` + +## Example of stopping the viewability measurement, when you have direct access to pbjs +``` +pbjs.viewability.stopMeasurement('ae0f9'); +``` + +## Example of stopping the viewability measurement from within a rendered creative +``` +let viewabilityRecord = { + vid: 'ae0f9', + message: 'Prebid Viewability', + action: 'stopMeasurement' +} + +window.parent.postMessage(JSON.stringify(viewabilityRecord), '*'); +``` diff --git a/modules/viewdeosDXBidAdapter.js b/modules/viewdeosDXBidAdapter.js index e3d02938c5b..9e0cb91af9b 100644 --- a/modules/viewdeosDXBidAdapter.js +++ b/modules/viewdeosDXBidAdapter.js @@ -1,8 +1,8 @@ -import { deepAccess, isArray, flatten, logError, parseSizesInput } from '../src/utils.js'; +import {deepAccess, flatten, isArray, logError, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {VIDEO, BANNER} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; -import findIndex from 'core-js-pure/features/array/find-index.js'; +import {findIndex} from '../src/polyfill.js'; const URL = 'https://ghb.sync.viewdeos.com/auction/'; const OUTSTREAM_SRC = 'https://player.sync.viewdeos.com/outstream-unit/2.01/outstream.min.js'; diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index 1d80ea79e99..696d54e4b52 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -1,4 +1,4 @@ -import { triggerPixel, parseSizesInput, deepAccess, logError } from '../src/utils.js'; +import { triggerPixel, parseSizesInput, deepAccess, logError, getGptSlotInfoForAdUnitCode } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -42,7 +42,7 @@ export const spec = { } } } - return !!bid.params.uid; + return !!bid.params.uid && !isNaN(parseInt(bid.params.uid)); }, buildRequests: function(validBidRequests, bidderRequest) { const auids = []; @@ -203,7 +203,16 @@ export const spec = { }, onTimeout: function(timeoutData) { // Call '/track/bid_timeout' with timeout data - triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '?data=' + JSON.stringify(timeoutData)); + timeoutData.forEach(({ params }) => { + if (params) { + params.forEach((item) => { + if (item && item.uid) { + item.uid = parseInt(item.uid); + } + }); + } + }); + triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '//' + JSON.stringify(timeoutData)); } }; @@ -241,7 +250,7 @@ function makeVideo(videoParams = {}) { } function buildImpObject(bid) { - const { params: { uid }, bidId, mediaTypes, sizes } = bid; + const { params: { uid }, bidId, mediaTypes, sizes, adUnitCode } = bid; const video = mediaTypes && _isVideoBid(bid) && _isValidVideoBid(bid) && makeVideo(mediaTypes.video); const banner = makeBanner((mediaTypes && mediaTypes.banner) || (!video && { sizes })); const impObject = { @@ -249,10 +258,14 @@ function buildImpObject(bid) { ...(banner && { banner }), ...(video && { video }), ext: { - bidder: { uid: Number(uid) }, + bidder: { uid: parseInt(uid) }, } }; + if (impObject.banner) { + impObject.ext.bidder.adslotExists = _isAdSlotExists(adUnitCode); + } + if (impObject.ext.bidder.uid && (impObject.banner || impObject.video)) { return impObject; } @@ -355,4 +368,17 @@ function _isValidVideoBid(bid, logErrors = false) { return result; } +function _isAdSlotExists(adUnitCode) { + if (document.getElementById(adUnitCode)) { + return true; + } + + const gptAdSlot = getGptSlotInfoForAdUnitCode(adUnitCode); + if (gptAdSlot && gptAdSlot.divId && document.getElementById(gptAdSlot.divId)) { + return true; + } + + return false; +} + registerBidder(spec); diff --git a/modules/visxBidAdapter.md b/modules/visxBidAdapter.md index 9578f7cc4a7..34ebe9bb937 100644 --- a/modules/visxBidAdapter.md +++ b/modules/visxBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: YOC VIS.X Bidder Adapter Module Type: Bidder Adapter -Maintainer: service@yoc.com +Maintainer: supply.partners@yoc.com ``` # Description @@ -47,16 +47,14 @@ var adUnits = [ } ] }, - // YOC In-stream adUnit + // In-stream video adUnit { code: 'instream-test-div', mediaTypes: { video: { context: 'instream', - playerSize: [400, 300], - mimes: ['video/mp4'], - protocols: [3, 6] - }, + playerSize: [400, 300] + } }, bids: [ { diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index 8db97800630..25dbbda90cf 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -1,7 +1,7 @@ -import { _map, logWarn, deepAccess, isArray } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js' -import {BANNER, VIDEO} from '../src/mediaTypes.js' -import find from 'core-js-pure/features/array/find.js'; +import {_map, deepAccess, isArray, logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {find} from '../src/polyfill.js'; import {auctionManager} from '../src/auctionManager.js'; import {Renderer} from '../src/Renderer.js'; diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index ee17a71dd35..1a97e3bd351 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -1,8 +1,8 @@ -import { logError, isArray, deepAccess, getBidIdParameter } from '../src/utils.js'; +import {deepAccess, getBidIdParameter, isArray, logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from '../src/polyfill.js'; const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid`; const BIDDER_CODE = 'waardex'; diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 01086ad129f..0885df02f05 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -2,90 +2,556 @@ * This module adds Weborama provider to the real time data module * The {@link module:modules/realTimeData} module is required * The module will fetch contextual data (page-centric) from Weborama server + * and may access user-centric data from local storage * @module modules/weboramaRtdProvider * @requires module:modules/realTimeData */ +/** onData callback type + * @callback dataCallback + * @param {Object} data profile data + * @param {Boolean} site true if site, else it is user + * @returns {void} + */ + /** -* @typedef {Object} ModuleParams -* @property {WeboCtxConf} weboCtxConf -*/ + * @typedef {Object} ModuleParams + * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default undefined) + * @property {?dataCallback} onData callback + * @property {?WeboCtxConf} weboCtxConf + * @property {?WeboUserDataConf} weboUserDataConf + */ + +/** + * @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 {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default params.setPrebidTargeting or true) + * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default params.sendToBidders or true) + * @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 + */ /** -* @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 {?boolean} setTargeting if true will set the GAM targeting -* @property {?object} defaultProfile to be used if the profile is not found -*/ - -import { deepSetValue, logError, tryAppendQueryString, logMessage } from '../src/utils.js'; -import {submodule} from '../src/hook.js'; -import {ajax} from '../src/ajax.js'; -import {config} from '../src/config.js'; + * @typedef {Object} WeboUserDataConf + * @property {?number} accountId wam account id + * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default params.setPrebidTargeting or true) + * @property {?Boolean} sendToBidders if true, will send the user-centric profile to all bidders (default params.sendToBidders or true) + * @property {?object} 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 + */ + +import { + getGlobal +} from '../src/prebidGlobal.js'; +import { + deepSetValue, + deepAccess, + isEmpty, + mergeDeep, + logError, + logWarn, + tryAppendQueryString, + logMessage, + isFn +} from '../src/utils.js'; +import { + submodule +} from '../src/hook.js'; +import { + ajax +} from '../src/ajax.js'; +import { + getStorageManager +} from '../src/storageManager.js'; + +const adapterManager = require('../src/adapterManager.js').default; /** @type {string} */ const MODULE_NAME = 'realTimeData'; /** @type {string} */ const SUBMODULE_NAME = 'weborama'; /** @type {string} */ -const WEBO_CTX = 'webo_ctx'; +export const DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY = 'webo_wam2gam_entry'; /** @type {string} */ -const WEBO_DS = 'webo_ds'; +const LOCAL_STORAGE_USER_TARGETING_SECTION = 'targeting'; +/** @type {number} */ +const GVLID = 284; +/** @type {object} */ +export const storage = getStorageManager({gvlid: GVLID, moduleName: SUBMODULE_NAME}); /** @type {null|Object} */ -let _bigseaContextualProfile = null; +let _weboContextualProfile = null; -/** function that provides ad server targeting data to RTD-core -* @param {Array} adUnitsCodes -* @param {Object} moduleConfig -* @returns {Object} target data +/** @type {Boolean} */ +let _weboCtxInitialized = false; + +/** @type {null|Object} */ +let _weboUserDataUserProfile = null; + +/** @type {Boolean} */ +let _weboUserDataInitialized = false; + +/** Initialize module + * @param {object} moduleConfig + * @return {Boolean} true if module was initialized with success */ -function getTargetingData(adUnitsCodes, moduleConfig) { +function init(moduleConfig) { moduleConfig = moduleConfig || {}; const moduleParams = moduleConfig.params || {}; - const weboCtxConf = moduleParams.weboCtxConf || {}; - const defaultContextualProfiles = weboCtxConf.defaultProfile || {} - const profile = _bigseaContextualProfile || defaultContextualProfiles; + const weboCtxConf = moduleParams.weboCtxConf; + const weboUserDataConf = moduleParams.weboUserDataConf; - if (weboCtxConf.setOrtb2) { - const ortb2 = config.getConfig('ortb2') || {}; - if (profile[WEBO_CTX]) { - deepSetValue(ortb2, 'site.ext.data.webo_ctx', profile[WEBO_CTX]); - } - if (profile[WEBO_DS]) { - deepSetValue(ortb2, 'site.ext.data.webo_ds', profile[WEBO_DS]); - } - config.setConfig({ortb2: ortb2}); + _weboCtxInitialized = initWeboCtx(moduleParams, weboCtxConf); + _weboUserDataInitialized = initWeboUserData(moduleParams, weboUserDataConf); + + return _weboCtxInitialized || _weboUserDataInitialized; +} + +/** Initialize contextual sub module + * @param {ModuleParams} moduleParams + * @param {WeboCtxConf} weboCtxConf + * @return {Boolean} true if sub module was initialized with success + */ +function initWeboCtx(moduleParams, weboCtxConf) { + if (!weboCtxConf || weboCtxConf.enabled === false) { + moduleParams.weboCtxConf = null; + + return false } - if (weboCtxConf.setTargeting === false) { - return {}; + normalizeConf(moduleParams, weboCtxConf); + + _weboCtxInitialized = false; + _weboContextualProfile = null; + + if (!weboCtxConf.token) { + logWarn('missing param "token" for weborama contextual sub module initialization'); + return false; + } + + logMessage('weborama contextual intialized with success'); + + return true; +} + +/** Initialize weboUserData sub module + * @param {ModuleParams} moduleParams + * @param {WeboUserDataConf} weboUserDataConf + * @return {Boolean} true if sub module was initialized with success + */ +function initWeboUserData(moduleParams, weboUserDataConf) { + if (!weboUserDataConf || weboUserDataConf.enabled === false) { + moduleParams.weboUserDataConf = null; + + return false; } + normalizeConf(moduleParams, weboUserDataConf); + + _weboUserDataInitialized = false; + _weboUserDataUserProfile = null; + + let message = 'weborama user-centric intialized with success'; + if (weboUserDataConf.hasOwnProperty('accountId')) { + message = `weborama user-centric intialized with success for account: ${weboUserDataConf.accountId}`; + } + + logMessage(message); + + return true; +} + +/** @type {Object} */ +const globalDefaults = { + setPrebidTargeting: true, + sendToBidders: true, + onData: (data, kind, def) => logMessage('onData(data,kind,default)', data, kind, def), +} + +/** normalize submodule configuration + * @param {ModuleParams} moduleParams + * @param {WeboCtxConf|WeboUserDataConf} submoduleParams + * @return {void} + */ +function normalizeConf(moduleParams, submoduleParams) { + Object.entries(globalDefaults).forEach(([propertyName, globalDefaultValue]) => { + if (!submoduleParams.hasOwnProperty(propertyName)) { + const hasModuleParam = moduleParams.hasOwnProperty(propertyName); + submoduleParams[propertyName] = (hasModuleParam) ? moduleParams[propertyName] : globalDefaultValue; + } + }) +} + +/** function that provides ad server targeting data to RTD-core + * @param {Array} adUnitsCodes + * @param {Object} moduleConfig + * @returns {Object} target data + */ +function getTargetingData(adUnitsCodes, moduleConfig) { + moduleConfig = moduleConfig || {}; + const moduleParams = moduleConfig.params || {}; + const weboCtxConf = moduleParams.weboCtxConf || {}; + const weboUserDataConf = moduleParams.weboUserDataConf || {}; + const weboCtxConfTargeting = weboCtxConf.setPrebidTargeting; + const weboUserDataConfTargeting = weboUserDataConf.setPrebidTargeting; + try { - const formattedProfile = profile; - const r = adUnitsCodes.reduce((rp, adUnitCode) => { + const profile = getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting); + + if (isEmpty(profile)) { + return {}; + } + + const td = adUnitsCodes.reduce((data, adUnitCode) => { if (adUnitCode) { - rp[adUnitCode] = formattedProfile; + data[adUnitCode] = profile; } - return rp; + return data; }, {}); - return r; + + return td; } catch (e) { logError('unable to format weborama rtd targeting data', e); return {}; } } +/** function that provides complete profile formatted to be used + * @param {ModuleParams} moduleParams + * @param {Boolean} weboCtxConfTargeting + * @param {Boolean} weboUserDataConfTargeting + * @returns {Object} complete profile + */ +function getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting) { + const profile = {}; + + if (weboCtxConfTargeting) { + const contextualProfile = getContextualProfile(moduleParams.weboCtxConf || {}); + mergeDeep(profile, contextualProfile); + } + + if (weboUserDataConfTargeting) { + const weboUserDataProfile = getWeboUserDataProfile(moduleParams.weboUserDataConf || {}); + mergeDeep(profile, weboUserDataProfile); + } + + return profile; +} + +/** return contextual profile + * @param {WeboCtxConf} weboCtxConf + * @returns {Object} contextual profile + */ +function getContextualProfile(weboCtxConf) { + const defaultContextualProfile = weboCtxConf.defaultProfile || {}; + return _weboContextualProfile || defaultContextualProfile; +} + +/** return weboUserData profile + * @param {WeboUserDataConf} weboUserDataConf + * @returns {Object} weboUserData profile + */ +function getWeboUserDataProfile(weboUserDataConf) { + const weboUserDataDefaultUserProfile = weboUserDataConf.defaultProfile || {}; + + if (storage.localStorageIsEnabled() && !_weboUserDataUserProfile) { + const localStorageProfileKey = weboUserDataConf.localStorageProfileKey || DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY; + + const entry = storage.getDataFromLocalStorage(localStorageProfileKey); + if (entry) { + const data = JSON.parse(entry); + if (data && Object.keys(data).length > 0) { + _weboUserDataUserProfile = data[LOCAL_STORAGE_USER_TARGETING_SECTION]; + } + } + } + + return _weboUserDataUserProfile || weboUserDataDefaultUserProfile; +} + +/** 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) { + moduleConfig = moduleConfig || {}; + const moduleParams = moduleConfig.params || {}; + const weboCtxConf = moduleParams.weboCtxConf || {}; + + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + + if (!_weboCtxInitialized) { + handleBidRequestData(adUnits, moduleParams); + + onDone(); + + return; + } + + fetchContextualProfile(weboCtxConf, (data) => { + logMessage('fetchContextualProfile on getBidRequestData is done'); + + setWeboContextualProfile(data); + }, () => { + handleBidRequestData(adUnits, moduleParams); + + onDone(); + }); +} + +/** function that handles bid request data + * @param {Object[]} adUnits + * @param {ModuleParams} moduleParams + * @returns {void} + */ + +function handleBidRequestData(adUnits, moduleParams) { + const weboCtxConf = moduleParams.weboCtxConf || {}; + const weboUserDataConf = moduleParams.weboUserDataConf || {}; + const weboCtxConfTargeting = weboCtxConf.sendToBidders; + const weboUserDataConfTargeting = weboUserDataConf.sendToBidders; + + if (weboCtxConfTargeting) { + const contextualProfile = getContextualProfile(weboCtxConf); + if (!isEmpty(contextualProfile)) { + setBidRequestProfile(adUnits, contextualProfile, true); + } + } + + if (weboUserDataConfTargeting) { + const weboUserDataProfile = getWeboUserDataProfile(weboUserDataConf); + if (!isEmpty(weboUserDataProfile)) { + setBidRequestProfile(adUnits, weboUserDataProfile, false); + } + } + + handleOnData(weboCtxConf, weboUserDataConf); +} + +/** function that handle with onData callbacks + * @param {WeboCtxConf} weboCtxConf + * @param {WeboUserDataConf} weboUserDataConf + */ + +function handleOnData(weboCtxConf, weboUserDataConf) { + const callbacks = [{ + onData: weboCtxConf.onData, + fetchData: () => getContextualProfile(weboCtxConf), + site: true, + }, { + onData: weboUserDataConf.onData, + fetchData: () => getWeboUserDataProfile(weboUserDataConf), + site: false, + }]; + + callbacks.filter(obj => isFn(obj.onData)).forEach(obj => { + try { + const data = obj.fetchData(); + obj.onData(data, obj.site); + } catch (e) { + const kind = (obj.site) ? 'site' : 'user'; + logError(`error while executure onData callback with ${kind}-based data:`, e); + } + }); +} + +/** function that set bid request data on each segment (site or user centric) + * @param {Object[]} adUnits + * @param {Object} profile + * @param {Boolean} site true if site centric, else it is user centric + * @returns {void} + */ +function setBidRequestProfile(adUnits, profile, site) { + setGlobalOrtb2(profile, site); + + adUnits.forEach(adUnit => { + if (adUnit.hasOwnProperty('bids')) { + const adUnitCode = adUnit.code || 'no code'; + adUnit.bids.forEach(bid => handleBid(adUnitCode, profile, site, bid)); + } + }); +} + +/** @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 {string} adUnitCode + * @param {Object} profile + * @param {Boolean} site true if site centric, else it is user centric + * @param {Object} bid + * @returns {void} + */ +function handleBid(adUnitCode, profile, site, bid) { + const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder; + + logMessage(`handling on adunit '${adUnitCode}', bidder '${bidder}' and bid`, bid); + + switch (bidder) { + case APPNEXUS: + handleAppnexusBid(profile, bid); + + break; + + case PUBMATIC: + handlePubmaticBid(profile, bid); + + break; + + case SMARTADSERVER: + handleSmartadserverBid(profile, bid); + + break; + case RUBICON: + handleRubiconBid(profile, site, bid); + + break; + default: + logMessage(`unsupported bidder '${bidder}', trying via bidder ortb2 fpd`); + const section = ((site) ? 'site' : 'user'); + const base = `ortb2.${section}.ext.data`; + + assignProfileToObject(bid, base, profile); + } +} + +/** + * set ortb2 global data + * @param {Object} profile + * @param {Boolean} site + * @returns {void} + */ +function setGlobalOrtb2(profile, site) { + const section = ((site) ? 'site' : 'user'); + const base = `${section}.ext.data`; + const addOrtb2 = {}; + + assignProfileToObject(addOrtb2, base, profile); + + if (!isEmpty(addOrtb2)) { + const testGlobal = getGlobal().getConfig('ortb2') || {}; + const ortb2 = { + ortb2: mergeDeep({}, testGlobal, addOrtb2) + }; + getGlobal().setConfig(ortb2); + } +} + +/** + * 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]) + }) +} + +/** handle rubicon bid + * @param {Object} profile + * @param {Boolean} site + * @param {Object} bid + * @returns {void} + */ +function handleRubiconBid(profile, site, bid) { + const section = (site) ? 'inventory' : 'visitor'; + const base = `params.${section}`; + assignProfileToObject(bid, base, profile); +} + +/** handle appnexus/xandr bid + * @param {Object} profile + * @param {Object} bid + * @returns {void} + */ +function handleAppnexusBid(profile, bid) { + const base = 'params.keywords'; + assignProfileToObject(bid, base, profile); +} + +/** handle pubmatic bid + * @param {Object} profile + * @param {Object} bid + * @returns {void} + */ +function handlePubmaticBid(profile, bid) { + const sep = '|'; + const subsep = ','; + const bidKey = 'params.dctr'; + const target = []; + + const data = deepAccess(bid, bidKey); + 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); + } + }); + + deepSetValue(bid, bidKey, target.join(sep)); +} + +/** handle smartadserver bid + * @param {Object} profile + * @param {Object} bid + * @returns {void} + */ +function handleSmartadserverBid(profile, bid) { + const sep = ';'; + const bidKey = 'params.target'; + const target = []; + + const data = deepAccess(bid, bidKey); + 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); + } + }); + }); + deepSetValue(bid, bidKey, target.join(sep)); +} + /** set bigsea contextual profile on module state - * if the profile is empty, will store the default profile * @param {null|Object} data * @returns {void} */ -export function setBigseaContextualProfile(data) { +export function setWeboContextualProfile(data) { if (data && Object.keys(data).length > 0) { - _bigseaContextualProfile = data; + _weboContextualProfile = data; } } @@ -96,9 +562,9 @@ export function setBigseaContextualProfile(data) { */ /** onDone callback type - * @callback doneCallback - * @returns {void} - */ + * @callback doneCallback + * @returns {void} + */ /** Fetch Bigsea Contextual Profile * @param {WeboCtxConf} weboCtxConf @@ -114,10 +580,10 @@ function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { queryString = tryAppendQueryString(queryString, 'token', token); queryString = tryAppendQueryString(queryString, 'url', targetURL); - const url = 'https://ctx.weborama.com/api/profile?' + queryString; + const url = `https://ctx.weborama.com/api/profile?${queryString}`; ajax(url, { - success: function (response, req) { + success: function(response, req) { if (req.status === 200) { try { const data = JSON.parse(response); @@ -126,49 +592,28 @@ function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { } catch (e) { onDone(); logError('unable to parse weborama data', e); + throw e; } } else if (req.status === 204) { onDone(); } }, - error: function () { + error: function() { onDone(); logError('unable to get weborama data'); } }, - null, - { + null, { method: 'GET', withCredentials: false, }); } -/** Initialize module - * @param {object} moduleConfig - * @return {boolean} true if module was initialized with success - */ -function init(moduleConfig) { - _bigseaContextualProfile = null; - - moduleConfig = moduleConfig || {}; - const moduleParams = moduleConfig.params || {}; - const weboCtxConf = moduleParams.weboCtxConf || {}; - - if (weboCtxConf.token) { - fetchContextualProfile(weboCtxConf, setBigseaContextualProfile, - () => logMessage('fetchContextualProfile on init is done')); - } else { - logError('missing param "token" for weborama rtd module initialization'); - return false; - } - - return true; -} - export const weboramaSubmodule = { name: SUBMODULE_NAME, init: init, getTargetingData: getTargetingData, + getBidRequestData: getBidRequestData, }; submodule(MODULE_NAME, weboramaSubmodule); diff --git a/modules/weboramaRtdProvider.md b/modules/weboramaRtdProvider.md index e7b9b96d668..732944c6e1c 100644 --- a/modules/weboramaRtdProvider.md +++ b/modules/weboramaRtdProvider.md @@ -10,8 +10,6 @@ Maintainer: prebid-support@weborama.com Weborama provides a Semantic AI Contextual API that classifies in Real-time a web page seen by a web user within generic and custom topics. It enables publishers to better monetize their inventory and unlock it to programmatic. -ORTB2 compliant and FPD support for Prebid versions < 4.29 - Contact prebid-support@weborama.com for information. ### Publisher Usage @@ -23,26 +21,51 @@ Compile the Weborama RTD module into your Prebid build: Add the Weborama RTD provider to your Prebid config. ```javascript -pbjs.setConfig( - ... - realTimeData: { - auctionDelay: 1000, - dataProviders: [ - { +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: { - setTargeting: true, - token: "<>", - targetURL: "..." // default is document.URL + setPrebidTargeting: true, // optional + sendToBidders: true, // optional + onData: function(data, site){ // optional + var kind = (site)? 'site' : 'user'; + console.log('onData', kind, data); + }, + weboCtxConf: { + token: "to-be-defined", // mandatory + targetURL: "https://prebid.org", // default is document.URL + setPrebidTargeting: true, // override param.setPrebidTargeting or default true + sendToBidders: true, // override param.sendToBidders or default true + defaultProfile: { // optional + webo_ctx: ['moon'], + webo_ds: ['bar'] + } + //, onData: function (data, ...) { ...} + }, + weboUserDataConf: { + accountId: 12345, // optional, used for logging + setPrebidTargeting: true, // override param.setPrebidTargeting or default true + sendToBidders: true, // override param.sendToBidders or default true + defaultProfile: { // optional + webo_cs: ['Red'], + webo_audiences: ['bam'] + }, + localStorageProfileKey: 'webo_wam2gam_entry' // default + //, onData: function (data, ...) { ...} } } - } - ] - } - ... -} + }] + } + }); +}); ``` ### Parameter Descriptions for the Weborama Configuration Section @@ -52,18 +75,82 @@ pbjs.setConfig( | name | String | Real time data module name | Mandatory. Always 'Weborama' | | waitForIt | Boolean | Mandatory. Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false but recommended to true | | params | Object | | Optional | -| params.weboCtxConf | Object | Weborama Contextual Configuration | Optional | -| params.weboCtxConf.token | String | Security Token provided by Weborama, unique per client | Mandatory | -| params.weboCtxConf.targetURL | String | Url to be profiled in the contextual api | Optional. Defaults to `document.URL` | -| params.weboCtxConf.defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | -| params.weboCtxConf.setTargeting|Boolean|If true, will use the contextual profile to set the gam targeting of all adunits managed by prebid.js| Optional. Default is *true*.| -| params.weboCtxConf.setOrtb2|Boolean|If true, will use the contextual profile to set the ortb2 configuration on `site.ext.data`| Optional. Default is *false*.| +| params.setPrebidTargeting | Boolean | If true, may use the profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | +| params.sendToBidders | Boolean | If true, may send the profile to all bidders | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | +| params.weboCtxConf | Object | Weborama Contextual Configuration | Optional +| params.weboUserDataConf | Object | Weborama User-Centric Configuration | Optional | +| params.onData | Callback | If set, will receive the profile and site flag | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | + +#### Contextual Configuration + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| 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` | +| setPrebidTargeting|Boolean|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|Boolean|If true, will send the contextual profile to all bidders| 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 `{}` | +| onData | Callback | If set, will receive the profile and site flag | Optional. Default is `params.onData` (if any) or log via prebid debug | +| enabled | Boolean| if false, will ignore this configuration| default true| + +#### User-Centric Configuration + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| accountId|Number|WAM account id. If present, will be used on logging and statistics| Optional.| +| setPrebidTargeting|Boolean|If true, will use the user 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|Boolean|If true, will send the user profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or **true**.| +| onData | Callback | If set, will receive the profile and site flag | Optional. Default is `params.onData` (if any) or log via prebid debug | +| defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | +| localStorageProfileKey| String | can be used to customize the local storage key | Optional | +| enabled | Boolean| if false, will ignore this configuration| default true| + +### Supported Bidders + +We currently support the following bidder adapters: +* SmartADServer SSP +* PubMatic SSP +* AppNexus SSP +* Rubicon SSP + +We also set the bidder and global ortb2 `site` and `user` sections. The following bidders may support it, to be sure, check the `First Party Data Support` on the feature list for the particular bidder from here: https://docs.prebid.org/dev-docs/bidders + +* Adagio +* AdformOpenRTB +* AdKernel +* AdMixer +* Adnuntius +* Adrelevantis +* adxcg +* AMX RTB +* Avocet +* BeOp +* Criteo +* Etarget +* Inmar +* Index Exchange +* Livewrapped +* Mediakeys +* NoBid +* OpenX +* Opt Out Advertising +* Ozone Project +* Proxistore +* Rise +* Smaato +* Sonobi +* TheMediaGrid +* TripleLift +* TrustX +* Yahoo SSP +* Yieldlab +* Zeta Global Ssp ### Testing To view an example of available segments returned by Weborama's backends: -`gulp serve --modules=rtdModule,weboramaRtdProvider,appnexusBidAdapter` +`gulp serve --notest --nolint --modules=rtdModule,weboramaRtdProvider,smartadserverBidAdapter,pubmaticBidAdapter,appnexusBidAdapter,rubiconBidAdapter,criteoBidAdapter` and then point your browser at: diff --git a/modules/welectBidAdapter.js b/modules/welectBidAdapter.js new file mode 100644 index 00000000000..d88a3f4c3e2 --- /dev/null +++ b/modules/welectBidAdapter.js @@ -0,0 +1,106 @@ +import { deepAccess } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'welect'; +const DEFAULT_DOMAIN = 'www.welect.de'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['wlt'], + gvlid: 282, + supportedMediaTypes: ['video'], + + // short code + /** + * 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) { + return ( + deepAccess(bid, 'mediaTypes.video.context') === 'instream' && + !!bid.params.placementId + ); + }, + /** + * 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) { + return validBidRequests.map((bidRequest) => { + let rawSizes = + deepAccess(bidRequest, 'mediaTypes.video.playerSize') || + bidRequest.sizes; + let size = rawSizes[0]; + + let domain = bidRequest.params.domain || DEFAULT_DOMAIN; + + let url = `https://${domain}/api/v2/preflight/${bidRequest.params.placementId}`; + + let gdprConsent = null; + + if (bidRequest && bidRequest.gdprConsent) { + gdprConsent = { + gdpr_consent: { + gdprApplies: bidRequest.gdprConsent.gdprApplies, + tcString: bidRequest.gdprConsent.gdprConsent, + }, + }; + } + + const data = { + width: size[0], + height: size[1], + bid_id: bidRequest.bidId, + ...gdprConsent, + }; + + return { + method: 'POST', + url: url, + data: data, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + }, + }; + }); + }, + /** + * 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 responseBody = serverResponse.body; + + if (typeof responseBody !== 'object' || responseBody.available !== true) { + return []; + } + + const bidResponses = []; + const bidResponse = { + requestId: responseBody.bidResponse.requestId, + cpm: responseBody.bidResponse.cpm, + width: responseBody.bidResponse.width, + height: responseBody.bidResponse.height, + creativeId: responseBody.bidResponse.creativeId, + currency: responseBody.bidResponse.currency, + netRevenue: responseBody.bidResponse.netRevenue, + ttl: responseBody.bidResponse.ttl, + ad: responseBody.bidResponse.ad, + vastUrl: responseBody.bidResponse.vastUrl, + meta: { + advertiserDomains: responseBody.bidResponse.meta.advertiserDomains + } + }; + bidResponses.push(bidResponse); + return bidResponses; + }, +}; +registerBidder(spec); diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js index 7890628f94b..ba94f90f9c9 100644 --- a/modules/widespaceBidAdapter.js +++ b/modules/widespaceBidAdapter.js @@ -1,14 +1,8 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { - parseQueryStringParameters, - parseSizesInput -} from '../src/utils.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import find from 'core-js-pure/features/array/find.js'; -import { getStorageManager } from '../src/storageManager.js'; - -export const storage = getStorageManager(); +import {parseQueryStringParameters, parseSizesInput} from '../src/utils.js'; +import {find, includes} from '../src/polyfill.js'; +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'widespace'; const WS_ADAPTER_VERSION = '2.0.1'; @@ -17,6 +11,7 @@ const LS_KEYS = { LC_UID: 'wsLcuid', CUST_DATA: 'wsCustomData' }; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); let preReqTime = 0; diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js index 9213c113460..124aba57866 100644 --- a/modules/winrBidAdapter.js +++ b/modules/winrBidAdapter.js @@ -1,12 +1,22 @@ -import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, deepAccess, logError, convertTypes, getParameterByName, getBidRequest, isEmpty, transformBidderParamKeywords, isFn } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; -import { getStorageManager } from '../src/storageManager.js'; - -export const storage = getStorageManager(); +import { + convertCamelToUnderscore, + convertTypes, + deepAccess, + getBidRequest, + getParameterByName, + isArray, + isEmpty, + isFn, + isNumber, + isPlainObject, + logError, + transformBidderParamKeywords +} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {find, includes} from '../src/polyfill.js'; +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'winr'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -17,6 +27,8 @@ const SOURCE = 'pbjs'; const DEFAULT_CURRENCY = 'USD'; const GATE_COOKIE_NAME = 'wnr_gate'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + function buildBid(bidData) { const bid = bidData; const position = { @@ -39,9 +51,9 @@ function wrapAd(bid, position) { - + ' + }, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'https://lax1-ib.adnxs.com/impression', + 'https://www.test.com/tracker' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', function () { + const expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'adUnitCode': 'code', + 'appnexus': { + 'buyerMemberId': 958 + }, + 'meta': { + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [{ + 'bsid': '958' + }] + } + } + } + ]; + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles outstream video responses', function () { + const response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + const bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + } + + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).not.to.have.property('vastXml'); + expect(result[0]).not.to.have.property('vastUrl'); + expect(result[0]).to.have.property('width', 1); + expect(result[0]).to.have.property('height', 1); + expect(result[0]).to.have.property('mediaType', 'banner'); + }); + }); + + describe('getUserSyncs', function() { + const syncOptions = { + syncEnabled: false + }; + + it('should not return sync', function() { + const serverResponse = [{ body: '' }]; + const result = spec.getUserSyncs(syncOptions, serverResponse); + expect(result).to.be.undefined; + }); + }); + + describe('transformBidParams', function() { + it('cast placementId to number', function() { + const adUnit = { + code: 'adunit-code', + params: { + placementId: '456' + } + }; + const bid = { + params: { + placementId: '456' + }, + sizes: [[300, 250]], + mediaTypes: { + banner: { sizes: [[300, 250]] } + } + }; + + const params = spec.transformBidParams({ placementId: '456' }, true, adUnit, [{ bidderCode: 'bigRichmedia', auctionId: bid.auctionId, bids: [bid] }]); + + expect(params.placement_id).to.exist; + expect(params.placement_id).to.be.a('number'); + }); + }); + + describe('onBidWon', function() { + it('Should not have any error', function() { + const result = spec.onBidWon({}); + expect(true).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js index 500f45e0573..f80051b0a50 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -48,6 +48,17 @@ const BANNER_BID_REQUEST = { sizes: [[300, 250], [300, 600]] } }, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '164', + hp: 1 + } + ] + }, bidder: 'bizzclick', params: { placementId: 'hash', @@ -252,6 +263,11 @@ describe('BizzclickAdapter', function() { expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); }) + it('check schain is set properly', function() { + expect(request.data[0].source.ext.schain.complete).to.equal(1); + expect(request.data[0].source.ext.schain.ver).to.equal('1.0'); + }) + it('Returns valid URL', function () { expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); }); diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index 04a200d95a7..729605f7db8 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -477,6 +477,8 @@ const testsBuildRequests = [ refererInfo: getConfigBuildRequest('banner').refererInfo }, data: { + gdpr: false, + gdpr_consent: '', height: 250, width: 300, keywords: '', diff --git a/test/spec/modules/brandmetricsRtdProvider_spec.js b/test/spec/modules/brandmetricsRtdProvider_spec.js new file mode 100644 index 00000000000..3cac5a3d559 --- /dev/null +++ b/test/spec/modules/brandmetricsRtdProvider_spec.js @@ -0,0 +1,191 @@ +import * as brandmetricsRTD from '../../../modules/brandmetricsRtdProvider.js'; +import {config} from 'src/config.js'; + +const VALID_CONFIG = { + name: 'brandmetrics', + waitForIt: true, + params: { + scriptId: '00000000-0000-0000-0000-000000000000', + bidders: ['ozone'] + } +}; + +const NO_BIDDERS_CONFIG = { + name: 'brandmetrics', + waitForIt: true, + params: { + scriptId: '00000000-0000-0000-0000-000000000000' + } +}; + +const NO_SCRIPTID_CONFIG = { + name: 'brandmetrics', + waitForIt: true +}; + +const USER_CONSENT = { + gdpr: { + vendorData: { + vendor: { + consents: { + 422: true + } + }, + purpose: { + consents: { + 1: true, + 7: true + } + } + }, + gdprApplies: true + } +}; + +const NO_TCF_CONSENT = { + gdpr: { + vendorData: { + vendor: { + consents: { + 422: false + } + }, + purpose: { + consents: { + 1: false, + 7: false + } + } + }, + gdprApplies: true + } +}; + +const NO_USP_CONSENT = { + usp: '1NYY' +}; + +function mockSurveyLoaded(surveyConf) { + const commands = window._brandmetrics || []; + commands.forEach(command => { + if (command.cmd === '_addeventlistener') { + const conf = command.val; + if (conf.event === 'surveyloaded') { + conf.handler(surveyConf); + } + } + }); +} + +function scriptTagExists(url) { + const tags = document.getElementsByTagName('script'); + for (let i = 0; i < tags.length; i++) { + if (tags[i].src === url) { + return true; + } + } + return false; +} + +describe('BrandmetricsRTD module', () => { + beforeEach(function () { + const scriptTags = document.getElementsByTagName('script'); + for (let i = 0; i < scriptTags.length; i++) { + if (scriptTags[i].src.indexOf('brandmetrics') !== -1) { + scriptTags[i].remove(); + } + } + }); + + it('should init and return true', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(VALID_CONFIG, USER_CONSENT)).to.equal(true); + }); + + it('should init and return true even if bidders is not included', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(NO_BIDDERS_CONFIG, USER_CONSENT)).to.equal(true); + }); + + it('should init even if script- id is not configured', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(NO_SCRIPTID_CONFIG, USER_CONSENT)).to.equal(true); + }); + + it('should not init when there is no TCF- consent', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(VALID_CONFIG, NO_TCF_CONSENT)).to.equal(false); + }); + + it('should not init when there is no usp- consent', () => { + expect(brandmetricsRTD.brandmetricsSubmodule.init(VALID_CONFIG, NO_USP_CONSENT)).to.equal(false); + }); +}); + +describe('getBidRequestData', () => { + beforeEach(function () { + config.resetConfig() + }) + + it('should set targeting keys for specified bidders', () => { + brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => { + const bidderConfig = config.getBidderConfig() + const expected = VALID_CONFIG.params.bidders + + expected.forEach(exp => { + expect(bidderConfig[exp].ortb2.user.ext.data.mockTargetKey).to.equal('mockMeasurementId') + }) + }, VALID_CONFIG); + + mockSurveyLoaded({ + available: true, + conf: { + displayOption: { + type: 'pbjs', + targetKey: 'mockTargetKey' + } + }, + survey: { + measurementId: 'mockMeasurementId' + } + }); + }); + + it('should only set targeting keys when the brandmetrics survey- type is "pbjs"', () => { + mockSurveyLoaded({ + available: true, + conf: { + displayOption: { + type: 'dfp', + targetKey: 'mockTargetKey' + } + }, + survey: { + measurementId: 'mockMeasurementId' + } + }); + + brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => {}, VALID_CONFIG); + const bidderConfig = config.getBidderConfig() + expect(Object.keys(bidderConfig).length).to.equal(0) + }); + + it('should use a default targeting key name if the brandmetrics- configuration does not include one', () => { + mockSurveyLoaded({ + available: true, + conf: { + displayOption: { + type: 'pbjs', + } + }, + survey: { + measurementId: 'mockMeasurementId' + } + }); + + brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => {}, VALID_CONFIG); + + const bidderConfig = config.getBidderConfig() + const expected = VALID_CONFIG.params.bidders + + expected.forEach(exp => { + expect(bidderConfig[exp].ortb2.user.ext.data.brandmetrics_survey).to.equal('mockMeasurementId') + }) + }); +}); diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js index 32e1c7fe795..c36b48c5105 100644 --- a/test/spec/modules/browsiRtdProvider_spec.js +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -1,6 +1,8 @@ import * as browsiRTD from '../../../modules/browsiRtdProvider.js'; import {makeSlot} from '../integration/faker/googletag.js'; import * as utils from '../../../src/utils' +import * as events from '../../../src/events'; +import * as sinon from 'sinon'; describe('browsi Real time data sub module', function () { const conf = { @@ -15,6 +17,28 @@ describe('browsi Real time data sub module', function () { } }] }; + const auction = {adUnits: [ + { + code: 'adMock', + transactionId: 1 + }, + { + code: 'hasPrediction', + transactionId: 1 + } + ]}; + + let sandbox; + let eventsEmitSpy; + + before(() => { + sandbox = sinon.sandbox.create(); + eventsEmitSpy = sandbox.spy(events, ['emit']); + }); + + after(() => { + sandbox.restore(); + }); it('should init and return true', function () { browsiRTD.collectData(); @@ -61,13 +85,13 @@ describe('browsi Real time data sub module', function () { describe('should return data to RTD module', function () { it('should return empty if no ad units defined', function () { browsiRTD.setData({}); - expect(browsiRTD.browsiSubmodule.getTargetingData([])).to.eql({}); + expect(browsiRTD.browsiSubmodule.getTargetingData([], null, null, auction)).to.eql({}); }); it('should return NA if no prediction for ad unit', function () { makeSlot({code: 'adMock', divId: 'browsiAd_2'}); browsiRTD.setData({}); - expect(browsiRTD.browsiSubmodule.getTargetingData(['adMock'])).to.eql({adMock: {bv: 'NA'}}); + expect(browsiRTD.browsiSubmodule.getTargetingData(['adMock'], null, null, auction)).to.eql({adMock: {bv: 'NA'}}); }); it('should return prediction from server', function () { @@ -78,7 +102,7 @@ describe('browsi Real time data sub module', function () { pmd: undefined }; browsiRTD.setData(data); - expect(browsiRTD.browsiSubmodule.getTargetingData(['hasPrediction'])).to.eql({hasPrediction: {bv: '0.20'}}); + expect(browsiRTD.browsiSubmodule.getTargetingData(['hasPrediction'], null, null, auction)).to.eql({hasPrediction: {bv: '0.20'}}); }) }) @@ -135,4 +159,77 @@ describe('browsi Real time data sub module', function () { expect(utils.deepAccess(fakeAdUnits[1], 'ortb2Imp.ext.data.browsi')).to.eql({bv: '0.10'}); }) }) + + describe('should emit billable event', function () { + beforeEach(() => { + eventsEmitSpy.resetHistory(); + }) + it('should send one event per ad unit code', function () { + const auction = {adUnits: [ + { + code: 'a', + transactionId: 1 + }, + { + code: 'b', + transactionId: 2 + }, + { + code: 'a', + transactionId: 3 + }, + ]}; + + browsiRTD.browsiSubmodule.getTargetingData(['a', 'b'], null, null, auction); + expect(eventsEmitSpy.callCount).to.equal(2); + }) + it('should send events only for received ad unit codes', function () { + const auction = {adUnits: [ + { + code: 'a', + transactionId: 1 + }, + { + code: 'b', + transactionId: 2 + }, + { + code: 'c', + transactionId: 3 + }, + ]}; + + browsiRTD.browsiSubmodule.getTargetingData(['a'], null, null, auction); + expect(eventsEmitSpy.callCount).to.equal(1); + browsiRTD.browsiSubmodule.getTargetingData(['b'], null, null, auction); + expect(eventsEmitSpy.callCount).to.equal(2); + }) + it('should use 1st transaction ID in case of twin ad unit codes', function () { + const auction = { + auctionId: '123', + adUnits: [ + { + code: 'a', + transactionId: 1 + }, + { + code: 'a', + transactionId: 3 + }, + ]}; + + const expectedCall = { + vendor: 'browsi', + type: 'adRequest', + transactionId: 1, + auctionId: '123' + } + + browsiRTD.browsiSubmodule.getTargetingData(['a'], null, null, auction); + const callArguments = eventsEmitSpy.getCalls()[0].args[1]; + // billing id is random, we can't check its value + delete callArguments['billingId']; + expect(callArguments).to.eql(expectedCall); + }) + }) }); diff --git a/test/spec/modules/codefuelBidAdapter_spec.js b/test/spec/modules/codefuelBidAdapter_spec.js index 808c221af07..a2549012d84 100644 --- a/test/spec/modules/codefuelBidAdapter_spec.js +++ b/test/spec/modules/codefuelBidAdapter_spec.js @@ -183,7 +183,7 @@ describe('Codefuel Adapter', function () { tmax: 500 } const res = spec.buildRequests([bidRequest], commonBidderRequest) - expect(res.url).to.equal('https://bidder-url.com') + expect(res.url).to.equal('https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid') expect(res.data).to.deep.equal(expectedData) }) diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index 0fe4d5b358e..32f02def27e 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -7,7 +7,8 @@ describe('ColossussspAdapter', function () { bidder: 'colossusssp', bidderRequestId: '145e1d6a7837c9', params: { - placement_id: 0 + placement_id: 0, + group_id: 0 }, placementCode: 'placementid_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', @@ -60,6 +61,7 @@ describe('ColossussspAdapter', function () { }); it('Should return false when placement_id is not a number', function () { bid.params.placement_id = 'aaa'; + delete bid.params.group_id; expect(spec.isBidRequestValid(bid)).to.be.false; }); }); @@ -95,9 +97,10 @@ describe('ColossussspAdapter', function () { let placements = data['placements']; for (let i = 0; i < placements.length; i++) { let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'floor', 'gpid'); + expect(placement).to.have.all.keys('placementId', 'groupId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'floor', 'gpid'); expect(placement.schain).to.be.an('object') expect(placement.placementId).to.be.a('number'); + expect(placement.groupId).to.be.a('number'); expect(placement.bidId).to.be.a('string'); expect(placement.traffic).to.be.a('string'); expect(placement.sizes).to.be.an('array'); @@ -106,7 +109,7 @@ describe('ColossussspAdapter', function () { } }); it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + serverRequest = spec.buildRequests([], bidderRequest); let data = serverRequest.data; expect(data.placements).to.be.an('array').that.is.empty; }); @@ -186,14 +189,23 @@ describe('ColossussspAdapter', function () { }); }); + describe('onBidWon', function () { + it('should make an ajax call', function () { + const bid = { + nurl: 'http://example.com/win', + }; + expect(spec.onBidWon(bid)).to.equals(undefined); + }); + }) + describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); + let userSync = spec.getUserSyncs({}, {}, {}, {}); it('Returns valid URL and type', 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('image'); - expect(userSync[0].url).to.be.equal('https://colossusssp.com/?c=o&m=cookie'); + expect(userSync[0].type).to.be.equal('hms.gif'); + expect(userSync[0].url).to.be.equal('https://sync.colossusssp.com/hms.gif?pbjs=1&coppa=0'); }); }); }); diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js new file mode 100644 index 00000000000..28021c4f7c0 --- /dev/null +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -0,0 +1,398 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/compassBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'compass' + +describe('CompassBidAdapter', 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://sa-lb.deliverimp.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; + }); + }); + + 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://sa-cs.deliverimp.com/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://sa-cs.deliverimp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index 7d3cd48a8e4..32fd2ddb2e2 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -8,9 +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 {uspDataHandler} from 'src/adapterManager.js'; +import 'src/prebid.js'; -let assert = require('chai').assert; let expect = require('chai').expect; function createIFrameMarker() { @@ -58,6 +58,12 @@ describe('consentManagement', function () { sinon.assert.notCalled(utils.logInfo); }); + it('should not produce any USP metadata', function() { + setConsentConfig({}); + let consentMeta = uspDataHandler.getConsentMeta(); + expect(consentMeta).to.be.undefined; + }); + it('should exit the consent manager if only config.gdpr is an object', function() { setConsentConfig({ gdpr: { cmpApi: 'iab' } }); expect(consentAPI).to.be.undefined; @@ -71,6 +77,11 @@ describe('consentManagement', function () { sinon.assert.calledOnce(utils.logWarn); sinon.assert.notCalled(utils.logInfo); }); + + it('should immediately start looking up consent data', () => { + setConsentConfig({usp: {cmpApi: 'invalid'}}); + expect(uspDataHandler.ready).to.be.true; + }); }); describe('valid setConsentConfig value', function () { @@ -91,6 +102,21 @@ describe('consentManagement', function () { expect(consentAPI).to.be.equal('daa'); expect(consentTimeout).to.be.equal(7500); }); + + it('should enable uspDataHandler', () => { + setConsentConfig({usp: {cmpApi: 'daa', timeout: 7500}}); + expect(uspDataHandler.enabled).to.be.true; + }); + + it('should call setConsentData(null) on invalid CMP api', () => { + setConsentConfig({usp: {cmpApi: 'invalid'}}); + let hookRan = false; + requestBidsHook(() => { + hookRan = true; + }, {}); + expect(hookRan).to.be.true; + expect(uspDataHandler.ready).to.be.true; + }); }); describe('static consent string setConsentConfig value', () => { @@ -220,6 +246,32 @@ describe('consentManagement', function () { expect(consent).to.equal(testConsentData.uspString); sinon.assert.called(uspStub); }); + + it('should call uspDataHandler.setConsentData(null) on error', () => { + let hookRan = false; + uspStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + args[2](null, false); + }); + requestBidsHook(() => { + hookRan = true; + }, {}); + expect(hookRan).to.be.true; + expect(uspDataHandler.ready).to.be.true; + expect(uspDataHandler.getConsentData()).to.equal(null); + }); + + it('should call uspDataHandler.setConsentData(null) on timeout', (done) => { + setConsentConfig({usp: {timeout: 10}}); + let hookRan = false; + uspStub = sinon.stub(window, '__uspapi').callsFake(() => {}); + requestBidsHook(() => { hookRan = true; }, {}); + setTimeout(() => { + expect(hookRan).to.be.true; + expect(uspDataHandler.ready).to.be.true; + expect(uspDataHandler.getConsentData()).to.equal(null); + done(); + }, 20) + }); }); describe('USPAPI workflow for iframed page', function () { @@ -366,6 +418,27 @@ describe('consentManagement', function () { expect(didHookReturn).to.be.true; expect(consent).to.equal(testConsentData.uspString); }); + + it('returns USP consent metadata', function () { + let testConsentData = { + uspString: '1NY' + }; + + uspapiStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfig); + requestBidsHook(() => { didHookReturn = true; }, {}); + + let consentMeta = uspDataHandler.getConsentMeta(); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + + expect(consentMeta.usp).to.equal(testConsentData.uspString); + expect(consentMeta.generatedAt).to.be.above(1644367751709); + }); }); }); }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 5e9b0f07f46..712e311e433 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -2,6 +2,7 @@ import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTi import { gdprDataHandler } 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; @@ -45,6 +46,18 @@ describe('consentManagement', function () { expect(userCMP).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); + + it('should not produce any consent metadata', function() { + setConsentConfig(undefined) + let consentMetadata = gdprDataHandler.getConsentMeta(); + expect(consentMetadata).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }) + + it('should immediately look up consent data', () => { + setConsentConfig({gdpr: {cmpApi: 'invalid'}}); + expect(gdprDataHandler.ready).to.be.true; + }) }); describe('valid setConsentConfig value', function () { @@ -124,6 +137,11 @@ describe('consentManagement', function () { }); expect(gdprScope).to.be.equal(false); }); + + it('should enable gdprDataHandler', () => { + setConsentConfig({gdpr: {}}); + expect(gdprDataHandler.enabled).to.be.true; + }); }); describe('static consent string setConsentConfig value', () => { @@ -318,6 +336,14 @@ describe('consentManagement', function () { expect(consent).to.be.null; }); + it('should call gpdrDataHandler.setConsentData() when unknown CMP api is used', () => { + setConsentConfig({gdpr: {cmpApi: 'invalid'}}); + let hookRan = false; + requestBidsHook(() => { hookRan = true; }, {}); + expect(hookRan).to.be.true; + expect(gdprDataHandler.ready).to.be.true; + }) + it('should throw proper errors when CMP is not found', function () { setConsentConfig(goodConfigWithCancelAuction); @@ -329,6 +355,7 @@ describe('consentManagement', function () { sinon.assert.calledTwice(utils.logError); expect(didHookReturn).to.be.false; expect(consent).to.be.null; + expect(gdprDataHandler.ready).to.be.true; }); }); @@ -667,6 +694,33 @@ describe('consentManagement', function () { expect(consent.apiVersion).to.equal(2); }); + it('produces gdpr metadata', function () { + let testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded', + vendorData: { + tcString: 'abc12345234' + } + }; + cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfigWithAllowAuction); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consentMeta = gdprDataHandler.getConsentMeta(); + sinon.assert.notCalled(utils.logError); + expect(consentMeta.consentStringSize).to.be.above(0) + expect(consentMeta.gdprApplies).to.be.true; + expect(consentMeta.apiVersion).to.equal(2); + expect(consentMeta.generatedAt).to.be.above(1644367751709); + }); + it('performs lookup check and stores consentData for a valid existing user with additional consent', function () { let testConsentData = { tcString: 'abc12345234', @@ -703,6 +757,11 @@ describe('consentManagement', function () { setConsentConfig(goodConfigWithAllowAuction); + sinon.assert.calledOnce(utils.logWarn); + sinon.assert.notCalled(utils.logError); + + [utils.logWarn, utils.logError].forEach((stub) => stub.reset()); + requestBidsHook(() => { didHookReturn = true; }, { bidsBackHandler: () => bidsBackHandlerReturn = true }); @@ -713,6 +772,28 @@ describe('consentManagement', function () { expect(didHookReturn).to.be.false; expect(bidsBackHandlerReturn).to.be.true; expect(consent).to.be.null; + expect(gdprDataHandler.ready).to.be.true; + }); + + it('allows the auction when CMP is unresponsive', (done) => { + setConsentConfig({ + cmpApi: 'iab', + timeout: 10, + defaultGdprScope: true + }); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + + setTimeout(() => { + expect(didHookReturn).to.be.true; + const consent = gdprDataHandler.getConsentData(); + expect(consent.gdprApplies).to.be.true; + expect(consent.consentString).to.be.undefined; + expect(gdprDataHandler.ready).to.be.true; + done(); + }, 20); }); it('It still considers it a valid cmp response if gdprApplies is not a boolean', function () { diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index f0b02913f96..5dbb922e6dd 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -177,6 +177,69 @@ const AD_SERVER_RESPONSE = { } }; +const AD_SERVER_RESPONSE_2 = { + 'headers': null, + 'body': { + 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'pixels': [{ 'type': 'image', 'url': '//sync.serverbid.com/ss/' }], + 'bdr': 'notcx', + 'decisions': { + '2b0f82502298c9': { + 'adId': 2364764, + 'creativeId': 1950991, + 'flightId': 2788300, + 'campaignId': 542982, + 'clickUrl': 'https://e.serverbid.com/r', + 'impressionUrl': 'https://e.serverbid.com/i.gif', + 'contents': [{ + 'type': 'html', + 'body': '', + 'data': { + 'height': 90, + 'width': 728, + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' + }, + 'template': 'image' + }], + 'height': 90, + 'width': 728, + 'events': [], + 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5}, + 'mediaType': 'banner', + 'cats': ['IAB1', 'IAB2', 'IAB3'], + 'networkId': 1234567, + }, + '123': { + 'adId': 2364764, + 'creativeId': 1950991, + 'flightId': 2788300, + 'campaignId': 542982, + 'clickUrl': 'https://e.serverbid.com/r', + 'impressionUrl': 'https://e.serverbid.com/i.gif', + 'contents': [{ + 'type': 'html', + 'body': '', + 'data': { + 'height': 90, + 'width': 728, + 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', + 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' + }, + 'template': 'image' + }], + 'height': 90, + 'width': 728, + 'events': [], + 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5}, + 'mediaType': 'banner', + 'cats': ['IAB1', 'IAB2'], + 'networkId': 2345678, + } + } + } +}; + const BUILD_REQUESTS_OUTPUT = { method: 'POST', url: 'https://e.serverbid.com/api/v2', @@ -285,7 +348,7 @@ describe('Consumable BidAdapter', function () { }); it('registers bids', function () { - let bids = spec.interpretResponse(AD_SERVER_RESPONSE, BUILD_REQUESTS_OUTPUT); + let bids = spec.interpretResponse(AD_SERVER_RESPONSE_2, BUILD_REQUESTS_OUTPUT); bids.forEach(b => { expect(b).to.have.property('cpm'); expect(b.cpm).to.be.above(0); @@ -302,6 +365,11 @@ describe('Consumable BidAdapter', function () { expect(b.meta).to.have.property('advertiserDomains'); expect(b).to.have.property('netRevenue', true); expect(b).to.have.property('referrer'); + expect(b.meta).to.have.property('advertiserDomains'); + expect(b.meta).to.have.property('primaryCatId'); + expect(b.meta).to.have.property('secondaryCatIds'); + expect(b.meta).to.have.property('networkId'); + expect(b.meta).to.have.property('mediaType'); }); }); @@ -333,6 +401,24 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); + it('should return a sync url if iframe syncs are enabled and server response is empty', function () { + let opts = spec.getUserSyncs(syncOptions, []); + + expect(opts.length).to.equal(1); + }); + + it('should return a sync url if iframe syncs are enabled and server response does not contain a bdr attribute', function () { + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE]); + + expect(opts.length).to.equal(1); + }); + + it('should return a sync url if iframe syncs are enabled and server response contains a bdr attribute that is not cx', function () { + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE_2]); + + expect(opts.length).to.equal(1); + }); + 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/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index e871ab3f9c6..53169326d3b 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -2,6 +2,8 @@ import {expect} from 'chai'; import {spec, storage} from 'modules/conversantBidAdapter.js'; import * as utils from 'src/utils.js'; import {createEidsArray} from 'modules/userId/eids.js'; +import { config } from '../../../src/config.js'; +import {deepAccess} from 'src/utils'; describe('Conversant adapter tests', function() { const siteId = '108060'; @@ -119,7 +121,34 @@ describe('Conversant adapter tests', function() { bidId: 'bid005', bidderRequestId: '117d765b87bed38', auctionId: 'req000' - }]; + }, + // video with first party data + { + bidder: 'conversant', + params: { + site_id: siteId + }, + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4', 'video/x-flv'] + } + }, + ortb2Imp: { + instl: 1, + ext: { + data: { + pbadslot: 'homepage-top-rect' + } + } + }, + placementCode: 'pcode006', + transactionId: 'tx006', + bidId: 'bid006', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' + } + ]; const bidResponses = { body: { @@ -216,7 +245,7 @@ describe('Conversant adapter tests', function() { expect(payload).to.have.property('id', 'req000'); expect(payload).to.have.property('at', 1); expect(payload).to.have.property('imp'); - expect(payload.imp).to.be.an('array').with.lengthOf(6); + expect(payload.imp).to.be.an('array').with.lengthOf(7); expect(payload.imp[0]).to.have.property('id', 'bid000'); expect(payload.imp[0]).to.have.property('secure', 1); @@ -306,6 +335,16 @@ describe('Conversant adapter tests', function() { expect(payload.imp[5].video).to.not.have.property('maxduration'); expect(payload.imp[5]).to.not.have.property('banner'); + expect(payload.imp[6]).to.have.property('id', 'bid006'); + expect(payload.imp[6]).to.have.property('video'); + expect(payload.imp[6].video).to.have.property('mimes'); + expect(payload.imp[6].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[6]).to.not.have.property('banner'); + expect(payload.imp[6]).to.have.property('instl'); + expect(payload.imp[6]).to.have.property('ext'); + expect(payload.imp[6].ext).to.have.property('data'); + expect(payload.imp[6].ext.data).to.have.property('pbadslot'); + expect(payload).to.have.property('site'); expect(payload.site).to.have.property('id', siteId); expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); @@ -321,6 +360,34 @@ describe('Conversant adapter tests', function() { expect(payload).to.not.have.property('user'); // there should be no user by default }); + it('Verify first party data', () => { + const bidderRequest = {refererInfo: {referer: 'http://test.com?a=b&c=123'}}; + const cfg = {ortb2: {site: {content: {series: 'MySeries', season: 'MySeason', episode: 3, title: 'MyTitle'}}}}; + config.setConfig(cfg); + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload.site).to.have.property('content'); + expect(payload.site.content).to.have.property('series'); + expect(payload.site.content).to.have.property('season'); + expect(payload.site.content).to.have.property('episode'); + expect(payload.site.content).to.have.property('title'); + config.resetConfig(); + }); + + it('Verify supply chain data', () => { + const bidderRequest = {refererInfo: {referer: 'http://test.com?a=b&c=123'}}; + const schain = {complete: 1, ver: '1.0', nodes: [{asi: 'bidderA.com', sid: '00001', hp: 1}]}; + const bidsWithSchain = bidRequests.map((bid) => { + return Object.assign({ + schain: schain + }, bid); + }); + const request = spec.buildRequests(bidsWithSchain, bidderRequest); + const payload = request.data; + expect(deepAccess(payload, 'source.ext.schain.nodes')).to.exist; + expect(payload.source.ext.schain.nodes[0].asi).equals(schain.nodes[0].asi); + }); + it('Verify override url', function() { const testUrl = 'https://someurl?name=value'; const request = spec.buildRequests([{params: {white_label_url: testUrl}}]); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index aa995b3c9a0..8793d9351d4 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -430,7 +430,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: {} }, ]; @@ -445,7 +449,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: { zoneId: 123, publisherSubId: '123', @@ -473,7 +481,11 @@ describe('The Criteo bidding adapter', function () { it('should keep undefined sizes for non native banner', function () { const bidRequests = [ { - sizes: [[undefined, undefined]], + mediaTypes: { + banner: { + sizes: [[undefined, undefined]] + } + }, params: {}, }, ]; @@ -486,7 +498,11 @@ describe('The Criteo bidding adapter', function () { it('should keep undefined size for non native banner', function () { const bidRequests = [ { - sizes: [undefined, undefined], + mediaTypes: { + banner: { + sizes: [undefined, undefined] + } + }, params: {}, }, ]; @@ -499,7 +515,11 @@ describe('The Criteo bidding adapter', function () { it('should properly detect and get sizes of native sizeless banner', function () { const bidRequests = [ { - sizes: [[undefined, undefined]], + mediaTypes: { + banner: { + sizes: [[undefined, undefined]] + } + }, params: { nativeCallback: function() {} }, @@ -514,7 +534,11 @@ describe('The Criteo bidding adapter', function () { it('should properly detect and get size of native sizeless banner', function () { const bidRequests = [ { - sizes: [undefined, undefined], + mediaTypes: { + banner: { + sizes: [undefined, undefined] + } + }, params: { nativeCallback: function() {} }, @@ -585,7 +609,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: { zoneId: 123, }, @@ -594,7 +622,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-234', transactionId: 'transaction-234', - sizes: [[300, 250], [728, 90]], + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, params: { networkId: 456, }, @@ -625,7 +657,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: { zoneId: 123, }, @@ -647,7 +683,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: { zoneId: 123, }, @@ -663,13 +703,42 @@ describe('The Criteo bidding adapter', function () { expect(request.data.user.uspIab).to.equal('1YNY'); }); + it('should properly build a request with schain object', function () { + const expectedSchain = { + someProperty: 'someValue' + }; + const bidRequests = [ + { + bidder: 'criteo', + schain: expectedSchain, + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.source.ext.schain).to.equal(expectedSchain); + }); + it('should properly build a request with if ccpa consent field is not provided', function () { const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: { zoneId: 123, }, @@ -690,7 +759,7 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + sizes: [[640, 480]], mediaTypes: { video: { playerSize: [640, 480], @@ -717,6 +786,7 @@ describe('The Criteo bidding adapter', function () { expect(request.method).to.equal('POST'); const ortbRequest = request.data; expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(ortbRequest.slots[0].sizes).to.deep.equal([]); expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['640x480']); expect(ortbRequest.slots[0].video.maxduration).to.equal(30); expect(ortbRequest.slots[0].video.api).to.deep.equal([1, 2]); @@ -734,7 +804,7 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + sizes: [[640, 480], [800, 600]], mediaTypes: { video: { playerSize: [[640, 480], [800, 600]], @@ -760,6 +830,7 @@ describe('The Criteo bidding adapter', function () { expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; + expect(ortbRequest.slots[0].sizes).to.deep.equal([]); expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['640x480', '800x600']); expect(ortbRequest.slots[0].video.maxduration).to.equal(30); @@ -778,7 +849,7 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + sizes: [[300, 250]], mediaTypes: { video: { playerSize: [ [300, 250] ], @@ -800,6 +871,7 @@ describe('The Criteo bidding adapter', function () { expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; + expect(ortbRequest.slots[0].sizes).to.deep.equal([]); expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['300x250']); expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg']); expect(ortbRequest.slots[0].video.minduration).to.equal(1); @@ -816,7 +888,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: { zoneId: 123, }, @@ -838,7 +914,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: { zoneId: 123 } @@ -863,7 +943,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: { zoneId: 123, ext: { @@ -907,7 +991,11 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, params: { zoneId: 123, ext: { diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 828b8401af1..d50eadebb55 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -116,16 +116,19 @@ describe('CriteoId module', function () { expect(setCookieStub.calledWith('cto_bundle')).to.be.false; expect(setLocalStorageStub.calledWith('cto_bundle')).to.be.false; } else if (response.bundle) { - expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs)).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.testdev.com')).to.be.true; expect(setLocalStorageStub.calledWith('cto_bundle', response.bundle)).to.be.true; expect(triggerPixelStub.called).to.be.false; } if (response.bidId) { - expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs)).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.testdev.com')).to.be.true; expect(setLocalStorageStub.calledWith('cto_bidid', response.bidId)).to.be.true; } else { - expect(setCookieStub.calledWith('cto_bidid', '', pastDateString)).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', '', pastDateString, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', '', pastDateString, null, '.testdev.com')).to.be.true; expect(removeFromLocalStorageStub.calledWith('cto_bidid')).to.be.true; } }); diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index ccd205964a9..928c252943c 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -9,8 +9,11 @@ import { setConfig, addBidResponseHook, currencySupportEnabled, - currencyRates + currencyRates, + ready } from 'modules/currency.js'; +import {createBid} from '../../../src/bidfactory.js'; +import CONSTANTS from '../../../src/constants.json'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -22,8 +25,13 @@ describe('currency', function () { let fn = sinon.spy(); + function makeBid(bidProps) { + return Object.assign(createBid(CONSTANTS.STATUS.GOOD), bidProps); + } + beforeEach(function () { fakeCurrencyFileServer = sinon.fakeServer.create(); + ready.reset(); }); afterEach(function () { @@ -286,7 +294,7 @@ describe('currency', function () { }); describe('currency.addBidResponseDecorator bidResponseQueue', function () { - it('not run until currency rates file is loaded', function () { + it('not run until currency rates file is loaded', function (done) { setConfig({}); fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); @@ -296,21 +304,34 @@ describe('currency', function () { setConfig({ 'adServerCurrency': 'JPY' }); var marker = false; - addBidResponseHook(function() { + let promiseResolved = false; + addBidResponseHook(Object.assign(function() { marker = true; - }, 'elementId', bid); + }, { + bail: function (promise) { + promise.then(() => promiseResolved = true); + } + }), 'elementId', bid); expect(marker).to.equal(false); - fakeCurrencyFileServer.respond(); - expect(marker).to.equal(true); + setTimeout(() => { + expect(promiseResolved).to.be.false; + fakeCurrencyFileServer.respond(); + + setTimeout(() => { + expect(marker).to.equal(true); + expect(promiseResolved).to.be.true; + done(); + }); + }); }); }); describe('currency.addBidResponseDecorator', function () { it('should leave bid at 1 when currency support is not enabled and fromCurrency is USD', function () { setConfig({}); - var bid = { 'cpm': 1, 'currency': 'USD' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'USD' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -321,7 +342,7 @@ describe('currency', function () { it('should result in NO_BID when currency support is not enabled and fromCurrency is not USD', function () { setConfig({}); - var bid = { 'cpm': 1, 'currency': 'GBP' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'GBP' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -333,7 +354,7 @@ describe('currency', function () { setConfig({ 'adServerCurrency': 'USD' }); - var bid = { 'cpm': 1, 'currency': 'USD' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'USD' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -348,7 +369,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'JPY' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'ABC' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'ABC' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -360,7 +381,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'ABC' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'GBP' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'GBP' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -372,7 +393,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'JPY' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'JPY' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'JPY' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -385,7 +406,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'GBP' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'USD' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'USD' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -398,7 +419,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'GBP' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'CNY' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'CNY' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; @@ -411,7 +432,7 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'CNY' }); fakeCurrencyFileServer.respond(); - var bid = { 'cpm': 1, 'currency': 'JPY' }; + var bid = makeBid({ 'cpm': 1, 'currency': 'JPY' }); var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index 4ed19cf7b74..b21e73e1561 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import * as utils from '../../../src/utils.js'; +import { config } from '../../../src/config.js'; import { spec, CW_PAGE_VIEW_ID, @@ -24,7 +25,6 @@ const BID_DEFAULTS = { params: { placementId: 123456, pageId: 777, - adUnitElementId: 'target-div' }, sizes: [[300, 250], [1, 1]], }; @@ -36,11 +36,6 @@ const BidderRequestBuilder = function BidderRequestBuilder(options) { bidderRequestId: BID_DEFAULTS.request.bidderRequestId, transactionId: BID_DEFAULTS.request.transactionId, timeout: 3000, - refererInfo: { - numIframes: 0, - reachedTop: true, - referer: 'http://test.io/index.html?pbjs_debug=true' - } }; const request = { @@ -82,17 +77,15 @@ const BidRequestBuilder = function BidRequestBuilder(options, deleteKeys) { }; describe('C-WIRE bid adapter', () => { - let utilsMock; let sandbox; beforeEach(() => { - utilsMock = sinon.mock(utils); sandbox = sinon.createSandbox(); }); afterEach(() => { - utilsMock.restore(); sandbox.restore(); + config.resetConfig(); }); // START TESTING @@ -127,25 +120,149 @@ describe('C-WIRE bid adapter', () => { bid01.params.pageId = '3320'; expect(spec.isBidRequestValid(bid01)).to.equal(false); }); + }); + + 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'); + }); - it('should use params.adUnitElementId if provided', function () { + it('creates a valid request - read debug params from second bid', function () { const bid01 = new BidRequestBuilder().withParams().build(); - expect(spec.isBidRequestValid(bid01)).to.equal(true); - expect(bid01.params.adUnitElementId).to.exist; - expect(bid01.params.adUnitElementId).to.equal('target-div'); + 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'); }); - it('should use default adUnitCode if no adUnitElementId provided', function () { - const bid01 = new BidRequestBuilder().withParams({}, ['adUnitElementId']).build(); - expect(spec.isBidRequestValid(bid01)).to.equal(true); - expect(bid01.params.adUnitElementId).to.exist; - expect(bid01.params.adUnitElementId).to.equal('original-div'); + 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('C-WIRE - buildRequests()', function () { - it('creates a valid request', function () { + 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('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'); + }); + + 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: { @@ -153,12 +270,18 @@ describe('C-WIRE bid adapter', () => { } } }).withParams().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(null); + expect(requests.data.cwapikey).to.equal(null); + expect(requests.data.refgroups.length).to.equal(0); }); }); diff --git a/test/spec/modules/dacIdSystem_spec.js b/test/spec/modules/dacIdSystem_spec.js new file mode 100644 index 00000000000..d78b4a69000 --- /dev/null +++ b/test/spec/modules/dacIdSystem_spec.js @@ -0,0 +1,51 @@ +import { dacIdSystemSubmodule, storage, cookieKey } from 'modules/dacIdSystem.js'; + +const DACID_DUMMY_VALUE = 'dacIdTest'; +const DACID_DUMMY_OBJ = { + dacId: DACID_DUMMY_VALUE +}; + +describe('dacId module', function () { + let getCookieStub; + + beforeEach(function (done) { + getCookieStub = sinon.stub(storage, 'getCookie'); + done(); + }); + + afterEach(function () { + getCookieStub.restore(); + }); + + const cookieTestCasesForEmpty = [ + undefined, + null, + '' + ] + + describe('getId()', function () { + it('should return the uid when it exists in cookie', function () { + getCookieStub.withArgs(cookieKey).returns(DACID_DUMMY_VALUE); + const id = dacIdSystemSubmodule.getId(); + expect(id).to.be.deep.equal({id: {dacId: DACID_DUMMY_VALUE}}); + }); + + cookieTestCasesForEmpty.forEach(testCase => it('should return the uid when it not exists in cookie', function () { + getCookieStub.withArgs(cookieKey).returns(testCase); + const id = dacIdSystemSubmodule.getId(); + expect(id).to.be.deep.equal(undefined); + })); + }); + + describe('decode()', function () { + it('should return the uid when it exists in cookie', function () { + const decoded = dacIdSystemSubmodule.decode(DACID_DUMMY_OBJ); + expect(decoded).to.be.deep.equal({dacId: {id: DACID_DUMMY_VALUE}}); + }); + + it('should return the undefined when decode id is not "string"', function () { + const decoded = dacIdSystemSubmodule.decode(1); + expect(decoded).to.equal(undefined); + }); + }); +}); diff --git a/test/spec/modules/dailyhuntBidAdapter_spec.js b/test/spec/modules/dailyhuntBidAdapter_spec.js new file mode 100644 index 00000000000..f347d6cec5b --- /dev/null +++ b/test/spec/modules/dailyhuntBidAdapter_spec.js @@ -0,0 +1,404 @@ +import { expect } from 'chai'; +import { spec } from 'modules/dailyhuntBidAdapter.js'; + +const PROD_PREBID_ENDPOINT_URL = 'https://pbs.dailyhunt.in/openrtb2/auction?partner=dailyhunt'; +const PROD_PREBID_TEST_ENDPOINT_URL = 'https://qa-pbs-van.dailyhunt.in/openrtb2/auction?partner=dailyhunt'; + +const _encodeURIComponent = function (a) { + if (!a) { return } + let b = window.encodeURIComponent(a); + b = b.replace(/'/g, '%27'); + return b; +} + +describe('DailyhuntAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'dailyhunt', + 'params': { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt' + } + }; + + 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 = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + describe('buildRequests', function() { + let bidRequests = [ + { + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt', + bidfloor: 0.1, + device: { + ip: '47.9.247.217' + }, + site: { + cat: ['1', '2', '3'] + } + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 50]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + let nativeBidRequests = [ + { + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt', + }, + nativeParams: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [150, 50] + }, + }, + mediaTypes: { + native: { + title: { + required: true + }, + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 50]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + let videoBidRequests = [ + { + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt' + }, + nativeParams: { + video: { + context: 'instream' + } + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 50]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + let bidderRequest = { + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'bidderCode': 'dailyhunt', + 'bids': [ + { + ...bidRequests[0] + } + ], + 'refererInfo': { + 'referer': 'http://m.dailyhunt.in/' + } + }; + let nativeBidderRequest = { + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'bidderCode': 'dailyhunt', + 'bids': [ + { + ...nativeBidRequests[0] + } + ], + 'refererInfo': { + 'referer': 'http://m.dailyhunt.in/' + } + }; + let videoBidderRequest = { + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'bidderCode': 'dailyhunt', + 'bids': [ + { + ...videoBidRequests[0] + } + ], + 'refererInfo': { + 'referer': 'http://m.dailyhunt.in/' + } + }; + + it('sends display bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); + expect(request.method).to.equal('POST'); + }); + + it('sends native bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(nativeBidRequests, nativeBidderRequest)[0]; + expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); + expect(request.method).to.equal('POST'); + }); + + it('sends video bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(videoBidRequests, videoBidderRequest)[0]; + expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); + expect(request.method).to.equal('POST'); + }); + }); + describe('interpretResponse', function () { + let bidResponses = { + id: 'da32def7-6779-403c-ada7-0b201dbc9744', + seatbid: [ + { + bid: [ + { + id: 'id1', + impid: 'banner-impid', + price: 1.4, + adm: 'adm', + adid: '66658', + crid: 'asd5ddbf014cac993.66466212', + dealid: 'asd5ddbf014cac993.66466212', + w: 300, + h: 250, + nurl: 'winUrl', + ext: { + prebid: { + type: 'banner' + } + } + }, + { + id: '5caccc1f-94a6-4230-a1f9-6186ee65da99', + impid: 'video-impid', + price: 1.4, + nurl: 'winUrl', + adm: 'adm', + adid: '980', + crid: '2394', + w: 300, + h: 250, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + cacheKey: 'cache_key', + vastUrl: 'vastUrl' + } + } + }, + { + id: '74973faf-cce7-4eff-abd0-b59b8e91ca87', + impid: 'native-impid', + price: 50, + nurl: 'winUrl', + adm: '{"native":{"link":{"url":"url","clicktrackers":[]},"assets":[{"id":1,"required":1,"img":{},"video":{},"data":{},"title":{"text":"TITLE"},"link":{}},{"id":1,"required":1,"img":{},"video":{},"data":{"type":2,"value":"Lorem Ipsum Lorem Ipsum Lorem Ipsum."},"title":{},"link":{}},{"id":1,"required":1,"img":{},"video":{},"data":{"type":12,"value":"Install Here"},"title":{},"link":{}},{"id":1,"required":1,"img":{"type":3,"url":"urk","w":990,"h":505},"video":{},"data":{},"title":{},"link":{}}],"imptrackers":[]}}', + adid: '968', + crid: '2370', + w: 300, + h: 250, + ext: { + prebid: { + type: 'native' + }, + bidder: null + } + }, + { + id: '5caccc1f-94a6-4230-a1f9-6186ee65da99', + impid: 'video-outstream-impid', + price: 1.4, + nurl: 'winUrl', + adm: 'adm', + adid: '980', + crid: '2394', + w: 300, + h: 250, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + cacheKey: 'cache_key', + vastUrl: 'vastUrl' + } + } + }, + ], + seat: 'dailyhunt' + } + ], + ext: { + responsetimemillis: { + dailyhunt: 119 + } + } + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '1', + cpm: 1.4, + creativeId: 'asd5ddbf014cac993.66466212', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + ad: 'adm', + mediaType: 'banner', + winUrl: 'winUrl', + adomain: 'dailyhunt' + }, + { + requestId: '2', + cpm: 1.4, + creativeId: '2394', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + mediaType: 'video', + winUrl: 'winUrl', + adomain: 'dailyhunt', + videoCacheKey: 'cache_key', + vastUrl: 'vastUrl', + }, + { + requestId: '3', + cpm: 1.4, + creativeId: '2370', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + mediaType: 'native', + winUrl: 'winUrl', + adomain: 'dailyhunt', + native: { + clickUrl: 'https%3A%2F%2Fmontu1996.github.io%2F', + clickTrackers: [], + impressionTrackers: [], + javascriptTrackers: [], + title: 'TITLE', + body: 'Lorem Ipsum Lorem Ipsum Lorem Ipsum.', + cta: 'Install Here', + image: { + url: 'url', + height: 505, + width: 990 + } + } + }, + { + requestId: '4', + cpm: 1.4, + creativeId: '2394', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + mediaType: 'video', + winUrl: 'winUrl', + adomain: 'dailyhunt', + vastXml: 'adm', + }, + ]; + let bidderRequest = { + bids: [ + { + bidId: 'banner-impid', + adUnitCode: 'code1', + requestId: '1' + }, + { + bidId: 'video-impid', + adUnitCode: 'code2', + requestId: '2', + mediaTypes: { + video: { + context: 'instream' + } + } + }, + { + bidId: 'native-impid', + adUnitCode: 'code3', + requestId: '3' + }, + { + bidId: 'video-outstream-impid', + adUnitCode: 'code4', + requestId: '4', + mediaTypes: { + video: { + context: 'outstream' + } + } + }, + ] + } + let result = spec.interpretResponse({ body: bidResponses }, bidderRequest); + result.forEach((r, i) => { + expect(Object.keys(r)).to.have.members(Object.keys(expectedResponse[i])); + }); + }); + }) + describe('onBidWon', function () { + it('should hit win url when bid won', function () { + let bid = { + requestId: '1', + cpm: 1.4, + creativeId: 'asd5ddbf014cac993.66466212', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + ad: 'adm', + mediaType: 'banner', + winUrl: 'winUrl' + }; + expect(spec.onBidWon(bid)).to.equal(undefined); + }); + }) +}) diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 0ec12905430..ff7b0aad48c 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -96,8 +96,8 @@ const bidderRequest = { refererInfo: { numIframes: 0, reachedTop: true, - referer: 'https://v5demo.datablocks.net/test', - stack: ['https://v5demo.datablocks.net/test'] + referer: 'https://7560.v5demo.datablocks.net/test', + stack: ['https://7560.v5demo.datablocks.net/test'] }, start: Date.now(), timeout: 10000 @@ -452,7 +452,7 @@ describe('DatablocksAdapter', function() { it('Returns valid URL', function() { expect(request.url).to.exist; - expect(request.url).to.equal('https://7560.v5demo.datablocks.net/openrtb/?sid=7560'); + expect(request.url).to.equal('https://v5demo.datablocks.net/openrtb/?sid=7560'); }); it('Creates an array of request objects', function() { diff --git a/test/spec/modules/dchain_spec.js b/test/spec/modules/dchain_spec.js new file mode 100644 index 00000000000..45061c539c1 --- /dev/null +++ b/test/spec/modules/dchain_spec.js @@ -0,0 +1,329 @@ +import { checkDchainSyntax, addBidResponseHook } from '../../../modules/dchain.js'; +import { config } from '../../../src/config.js'; +import { expect } from 'chai'; + +describe('dchain module', function () { + const STRICT = 'strict'; + const RELAX = 'relaxed'; + const OFF = 'off'; + + describe('checkDchainSyntax', function () { + let bid; + + beforeEach(function () { + bid = { + meta: { + dchain: { + 'ver': '1.0', + 'complete': 0, + 'ext': {}, + 'nodes': [{ + 'asi': 'domain.com', + 'bsid': '12345', + }, { + 'name': 'bidder', + 'domain': 'bidder.com', + 'ext': {} + }] + } + } + }; + }); + + it('Returns false if complete param is not 0 or 1', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.complete = 0; // integer + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.complete = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.complete = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.complete; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if ver param is not a String', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.ver = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.ver = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.ver; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if ext param is not an Object', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.ext = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.true; + delete dchainConfig.ext; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.ext = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes param is not an Array', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if unknown field is used in main dchain', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.test = '1'; // String + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].asi is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].asi = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].asi = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].asi = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].asi; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].asi = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].asi = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].bsid is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].bsid = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].bsid = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].bsid = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].bsid; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].bsid = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].bsid = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].rid is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].rid = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].rid = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].rid = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].rid; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].rid = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].rid = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].name is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].name = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].name = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].name = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].name; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].name = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].name = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].domain is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].domain = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].domain = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].domain = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].domain; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].domain = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].domain = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].ext is not an Object', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.nodes[0].ext = '1'; // String + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.true; + delete dchainConfig.nodes[0].ext; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].ext = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if unknown field is used in nodes[]', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.nodes[0].test = '1'; // String + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Relaxed mode: returns true even for invalid config', function () { + bid.meta.dchain = { + ver: 1.1, + complete: '0', + nodes: [{ + name: 'asdf', + domain: ['domain.com'] + }] + }; + + expect(checkDchainSyntax(bid, RELAX)).to.true; + }); + }); + + describe('addBidResponseHook', function () { + let bid; + let adUnitCode = 'adUnit1'; + + beforeEach(function () { + bid = { + bidderCode: 'bidderA', + meta: { + dchain: { + 'ver': '1.0', + 'complete': 0, + 'ext': {}, + 'nodes': [{ + 'asi': 'domain.com', + 'bsid': '12345', + }, { + 'name': 'bidder', + 'domain': 'bidder.com', + 'ext': {} + }] + }, + networkName: 'myNetworkName', + networkId: 8475 + } + }; + }); + + afterEach(function () { + config.resetConfig(); + }); + + it('good strict config should append a node object to existing bid.meta.dchain object', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.nodes[1]).to.exist.and.to.deep.equal({ + 'name': 'bidder', + 'domain': 'bidder.com', + 'ext': {} + }); + expect(bid.meta.dchain.nodes[2]).to.exist.and.to.deep.equal({ asi: 'bidderA' }); + } + + config.setConfig({ dchain: { validation: STRICT } }); + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('bad strict config should delete the bid.meta.dchain object', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.not.exist; + } + + config.setConfig({ dchain: { validation: STRICT } }); + bid.meta.dchain.complete = 3; + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('relaxed config should allow bid.meta.dchain to proceed, even with bad values', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.complete).to.exist.and.to.equal(3); + expect(bid.meta.dchain.nodes[2]).to.exist.and.to.deep.equal({ asi: 'bidderA' }); + } + + config.setConfig({ dchain: { validation: RELAX } }); + bid.meta.dchain.complete = 3; + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('off config should allow the bid.meta.dchain to proceed', function () { + // check for missing nodes + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.complete).to.exist.and.to.equal(0); + expect(bid.meta.dchain.nodes).to.exist.and.to.deep.equal({ test: 123 }); + } + + config.setConfig({ dchain: { validation: OFF } }); + bid.meta.dchain.nodes = { test: 123 }; + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('no bidder dchain', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.ver).to.exist.and.to.equal('1.0'); + expect(bid.meta.dchain.complete).to.exist.and.to.equal(0); + expect(bid.meta.dchain.nodes).to.exist.and.to.deep.equal([{ name: 'myNetworkName', bsid: '8475' }, { name: 'bidderA' }]); + } + + delete bid.meta.dchain; + config.setConfig({ dchain: { validation: RELAX } }); + addBidResponseHook(testCallback, adUnitCode, bid); + }); + }); +}); diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js new file mode 100644 index 00000000000..79866d023e9 --- /dev/null +++ b/test/spec/modules/debugging_mod_spec.js @@ -0,0 +1,451 @@ +import {expect} from 'chai'; +import {BidInterceptor} from '../../../modules/debugging/bidInterceptor.js'; +import {bidderBidInterceptor} from '../../../modules/debugging/index.js'; +import {pbsBidInterceptor} from '../../../modules/debugging/pbsInterceptor.js'; + +describe('bid interceptor', () => { + let interceptor, mockSetTimeout; + beforeEach(() => { + mockSetTimeout = sinon.stub().callsFake((fn) => fn()); + interceptor = new BidInterceptor({setTimeout: mockSetTimeout}); + }); + + function setRules(...rules) { + interceptor.updateConfig({ + intercept: rules + }); + } + + describe('serializeConfig', () => { + Object.entries({ + regexes: /pat/, + functions: () => ({}) + }).forEach(([test, arg]) => { + it(`should filter out ${test}`, () => { + const valid = [{key1: 'value'}, {key2: 'value'}]; + const ser = interceptor.serializeConfig([...valid, {outer: {inner: arg}}]); + expect(ser).to.eql(valid); + }); + }); + }); + + describe('match()', () => { + Object.entries({ + value: {key: 'value'}, + regex: {key: /^value$/}, + 'function': (o) => o.key === 'value' + }).forEach(([test, matcher]) => { + describe(`by ${test}`, () => { + it('should work on matching top-level properties', () => { + setRules({when: matcher}); + const rule = interceptor.match({key: 'value'}); + expect(rule).to.not.eql(null); + }); + + it('should work on matching nested properties', () => { + setRules({when: {outer: {inner: matcher}}}); + const rule = interceptor.match({outer: {inner: {key: 'value'}}}); + expect(rule).to.not.eql(null); + }); + + it('should not work on non-matching inputs', () => { + setRules({when: matcher}); + expect(interceptor.match({key: 'different-value'})).to.not.be.ok; + expect(interceptor.match({differentKey: 'value'})).to.not.be.ok; + }); + }); + }); + + it('should respect rule order', () => { + setRules({when: {key: 'value'}}, {when: {}}, {when: {}}); + const rule = interceptor.match({}); + expect(rule.no).to.equal(2); + }); + + it('should pass extra arguments to property function matchers', () => { + let matchDef = { + key: sinon.stub(), + outer: {inner: {key: sinon.stub()}} + }; + const extraArgs = [{}, {}]; + setRules({when: matchDef}); + interceptor.match({key: {}, outer: {inner: {key: {}}}}, ...extraArgs); + [matchDef.key, matchDef.outer.inner.key].forEach((fn) => { + expect(fn.calledOnceWith(sinon.match.any, ...extraArgs.map(sinon.match.same))).to.be.true; + }); + }); + + it('should pass extra arguments to single-function matcher', () => { + let matchDef = sinon.stub(); + setRules({when: matchDef}); + const args = [{}, {}, {}]; + interceptor.match(...args); + expect(matchDef.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }); + }); + + describe('rule', () => { + function matchingRule({replace, options}) { + setRules({when: {}, then: replace, options: options}); + return interceptor.match({}); + } + + describe('.replace()', () => { + const REQUIRED_KEYS = [ + // https://docs.prebid.org/dev-docs/bidder-adaptor.html#bidder-adaptor-Interpreting-the-Response + 'requestId', 'cpm', 'currency', 'width', 'height', 'ttl', + 'creativeId', 'netRevenue', 'meta', 'ad' + ]; + it('should include required bid response keys by default', () => { + expect(matchingRule({}).replace({})).to.include.keys(REQUIRED_KEYS); + }); + + Object.entries({ + value: {key: 'value'}, + 'function': () => ({key: 'value'}) + }).forEach(([test, replDef]) => { + describe(`by ${test}`, () => { + it('should merge top-level properties with replace definition', () => { + const result = matchingRule({replace: replDef}).replace({}); + expect(result).to.include.keys(REQUIRED_KEYS); + expect(result.key).to.equal('value'); + }); + + it('should merge nested properties with replace definition', () => { + const result = matchingRule({replace: {outer: {inner: replDef}}}).replace({}); + expect(result).to.include.keys(REQUIRED_KEYS); + expect(result.outer.inner).to.eql({key: 'value'}); + }); + }); + }); + + it('should pass extra arguments to single function replacer', () => { + const replDef = sinon.stub(); + const args = [{}, {}, {}]; + matchingRule({replace: replDef}).replace(...args); + expect(replDef.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }); + + it('should pass extra arguments to function property replacers', () => { + const replDef = { + key: sinon.stub(), + outer: {inner: {key: sinon.stub()}} + }; + const args = [{}, {}, {}]; + matchingRule({replace: replDef}).replace(...args); + [replDef.key, replDef.outer.inner.key].forEach((repl) => { + expect(repl.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }); + }); + }); + + describe('.options', () => { + it('should include default rule options', () => { + const optDef = {someOption: 'value'}; + const ruleOptions = matchingRule({options: optDef}).options; + expect(ruleOptions).to.include(optDef); + expect(ruleOptions).to.include(interceptor.DEFAULT_RULE_OPTIONS); + }); + + it('should override defaults', () => { + const optDef = {delay: 123}; + const ruleOptions = matchingRule({options: optDef}).options; + expect(ruleOptions).to.eql(optDef); + }); + }); + }); + + describe('intercept()', () => { + let done, addBid; + + function intercept(args = {}) { + const bidRequest = {bids: args.bids || []}; + return interceptor.intercept(Object.assign({bidRequest, done, addBid}, args)); + } + + beforeEach(() => { + done = sinon.spy(); + addBid = sinon.spy(); + }); + + describe('on no match', () => { + it('should return untouched bids and bidRequest', () => { + const bids = [{}, {}]; + const bidRequest = {}; + const result = intercept({bids, bidRequest}); + expect(result.bids).to.equal(bids); + expect(result.bidRequest).to.equal(bidRequest); + }); + + it('should call done() immediately', () => { + intercept(); + expect(done.calledOnce).to.be.true; + expect(mockSetTimeout.args[0][1]).to.equal(0); + }); + + it('should not call addBid', () => { + intercept(); + expect(addBid.called).to.not.be.ok; + }); + }); + + describe('on match', () => { + let match1, match2, repl1, repl2; + const DELAY_1 = 123; + const DELAY_2 = 321; + const REQUEST = { + bids: [ + {id: 1, match: false}, + {id: 2, match: 1}, + {id: 3, match: 2} + ] + }; + + beforeEach(() => { + match1 = sinon.stub().callsFake((bid) => bid.match === 1); + match2 = sinon.stub().callsFake((bid) => bid.match === 2); + repl1 = sinon.stub().returns({replace: 1}); + repl2 = sinon.stub().returns({replace: 2}); + setRules( + {when: match1, then: repl1, options: {delay: DELAY_1}}, + {when: match2, then: repl2, options: {delay: DELAY_2}}, + ); + }); + + it('should return only non-matching bids', () => { + const {bids, bidRequest} = intercept({bidRequest: REQUEST}); + expect(bids).to.eql([REQUEST.bids[0]]); + expect(bidRequest.bids).to.eql([REQUEST.bids[0]]); + }); + + it('should call addBid for each matching bid', () => { + intercept({bidRequest: REQUEST}); + expect(addBid.callCount).to.equal(2); + expect(addBid.calledWith(sinon.match({replace: 1, isDebug: true}), REQUEST.bids[1])).to.be.true; + expect(addBid.calledWith(sinon.match({replace: 2, isDebug: true}), REQUEST.bids[2])).to.be.true; + [DELAY_1, DELAY_2].forEach((delay) => { + expect(mockSetTimeout.calledWith(sinon.match.any, delay)).to.be.true; + }); + }); + + it('should call done()', () => { + intercept({bidRequest: REQUEST}); + expect(done.calledOnce).to.be.true; + }); + + it('should pass bid and bidRequest to match and replace functions', () => { + intercept({bidRequest: REQUEST}); + Object.entries({ + 1: [match1, repl1], + 2: [match2, repl2] + }).forEach(([index, fns]) => { + fns.forEach((fn) => { + expect(fn.calledWith(REQUEST.bids[index], REQUEST)).to.be.true; + }); + }); + }); + }); + }); +}); + +describe('bidderBidInterceptor', () => { + let next, interceptBids, onCompletion, interceptResult, done, addBid; + + function interceptorArgs({spec = {}, bids = [], bidRequest = {}, ajax = {}, wrapCallback = {}, cbs = {}} = {}) { + return [next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, Object.assign({onCompletion}, cbs)]; + } + + beforeEach(() => { + next = sinon.spy(); + interceptBids = sinon.stub().callsFake((opts) => { + done = opts.done; + addBid = opts.addBid; + return interceptResult; + }); + onCompletion = sinon.spy(); + interceptResult = {bids: [], bidRequest: {}}; + }); + + it('should pass to interceptBid an addBid that triggers onBid', () => { + const onBid = sinon.spy(); + bidderBidInterceptor(...interceptorArgs({cbs: {onBid}})); + const bid = {}; + addBid(bid); + expect(onBid.calledWith(sinon.match.same(bid))).to.be.true; + }); + + describe('with no remaining bids', () => { + it('should pass a done callback that triggers onCompletion', () => { + bidderBidInterceptor(...interceptorArgs()); + expect(onCompletion.calledOnce).to.be.false; + interceptBids.args[0][0].done(); + expect(onCompletion.calledOnce).to.be.true; + }); + + it('should not call next()', () => { + bidderBidInterceptor(...interceptorArgs()); + expect(next.called).to.be.false; + }); + }); + + describe('with remaining bids', () => { + const REMAINING_BIDS = [{id: 1}, {id: 2}]; + beforeEach(() => { + interceptResult = {bids: REMAINING_BIDS, bidRequest: {bids: REMAINING_BIDS}}; + }); + + it('should call next', () => { + const callbacks = { + onResponse: {}, + onRequest: {}, + onBid: {} + }; + const args = interceptorArgs({cbs: callbacks}); + const expectedNextArgs = [ + args[2], + interceptResult.bids, + interceptResult.bidRequest, + ...args.slice(5, args.length - 1), + ].map(sinon.match.same) + .concat([sinon.match({ + onResponse: sinon.match.same(callbacks.onResponse), + onRequest: sinon.match.same(callbacks.onRequest), + onBid: sinon.match.same(callbacks.onBid) + })]); + bidderBidInterceptor(...args); + expect(next.calledOnceWith(...expectedNextArgs)).to.be.true; + }); + + it('should trigger onCompletion once both interceptBids.done and next.cbs.onCompletion are called ', () => { + bidderBidInterceptor(...interceptorArgs()); + expect(onCompletion.calledOnce).to.be.false; + next.args[0][next.args[0].length - 1].onCompletion(); + expect(onCompletion.calledOnce).to.be.false; + done(); + expect(onCompletion.calledOnce).to.be.true; + }); + }); +}); + +describe('pbsBidInterceptor', () => { + const EMPTY_INT_RES = {bids: [], bidRequest: {bids: []}}; + let next, interceptBids, s2sBidRequest, bidRequests, ajax, onResponse, onError, onBid, interceptResults, + addBids, dones, reqIdx; + + beforeEach(() => { + reqIdx = 0; + [addBids, dones] = [[], []]; + next = sinon.spy(); + ajax = sinon.spy(); + onResponse = sinon.spy(); + onError = sinon.spy(); + onBid = sinon.spy(); + interceptBids = sinon.stub().callsFake((opts) => { + addBids.push(opts.addBid); + dones.push(opts.done); + return interceptResults[reqIdx++]; + }); + s2sBidRequest = {}; + bidRequests = [{bids: []}, {bids: []}]; + interceptResults = [EMPTY_INT_RES, EMPTY_INT_RES]; + }); + + function callInterceptor() { + return pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}); + } + + it('passes addBids that trigger onBid', () => { + callInterceptor(); + bidRequests.forEach((_, i) => { + const bid = {adUnitCode: i, prop: i}; + const bidRequest = {req: i}; + addBids[i](bid, bidRequest); + expect(onBid.calledWith({adUnit: i, bid: sinon.match(bid)})); + }); + }); + + describe('on no match', () => { + it('should not call next', () => { + callInterceptor(); + expect(next.called).to.be.false; + }); + + it('should pass done callbacks that trigger a dummy onResponse once they all run', () => { + callInterceptor(); + expect(onResponse.called).to.be.false; + bidRequests.forEach((_, i) => { + dones[i](); + expect(onResponse.called).to.equal(i === bidRequests.length - 1); + }); + expect(onResponse.calledWith(true, [])).to.be.true; + }); + }); + + describe('on match', () => { + let matchingBids; + beforeEach(() => { + matchingBids = [ + [{bidId: 1, matching: true}, {bidId: 2, matching: true}], + [], + [{bidId: 3, matching: true}] + ]; + interceptResults = matchingBids.map((bids) => ({bids, bidRequest: {bids}})); + s2sBidRequest = { + ad_units: [ + {bids: [{bid_id: 1, matching: true}, {bid_id: 3, matching: true}, {bid_id: 100}, {bid_id: 101}]}, + {bids: [{bid_id: 2, matching: true}, {bid_id: 110}, {bid_id: 111}]}, + {bids: [{bid_id: 120}]} + ] + }; + bidRequests = matchingBids.map((mBids, i) => [ + {bidId: 100 + (i * 10)}, + {bidId: 101 + (i * 10)}, + ...mBids + ]); + }); + + it('should call next', () => { + callInterceptor(); + expect(next.calledOnceWith( + sinon.match.any, + sinon.match.any, + ajax, + sinon.match({ + onError, + onBid + }) + )).to.be.true; + }); + + it('should filter out intercepted bids from s2sBidRequest', () => { + callInterceptor(); + const interceptedS2SReq = next.args[0][0]; + const allMatching = interceptedS2SReq.ad_units.every((u) => u.bids.length > 0 && u.bids.every((b) => b.matching)); + expect(allMatching).to.be.true; + }); + + it('should pass bidRequests as returned by interceptBids', () => { + callInterceptor(); + const passedBidReqs = next.args[0][1]; + interceptResults + .filter((r) => r.bids.length > 0) + .forEach(({bidRequest}, i) => { + expect(passedBidReqs[i]).to.equal(bidRequest); + }); + }); + + it('should pass an onResponse that triggers original onResponse only once all intercept dones are called', () => { + callInterceptor(); + const interceptedOnResponse = next.args[0][next.args[0].length - 1].onResponse; + expect(onResponse.called).to.be.false; + const responseArgs = ['dummy', 'args']; + interceptedOnResponse(...responseArgs); + expect(onResponse.called).to.be.false; + dones.forEach((f, i) => { + f(); + expect(onResponse.called).to.equal(i === dones.length - 1); + }); + expect(onResponse.calledOnceWith(...responseArgs)).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/deepintentDpesIdsystem_spec.js b/test/spec/modules/deepintentDpesIdsystem_spec.js index 7ea5553393c..4c26b118a98 100644 --- a/test/spec/modules/deepintentDpesIdsystem_spec.js +++ b/test/spec/modules/deepintentDpesIdsystem_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; import { storage, deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { config } from 'src/config.js'; diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index eaffca01e06..300e2104fae 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -410,6 +410,59 @@ describe('The DFP video support module', function () { expect(customParams).to.have.property('hb_cache_id', 'def'); }); + it('should keep the url protocol, host, and pathname when using url and params', function () { + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bid, + url: 'http://video.adserver.example/ads?sz=640x480&iu=/123/aduniturl&impl=s', + params: { + cust_params: { + hb_rand: 'random' + } + } + })); + + expect(url.protocol).to.equal('http:'); + expect(url.host).to.equal('video.adserver.example'); + expect(url.pathname).to.equal('/ads'); + }); + + it('should append to the url size param', () => { + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bid, + url: 'http://video.adserver.example/ads?sz=360x240&iu=/123/aduniturl&impl=s', + params: { + cust_params: { + hb_rand: 'random' + } + } + })); + + const queryObject = utils.parseQS(url.query); + expect(queryObject.sz).to.equal('360x240|640x480'); + }); + + it('should append to the existing url cust params', () => { + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bid, + url: 'http://video.adserver.example/ads?sz=360x240&iu=/123/aduniturl&impl=s&cust_params=existing_key%3Dexisting_value%26other_key%3Dother_value', + params: { + cust_params: { + hb_rand: 'random' + } + } + })); + + const queryObject = utils.parseQS(url.query); + const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); + + expect(customParams).to.have.property('existing_key', 'existing_value'); + expect(customParams).to.have.property('other_key', 'other_value'); + expect(customParams).to.have.property('hb_rand', 'random'); + }); + describe('adpod unit tests', function () { let amStub; let amGetAdUnitsStub; diff --git a/test/spec/modules/displayioBidAdapter_spec.js b/test/spec/modules/displayioBidAdapter_spec.js new file mode 100644 index 00000000000..dfeb67fb467 --- /dev/null +++ b/test/spec/modules/displayioBidAdapter_spec.js @@ -0,0 +1,239 @@ +import { expect } from 'chai' +import {spec} from 'modules/displayioBidAdapter.js' + +describe('Displayio adapter', function () { + const BIDDER = 'displayio' + const bidRequests = [{ + bidId: 'bidId_001', + bidder: BIDDER, + adUnitCode: 'adUnit_001', + auctionId: 'auctionId_001', + bidderRequestId: 'bidderRequestId_001', + mediaTypes: { + banner: { + sizes: [[320, 480]] + }, + video: { + sizes: [[360, 640]] + }, + }, + params: { + siteId: 1, + placementId: 1, + adsSrvDomain: 'adsSrvDomain', + cdnDomain: 'cdnDomain', + } + }] + const bidderRequest = { + refererInfo: { + referer: 'testprebid.com' + } + } + + describe('isBidRequestValid', function () { + it('should return true when required params found', function() { + const validBid = spec.isBidRequestValid(bidRequests[0]) + expect(validBid).to.be.true + }) + + const bidRequestsNoParams = [{ + bidder: BIDDER, + }] + it('should not validate without params', function () { + const request = spec.isBidRequestValid(bidRequestsNoParams, bidderRequest) + expect(request).to.be.false + }) + + const noSiteId = { + bidder: BIDDER, + params: { + placementId: 1, + adsSrvDomain: 'adsSrvDomain', + cdnDomain: 'cdnDomain', + } + } + it('should not validate without siteId', function() { + const invalidBid = spec.isBidRequestValid(noSiteId) + expect(invalidBid).to.be.false + }) + + const noPlacementId = { + bidder: BIDDER, + params: { + siteId: 1, + adsSrvDomain: 'adsSrvDomain', + cdnDomain: 'cdnDomain', + } + } + it('should not validate without placementId', function() { + const invalidBid = spec.isBidRequestValid(noPlacementId) + expect(invalidBid).to.be.false + }) + + const noAdsSrvDomain = { + bidder: BIDDER, + params: { + siteId: 1, + placementId: 1, + cdnDomain: 'cdnDomain', + } + } + it('should not validate without adsSrvDomain', function() { + const invalidBid = spec.isBidRequestValid(noAdsSrvDomain) + expect(invalidBid).to.be.false + }) + + const noCdnDomain = { + bidder: BIDDER, + params: { + siteId: 1, + placementId: 1, + adsSrvDomain: 'adsSrvDomain', + } + } + it('should not validate without cdnDomain', function() { + const invalidBid = spec.isBidRequestValid(noCdnDomain) + expect(invalidBid).to.be.false + }) + }) + + describe('buildRequests', function () { + it('should build request', function() { + const request = spec.buildRequests(bidRequests, bidderRequest) + expect(request).to.not.be.empty + }) + + it('sends bid request to the endpoint via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest) + expect(request[0].method).to.equal('POST') + }) + + it('sends all bid parameters', function () { + const request = spec.buildRequests(bidRequests, bidderRequest) + expect(request[0]).to.have.keys(['headers', 'data', 'method', 'url']) + }) + + it('should not crash when there is no media types', function () { + const bidRequestsNoMediaTypes = [{ + bidder: BIDDER, + params: { + siteId: 1, + placementId: 1, + adsSrvDomain: 'adsSrvDomain', + cdnDomain: 'cdnDomain', + } + }] + const request = spec.buildRequests(bidRequestsNoMediaTypes, bidderRequest) + expect(request[0]).to.have.keys(['headers', 'data', 'method', 'url']) + }) + }) + + describe('_getPayload', function () { + const payload = spec._getPayload(bidRequests[0], bidderRequest) + it('should not be empty', function() { + expect(payload).to.not.be.empty + }) + + it('should have userSession', function() { + expect(payload.userSession).to.be.a('string') + }) + + it('should have data object', function() { + expect(payload.data).to.be.a('object') + }) + + it('should have complianceData object', function() { + expect(payload.data.complianceData).to.be.a('object') + }) + + it('should have device object', function() { + expect(payload.data.device).to.be.a('object') + }) + + it('should have omidpn', function() { + expect(payload.data.omidpn).to.be.a('string') + }) + + it('should have integration', function() { + expect(payload.data.integration).to.be.a('string') + }) + + it('should have bidId', function() { + expect(payload.data.id).to.not.be.empty + }) + + it('should have action getPlacement', function() { + expect(payload.data.action).to.be.equal('getPlacement') + }) + + it('should have app parameter', function() { + expect(payload.data.app).to.be.a('number') + }) + + it('should have placement parameter', function() { + expect(payload.data.placement).to.be.a('number') + }) + }) + + describe('interpretResponse', function () { + const response = { + body: { + status: 'ok', + data: { + ads: [{ + ad: { + data: { + id: '001', + ecpm: 100, + w: 32, + h: 480, + markup: 'test ad' + } + }, + subtype: 'html' + }], + } + } + } + const serverRequest = { + data: { + data: { + id: 'id_001', + data: { + ref: 'testprebid.com' + } + } + } + } + + let ir = spec.interpretResponse(response, serverRequest) + + expect(ir.length).to.equal(1) + + ir = ir[0] + + it('should have requestId', function() { + expect(ir.requestId).to.be.a('string') + }) + + it('should have cpm', function() { + expect(ir.cpm).to.be.a('number') + }) + + it('should have width', function() { + expect(ir.width).to.be.a('number') + }) + + it('should have height', function() { + expect(ir.height).to.be.a('number') + }) + + it('should have creativeId', function() { + expect(ir.creativeId).to.be.a('number') + }) + + it('should have ad', function() { + expect(ir.ad).to.be.a('string') + }) + }) +}) diff --git a/test/spec/modules/distroscaleBidAdapter_spec.js b/test/spec/modules/distroscaleBidAdapter_spec.js new file mode 100644 index 00000000000..e5a78bbad11 --- /dev/null +++ b/test/spec/modules/distroscaleBidAdapter_spec.js @@ -0,0 +1,213 @@ +import { expect } from 'chai'; +import { spec } from 'modules/distroscaleBidAdapter.js'; +import * as utils from 'src/utils.js'; + +describe('distroscaleBidAdapter', function() { + const DSNAME = 'distroscale'; + + describe('isBidRequestValid', function() { + it('with no param', function() { + expect(spec.isBidRequestValid({ + bidder: DSNAME, + params: {} + })).to.equal(false); + }); + + it('with pubid param', function() { + expect(spec.isBidRequestValid({ + bidder: DSNAME, + params: { + pubid: '12345' + } + })).to.equal(true); + }); + + it('with pubid and zoneid params', function() { + expect(spec.isBidRequestValid({ + bidder: DSNAME, + params: { + pubid: '12345', + zoneid: '67890' + } + })).to.equal(true); + }); + }); + + describe('buildRequests', function() { + const CONSENT_STRING = 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw'; + const BID_REQUESTS = [{ + 'bidder': DSNAME, + 'params': { + 'pubid': '12345', + 'zoneid': '67890' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[970, 250], [300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'ca59932f-90f4-4dff-bed2-b90ffa2c2b6a', + 'sizes': [[970, 250], [300, 250]], + 'bidId': '20b96f0310083c', + 'bidderRequestId': '1dd684edba2006', + 'auctionId': '22ed3053-f76f-476c-a08e-dcda5862443d' + }]; + const BIDDER_REQUEST = { + 'bidderCode': DSNAME, + 'auctionId': '22ed3053-f76f-476c-a08e-dcda5862443d', + 'bidderRequestId': '1dd684edba2006', + 'refererInfo': { + 'referer': 'https://publisher.com/homepage.html', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'https://publisher.com/homepage.html' + ], + 'canonicalUrl': null + }, + 'gdprConsent': { + 'consentString': CONSENT_STRING, + 'gdprApplies': true + } + }; + + it('basic', function() { + const request = spec.buildRequests(BID_REQUESTS, BIDDER_REQUEST); + expect(request.method).to.equal('POST'); + expect(request.url).to.have.string('https://hb.jsrdn.com/hb?from=pbjs'); + expect(request.bidderRequest).to.deep.equal(BIDDER_REQUEST); + expect(request.data).to.exist; + expect(request.data.id).to.be.a('string').that.is.not.empty; + expect(request.data.at).to.equal(1); + expect(request.data.cur).to.deep.equal(['USD']); + expect(request.data.device).to.exist; + expect(request.data.site).to.exist; + expect(request.data.user).to.exist; + expect(request.data.imp).to.be.an('array').that.is.not.empty; + expect(request.data.imp[0]).to.exist; + expect(request.data.imp[0].id).to.equal(BID_REQUESTS[0].bidId); + expect(request.data.imp[0].tagid).to.equal(BID_REQUESTS[0].params.zoneid || ''); + expect(request.data.imp[0].secure).to.equal(1); + expect(request.data.imp[0].banner).to.exist; + expect(request.data.imp[0].banner.format).to.be.an('array').that.is.not.empty; + expect(request.data.imp[0].banner.format[0]).to.exist; + expect(request.data.imp[0].banner.format[0].w).to.equal(970); + expect(request.data.imp[0].banner.format[0].h).to.equal(250); + expect(request.data.imp[0].banner.w).to.equal(970); + expect(request.data.imp[0].banner.h).to.equal(250); + expect(request.data.imp[0].banner.pos).to.equal(0); + expect(request.data.imp[0].banner.topframe).to.be.oneOf([0, 1]); + expect(request.data.imp[0].ext).to.exist; + expect(request.data.imp[0].ext.pubid).to.equal(BID_REQUESTS[0].params.pubid); + expect(request.data.imp[0].ext.zoneid).to.equal(BID_REQUESTS[0].params.zoneid || ''); + }); + + it('gdpr', function() { + const request = spec.buildRequests(BID_REQUESTS, BIDDER_REQUEST); + expect(request.data).to.exist; + expect(request.data.regs).to.exist; + expect(request.data.regs.gdpr).to.equal(1); + expect(request.data.user).to.exist; + expect(request.data.user.consent).to.equal(CONSENT_STRING); + }); + }); + + describe('interpretResponse', function() { + const REQUEST = { + 'method': 'POST', + 'url': 'https://hb.jsrdn.com/hb?from=pbjs', + 'data': '{"id":"1648161050749","at":1,"cur":["USD"],"site":{"page":"https://publisher.com/homepage.html","domain":"publisher.com"},"device":{"ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36","js":1,"h":1200,"w":1920,"language":"en","dnt":0},"imp":[{"id":"20b96f0310083c","tagid":"67890","secure":1,"ext":{"pubid":"12345","zoneid":"67890"},"banner":{"pos":0,"w":970,"h":250,"topframe":1,"format":[{"w":970,"h":250}]}}],"user":{},"ext":{}}', + 'bidderRequest': { + 'bidderCode': DSNAME, + 'auctionId': '22ed3053-f76f-476c-a08e-dcda5862443d', + 'bidderRequestId': '1dd684edba2006', + 'bids': [{ + 'bidder': DSNAME, + 'params': { + 'pubid': '12345', + 'zoneid': '67890' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[970, 250], [300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'ca59932f-90f4-4dff-bed2-b90ffa2c2b6a', + 'sizes': [[970, 250], [300, 250]], + 'bidId': '20b96f0310083c', + 'bidderRequestId': '1dd684edba2006', + 'auctionId': '22ed3053-f76f-476c-a08e-dcda5862443d' + }], + 'refererInfo': { + 'referer': 'https://publisher.com/homepage.html', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'https://publisher.com/homepage.html' + ], + 'canonicalUrl': null + } + } + }; + const RESPONSE = { + 'body': { + 'id': '1648161050749', + 'seatbid': [{ + 'bid': [{ + 'id': '212f1c7b-378b-47e4-8294-ac38658b33f6_0', + 'impid': '20b96f0310083c', + 'price': 0.1, + 'w': 970, + 'h': 250, + 'adm': "
" + }] + }], + 'cur': 'USD' + }, + 'headers': {} + }; + const SAMPLE_PARSED = [{ + 'requestId': '20b96f0310083c', + 'cpm': 0.1, + 'currency': 'USD', + 'width': 970, + 'height': 250, + 'creativeId': 'bbbbbbbb-648d-4e03-a5e2-7198bcd07cfe', + 'netRevenue': true, + 'ttl': 300, + 'ad': "
", + 'meta': { + 'advertiserDomains': [] + } + }]; + + it('valid bid response for banner ad', function() { + const result = spec.interpretResponse(RESPONSE, REQUEST); + const bid = RESPONSE.body.seatbid[0].bid[0]; + expect(result).to.have.lengthOf(1); + expect(result[0].requestId).to.equal(bid.impid); + expect(result[0].cpm).to.equal(Number(bid.price)); + expect(result[0].currency).to.equal(RESPONSE.body.cur); + expect(result[0].width).to.equal(Number(bid.w)); + expect(result[0].height).to.equal(Number(bid.h)); + expect(result[0].creativeId).to.be.a('string').that.is.not.empty; + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + expect(result[0].ad).to.equal(bid.adm); + expect(result[0].meta).to.exist; + expect(result[0].meta.advertiserDomains).to.exist; + }); + + it('advertiserDomains is included when sent by server', function() { + const ADOMAIN = ['advertiser_adomain']; + let RESPONSE_CLONE = utils.deepClone(RESPONSE); + RESPONSE_CLONE.body.seatbid[0].bid[0].adomain = utils.deepClone(ADOMAIN); ; + let result = spec.interpretResponse(RESPONSE_CLONE, REQUEST); + expect(result[0].meta.advertiserDomains).to.deep.equal(ADOMAIN); + }); + }); +}); diff --git a/test/spec/modules/docereeBidAdapter_spec.js b/test/spec/modules/docereeBidAdapter_spec.js index efff2efa319..dadbb56b0c0 100644 --- a/test/spec/modules/docereeBidAdapter_spec.js +++ b/test/spec/modules/docereeBidAdapter_spec.js @@ -31,6 +31,8 @@ describe('BidlabBidAdapter', function () { bidder: 'doceree', params: { placementId: 'DOC_7jm9j5eqkl0xvc5w', + gdpr: '1', + gdprConsent: 'CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g' } }; @@ -44,6 +46,12 @@ describe('BidlabBidAdapter', function () { }); }); + describe('isGdprConsentPresent', function () { + it('Should return true if gdpr consent is present', function () { + expect(spec.isGdprConsentPresent(bid)).to.be.true; + }); + }); + describe('buildRequests', function () { let serverRequest = spec.buildRequests([bid]); serverRequest = serverRequest[0] @@ -56,7 +64,7 @@ describe('BidlabBidAdapter', function () { expect(serverRequest.method).to.equal('GET'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://bidder.doceree.com/v1/adrequest?id=DOC_7jm9j5eqkl0xvc5w&pubRequestedURL=undefined&loggedInUser=JTdCJTIyZ2VuZGVyJTIyJTNBJTIyJTIyJTJDJTIyZW1haWwlMjIlM0ElMjIlMjIlMkMlMjJoYXNoZWRFbWFpbCUyMiUzQSUyMiUyMiUyQyUyMmZpcnN0TmFtZSUyMiUzQSUyMiUyMiUyQyUyMmxhc3ROYW1lJTIyJTNBJTIyJTIyJTJDJTIybnBpJTIyJTNBJTIyJTIyJTJDJTIyaGFzaGVkTlBJJTIyJTNBJTIyJTIyJTJDJTIyY2l0eSUyMiUzQSUyMiUyMiUyQyUyMnppcENvZGUlMjIlM0ElMjIlMjIlMkMlMjJzcGVjaWFsaXphdGlvbiUyMiUzQSUyMiUyMiU3RA%3D%3D&prebidjs=true&requestId=testing&'); + expect(serverRequest.url).to.equal('https://bidder.doceree.com/v1/adrequest?id=DOC_7jm9j5eqkl0xvc5w&pubRequestedURL=undefined&loggedInUser=JTdCJTIyZ2VuZGVyJTIyJTNBJTIyJTIyJTJDJTIyZW1haWwlMjIlM0ElMjIlMjIlMkMlMjJoYXNoZWRFbWFpbCUyMiUzQSUyMiUyMiUyQyUyMmZpcnN0TmFtZSUyMiUzQSUyMiUyMiUyQyUyMmxhc3ROYW1lJTIyJTNBJTIyJTIyJTJDJTIybnBpJTIyJTNBJTIyJTIyJTJDJTIyaGFzaGVkTlBJJTIyJTNBJTIyJTIyJTJDJTIyY2l0eSUyMiUzQSUyMiUyMiUyQyUyMnppcENvZGUlMjIlM0ElMjIlMjIlMkMlMjJzcGVjaWFsaXphdGlvbiUyMiUzQSUyMiUyMiU3RA%3D%3D&prebidjs=true&requestId=testing&gdpr=1&gdpr_consent=CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g&'); }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index 09f40895ec9..2869385d7e7 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -62,6 +62,7 @@ describe('dspxAdapter', function () { 'bidId': '30b31c1838de1e1', 'bidderRequestId': '22edbae2733bf61', 'auctionId': '1d1a030790a475', + 'adUnitCode': 'testDiv1', 'userId': { 'netId': '123', 'uid2': '456' @@ -98,7 +99,8 @@ describe('dspxAdapter', function () { ], 'bidId': '30b31c1838de1e3', 'bidderRequestId': '22edbae2733bf69', - 'auctionId': '1d1a030790a477' + 'auctionId': '1d1a030790a477', + 'adUnitCode': 'testDiv2' }, { 'bidder': 'dspx', @@ -120,13 +122,15 @@ describe('dspxAdapter', function () { 'bidId': '30b31c1838de1e4', 'bidderRequestId': '22edbae2733bf67', - 'auctionId': '1d1a030790a478' + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv3' }, { 'bidder': 'dspx', 'params': { 'placement': '101', - 'devMode': true + 'devMode': true, + 'vastFormat': 'vast4' }, 'mediaTypes': { 'video': { @@ -136,7 +140,8 @@ describe('dspxAdapter', function () { }, 'bidId': '30b31c1838de1e41', 'bidderRequestId': '22edbae2733bf67', - 'auctionId': '1d1a030790a478' + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv4' } ]; @@ -157,16 +162,16 @@ describe('dspxAdapter', function () { it('sends bid request to our endpoint via GET', function () { expect(request1.method).to.equal('GET'); expect(request1.url).to.equal(ENDPOINT_URL); - let data = request1.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&did_netid=123&did_uid2=456'); + let data = request1.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&did_netid=123&did_uid2=456&auctionId=1d1a030790a475&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); }); var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; it('sends bid request to our DEV endpoint via GET', function () { expect(request2.method).to.equal('GET'); expect(request2.url).to.equal(ENDPOINT_URL_DEV); - let data = request2.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e2&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&prebidDevMode=1'); + let data = request2.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e2&pbver=test&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&prebidDevMode=1&auctionId=1d1a030790a476&media_types%5Bbanner%5D=300x250'); }); // Without gdprConsent @@ -179,23 +184,23 @@ describe('dspxAdapter', function () { it('sends bid request without gdprConsent to our endpoint via GET', function () { expect(request3.method).to.equal('GET'); expect(request3.url).to.equal(ENDPOINT_URL); - let data = request3.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e3&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&bcat=IAB2%2CIAB4&dvt=desktop'); + let data = request3.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e3&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&bcat=IAB2%2CIAB4&dvt=desktop&auctionId=1d1a030790a477&pbcode=testDiv2&media_types%5Bbanner%5D=300x250'); }); var request4 = spec.buildRequests([bidRequests[3]], bidderRequestWithoutGdpr)[0]; it('sends bid request without gdprConsent to our DEV endpoint via GET', function () { expect(request4.method).to.equal('GET'); expect(request4.url).to.equal(ENDPOINT_URL_DEV); - let data = request4.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&prebidDevMode=1'); + let data = request4.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250'); }); var request5 = spec.buildRequests([bidRequests[4]], bidderRequestWithoutGdpr)[0]; - it('sends bid video request to our rads endpoint via GET', function () { + it('sends bid video request to our endpoint via GET', function () { expect(request5.method).to.equal('GET'); - let data = request5.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); - expect(data).to.equal('_f=vast2&alternative=prebid_js&inventory_item_id=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&prebidDevMode=1'); + let data = request5.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&vf=vast4&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv4&media_types%5Bvideo%5D=640x480'); }); }); diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js new file mode 100644 index 00000000000..1f60edda0ef --- /dev/null +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -0,0 +1,242 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/e_volutionBidAdapter.js'; + +describe('EvolutionTechBidAdapter', function () { + let bid = { + bidId: '23fhj33i987f', + bidder: 'e_volution', + params: { + placementId: 0 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and placementId 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]); + 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://service.e-volution.ai/?c=o&m=multi'); + }); + 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'); + 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'); + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'bidfloor'); + expect(placement.placementId).to.equal(0); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal('banner'); + }); + 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: '23fhj33i987f', + 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('23fhj33i987f'); + 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'); + }); + 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: {} + }] + }; + 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'); + }); + 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: {} + }] + }; + 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'); + }); + 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 () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + if (spec.noSync) { + expect(userSync).to.be.equal(false); + } else { + 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('image'); + expect(userSync[0].url).to.be.equal('https://service.e-volution.ai/?c=o&m=sync'); + } + }); + }); +}); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 1277486a154..9edda3c9e95 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -240,9 +240,9 @@ describe('eids array generation for known sub-modules', function() { }); }); - it('haloId', function() { + it('hadronId', function() { const userId = { - haloId: 'some-random-id-value' + hadronId: 'some-random-id-value' }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); @@ -348,6 +348,21 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + + it('qid', function() { + const userId = { + qid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adquery.io', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); }); describe('Negative case', function() { it('eids array generation for UN-known sub-module', function() { diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index 5831a8506c1..043a8a3709e 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -432,6 +432,16 @@ describe('emx_digital Adapter', function () { }); }); + it('should add gpid to request if present', () => { + const gpid = '/12345/my-gpt-tag-0'; + let bid = utils.deepClone(bidderRequest.bids[0]); + bid.ortb2Imp = { ext: { data: { adserver: { adslot: gpid } } } }; + bid.ortb2Imp = { ext: { data: { pbadslot: gpid } } }; + let requestWithGPID = spec.buildRequests([bid], bidderRequest); + requestWithGPID = JSON.parse(requestWithGPID.data); + expect(requestWithGPID.imp[0].ext.gpid).to.exist.and.equal(gpid); + }); + it('should add UID 2.0 to request', () => { const uid2 = { id: '456' }; const bidRequestWithUID = utils.deepClone(bidderRequest); diff --git a/test/spec/modules/engageyaBidAdapter_spec.js b/test/spec/modules/engageyaBidAdapter_spec.js index ae22948994b..283f0148402 100644 --- a/test/spec/modules/engageyaBidAdapter_spec.js +++ b/test/spec/modules/engageyaBidAdapter_spec.js @@ -4,18 +4,7 @@ import * as utils from 'src/utils.js'; const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json'; -export const _getUrlVars = function (url) { - var hash; - var myJson = {}; - var hashes = url.slice(url.indexOf('?') + 1).split('&'); - for (var i = 0; i < hashes.length; i++) { - hash = hashes[i].split('='); - myJson[hash[0]] = hash[1]; - } - return myJson; -} - -describe('engageya adapter', function () { +describe('Engageya adapter', function () { let bidRequests; let nativeBidRequests; @@ -55,40 +44,159 @@ describe('engageya adapter', function () { } ] }) + + describe('isValidSize', function () { + const bid = { + bidder: 'engageya', + params: { + widgetId: 85610, + websiteId: 91140, + pageUrl: '[PAGE_URL]' + } + }; + it('Exact match, 236X202', function () { + bid.sizes = [[236, 202]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + it('Exact match, 300X200', function () { + bid.sizes = [[300, 200]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + it('Exact match, 600X500', function () { + bid.sizes = [[600, 500]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + + it('Ratio max limit, 236X212', function () { + bid.sizes = [[236, 212]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + it('Ratio max limit, 300X209', function () { + bid.sizes = [[300, 209]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + it('Ratio max limit, 600X524', function () { + bid.sizes = [[600, 524]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + + it('Ratio & width max limit, 248X222', function () { + bid.sizes = [[248, 222]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + it('Ratio & width max limit, 315X220', function () { + bid.sizes = [[315, 220]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + it('Ratio & width max limit, 631X551', function () { + bid.sizes = [[631, 551]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + + it('Width too big, 320X285', function () { + bid.sizes = [[320, 285]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.false; + }); + it('Width too big, 316X220', function () { + bid.sizes = [[316, 220]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.false; + }); + it('Width too big, 632X551', function () { + bid.sizes = [[632, 551]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.false; + }); + + it('Ratio too big, 600X525', function () { + bid.sizes = [[600, 525]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.false; + }); + + it('Ratio min limit, 236X192', function () { + bid.sizes = [[236, 192]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + it('Ratio min limit, 300X190', function () { + bid.sizes = [[300, 190]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + it('Ratio min limit, 600X475', function () { + bid.sizes = [[600, 475]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + + it('Ratio too small, 600X474', function () { + bid.sizes = [[600, 474]]; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.be.false; + }); + }) + describe('isBidRequestValid', function () { - it('valid bid case', function () { + it('Valid bid case', function () { let validBid = { bidder: 'engageya', params: { widgetId: 85610, websiteId: 91140, pageUrl: '[PAGE_URL]' - } + }, + sizes: [[300, 250]] } let isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); + expect(isValid).to.be.true; }); - it('invalid bid case: widgetId and websiteId is not passed', function () { + it('Invalid bid case: widgetId and websiteId is not passed', function () { let validBid = { bidder: 'engageya', params: {} } let isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); + expect(isValid).to.be.false; }) - it('invalid bid case: widget id must be number', function () { + it('Invalid bid case: widget id must be number', function () { let invalidBid = { bidder: 'engageya', params: { widgetId: '157746a', websiteId: 91140, pageUrl: '[PAGE_URL]' - } + }, + sizes: [[300, 250]] } let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(false); + expect(isValid).to.be.false; + }) + + it('Invalid bid case: unsupported sizes', function () { + let invalidBid = { + bidder: 'engageya', + params: { + widgetId: '157746a', + websiteId: 91140, + pageUrl: '[PAGE_URL]' + }, + sizes: [[250, 250]] + } + let isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.be.false; }) }) @@ -113,36 +221,30 @@ describe('engageya adapter', function () { it('Request params check', function () { let request = spec.buildRequests(bidRequests)[0]; - const data = _getUrlVars(request.url) - expect(parseInt(data.wid)).to.exist.and.to.equal(bidRequests[0].params.widgetId); - expect(parseInt(data.webid)).to.exist.and.to.equal(bidRequests[0].params.websiteId); - }) - }) - - describe('interpretResponse', function () { - it('should return empty array if no response', function () { - const result = spec.interpretResponse({}, []) - expect(result).to.be.an('array').that.is.empty + const urlParams = new URL(request.url).searchParams; + expect(parseInt(urlParams.get('wid'))).to.exist.and.to.equal(bidRequests[0].params.widgetId); + expect(parseInt(urlParams.get('webid'))).to.exist.and.to.equal(bidRequests[0].params.websiteId); }); - it('should return empty array if no valid bids', function () { - let response = { - recs: [], - imageWidth: 300, - imageHeight: 250, - ireqId: '1d236f7890b', - pbtypeId: 2 - }; - let request = spec.buildRequests(bidRequests)[0]; - const result = spec.interpretResponse({ body: response }, request) - expect(result).to.be.an('array').that.is.empty + it('Request pageUrl - use param', function () { + const pageUrl = 'https://url.test'; + bidRequests[0].params.pageUrl = pageUrl; + const request = spec.buildRequests(bidRequests)[0]; + const urlParams = new URL(request.url).searchParams; + expect(urlParams.get('url')).to.exist.and.to.equal(pageUrl); }); + }) - it('should interpret native response', function () { - let serverResponse = { + describe('interpretResponse', function () { + let nativeResponse; + let bannerResponse; + + beforeEach(() => { + const recsResponse = { recs: [ { - ecpm: 0.0920, + ecpm: 9.20, + pecpm: 0.0520, postId: '', thumbnail_path: 'https://engageya.live/wp-content/uploads/2019/05/images.png', domain: 'domain.test', @@ -159,8 +261,80 @@ describe('engageya adapter', function () { imageWidth: 300, imageHeight: 250, ireqId: '1d236f7890b', - pbtypeId: 1 + viewPxl: '//view.pixel', + }; + + nativeResponse = { + ...recsResponse, + pbtypeId: 1, + } + + bannerResponse = { + ...recsResponse, + pbtypeId: 2, + widget: { + additionalData: '{"css":".eng_tag_ttl{display:block!important}"}' + }, + } + }) + + it('should return empty array if no response', function () { + const result = spec.interpretResponse({}, []) + expect(result).to.be.an('array').that.is.empty + }); + + it('should return empty array if no valid bids', function () { + let response = { + recs: [], + imageWidth: 300, + imageHeight: 250, + ireqId: '1d236f7890b', + pbtypeId: 2, + viewPxl: '//view.pixel', }; + let request = spec.buildRequests(bidRequests)[0]; + const result = spec.interpretResponse({ body: response }, request) + expect(result).to.be.an('array').that.is.empty + }); + + it('should interpret native response', function () { + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + native: { + title: 'Test title', + body: '', + image: { + url: 'https://engageya.live/wp-content/uploads/2019/05/images.png', + width: 300, + height: 250 + }, + privacyLink: '', + clickUrl: '//click.test', + displayUrl: '//url.test', + cta: '', + sponsoredBy: 'Test displayName', + impressionTrackers: ['//impression.test', '//view.test', '//view.pixel'], + }, + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: nativeResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret native response - without pecpm', function () { + delete nativeResponse.recs[0].pecpm; let expectedResult = [ { requestId: '1d236f7890b', @@ -187,81 +361,76 @@ describe('engageya adapter', function () { displayUrl: '//url.test', cta: '', sponsoredBy: 'Test displayName', - impressionTrackers: ['//impression.test', '//view.test'], + impressionTrackers: ['//impression.test', '//view.test', '//view.pixel'], }, } ]; let request = spec.buildRequests(bidRequests)[0]; - let result = spec.interpretResponse({ body: serverResponse }, request); + let result = spec.interpretResponse({ body: nativeResponse }, request); expect(result).to.deep.equal(expectedResult); }); - it('should interpret display response', function () { - let serverResponse = { - recs: [ - { - ecpm: 0.0920, - postId: '', - thumbnail_path: 'https://engageya.live/wp-content/uploads/2019/05/images.png', - domain: 'domain.test', + it('should interpret native response - without trackers', function () { + delete nativeResponse.recs[0].trackers; + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + native: { title: 'Test title', + body: '', + image: { + url: 'https://engageya.live/wp-content/uploads/2019/05/images.png', + width: 300, + height: 250 + }, + privacyLink: '', clickUrl: '//click.test', - url: '//url.test', - displayName: 'Test displayName', - trackers: { - impressionPixels: ['//impression.test'], - viewPixels: ['//view.test'], - } - } - ], - imageWidth: 300, - imageHeight: 250, - ireqId: '1d236f7890b', - pbtypeId: 2, - widget: { - additionalData: '{"css":".eng_tag_ttl{display:block!important}"}' + displayUrl: '//url.test', + cta: '', + sponsoredBy: 'Test displayName', + impressionTrackers: ['//view.pixel'], + }, } - }; + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: nativeResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret display response', function () { let expectedResult = [ { requestId: '1d236f7890b', - cpm: 0.0920, + cpm: 0.0520, width: 300, height: 250, - netRevenue: false, + netRevenue: true, currency: 'USD', creativeId: '', ttl: 360, meta: { advertiserDomains: ['domain.test'] }, - ad: ``, + ad: ``, } ]; let request = spec.buildRequests(bidRequests)[0]; - let result = spec.interpretResponse({ body: serverResponse }, request); + let result = spec.interpretResponse({ body: bannerResponse }, request); expect(result).to.deep.equal(expectedResult); }); - it('should interpret display response without title', function () { - let serverResponse = { - recs: [ - { - ecpm: 0.0920, - postId: '', - thumbnail_path: 'https://engageya.live/wp-content/uploads/2019/05/images.png', - domain: 'domain.test', - title: ' ', - clickUrl: '//click.test', - url: '//url.test', - displayName: 'Test displayName', - } - ], - imageWidth: 300, - imageHeight: 250, - ireqId: '1d236f7890b', - pbtypeId: 2, - }; + it('should interpret display response - without pecpm', function () { + delete bannerResponse.recs[0].pecpm; let expectedResult = [ { requestId: '1d236f7890b', @@ -275,11 +444,80 @@ describe('engageya adapter', function () { meta: { advertiserDomains: ['domain.test'] }, - ad: `
`, + ad: ``, + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: bannerResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret display response - without title', function () { + bannerResponse.recs[0].title = ' '; + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + ad: `
`, + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: bannerResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret display response - without widget additional data', function () { + bannerResponse.widget.additionalData = null; + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + ad: ``, + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: bannerResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret display response - without trackers', function () { + bannerResponse.recs[0].trackers = null; + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + ad: ``, } ]; let request = spec.buildRequests(bidRequests)[0]; - let result = spec.interpretResponse({ body: serverResponse }, request); + let result = spec.interpretResponse({ body: bannerResponse }, request); expect(result).to.deep.equal(expectedResult); }); }) diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js index 255d116a0ff..872518f2f27 100644 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ import eplAnalyticsAdapter from 'modules/eplanningAnalyticsAdapter.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {includes} from 'src/polyfill.js'; import { expect } from 'chai'; import { parseUrl } from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 6b75af0d55d..2739654eb5d 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -171,6 +171,21 @@ describe('FeedAdAdapter', function () { expect(result.data.bids).to.be.lengthOf(1); expect(result.data.bids[0]).to.deep.equal(bid); }); + it('should pass through additional bid parameters', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id', another: 'parameter', more: 'parameters'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.bids).to.be.lengthOf(1); + expect(result.data.bids[0].params.another).to.equal('parameter'); + expect(result.data.bids[0].params.more).to.equal('parameters'); + }); it('should detect empty media types', function () { let bid = { code: 'feedad', diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index 54d1a7e7976..407ceb305a2 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ import fntzAnalyticsAdapter from 'modules/fintezaAnalyticsAdapter.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {includes} from 'src/polyfill.js'; import { expect } from 'chai'; import { parseUrl } from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js index 70666532442..8ef99727ce7 100644 --- a/test/spec/modules/fluctBidAdapter_spec.js +++ b/test/spec/modules/fluctBidAdapter_spec.js @@ -83,6 +83,98 @@ describe('fluctAdapter', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.url).to.equal('https://hb.adingo.jp/prebid?dfpUnitCode=%2F100000%2Funit_code&tagId=10000%3A100000001&groupId=1000000002'); }); + + it('includes data.user.eids = [] by default', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.user.eids).to.eql([]); + }); + + it('includes no data.params.kv by default', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.params.kv).to.eql(undefined); + }); + + it('includes filtered user.eids if any exists', function () { + const bidRequests2 = bidRequests.map( + (bidReq) => Object.assign(bidReq, { + userIdAsEids: [ + { + source: 'foobar.com', + uids: [ + { id: 'foobar-id' } + ], + }, + { + source: 'adserver.org', + uids: [ + { id: 'tdid' } + ], + }, + { + source: 'criteo.com', + uids: [ + { id: 'criteo-id' } + ], + }, + { + source: 'intimatemerger.com', + uids: [ + { id: 'imuid' } + ], + }, + { + source: 'liveramp.com', + uids: [ + { id: 'idl-env' } + ], + }, + ], + }) + ); + const request = spec.buildRequests(bidRequests2, bidderRequest)[0]; + expect(request.data.user.eids).to.eql([ + { + source: 'adserver.org', + uids: [ + { id: 'tdid' } + ], + }, + { + source: 'criteo.com', + uids: [ + { id: 'criteo-id' } + ], + }, + { + source: 'intimatemerger.com', + uids: [ + { id: 'imuid' } + ], + }, + { + source: 'liveramp.com', + uids: [ + { id: 'idl-env' } + ], + }, + ]); + }); + + it('includes data.params.kv if any exists', function () { + const bidRequests2 = bidRequests.map( + (bidReq) => Object.assign(bidReq, { + params: { + kv: { + imsids: ['imsid1', 'imsid2'] + } + } + }) + ); + const request = spec.buildRequests(bidRequests2, bidderRequest)[0]; + expect(request.data.params.kv).to.eql({ + imsids: ['imsid1', 'imsid2'] + }); + }); }); describe('interpretResponse', function() { diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index a5b4bd2a03f..81f4ecec074 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -98,6 +98,19 @@ describe('freewheelSSP BidAdapter Test', () => { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'example.com', + 'sid': '0', + 'hp': 1, + 'rid': 'bidrequestid', + 'domain': 'example.com' + } + ] + } } ]; @@ -112,6 +125,12 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.playerSize).to.equal('300x600'); }); + 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) + }); + it('sends bid request to ENDPOINT via GET', () => { const request = spec.buildRequests(bidRequests); expect(request[0].url).to.contain(ENDPOINT); diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js new file mode 100644 index 00000000000..69d66d75bb1 --- /dev/null +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -0,0 +1,238 @@ +import { ftrackIdSubmodule } from 'modules/ftrackIdSystem.js'; +import * as utils from 'src/utils.js'; +import { uspDataHandler } from 'src/adapterManager.js'; +let expect = require('chai').expect; + +let server; + +let configMock = { + name: 'ftrack', + params: { + url: 'https://d9.flashtalking.com/d9core' + }, + storage: { + name: 'ftrackId', + type: 'html5', + expires: 90, + refreshInSeconds: 8 * 3600 + }, + debug: true +}; + +let consentDataMock = { + gdprApplies: 0, + consentString: '' +}; + +describe('FTRACK ID System', () => { + describe(`Global Module Rules`, () => { + it(`should not use the "PREBID_GLOBAL" variable nor otherwise obtain a pointer to the global PBJS object`, () => { + expect((/PREBID_GLOBAL/gi).test(JSON.stringify(ftrackIdSubmodule))).to.not.be.ok; + }); + }); + + describe('ftrackIdSubmodule.isConfigOk():', () => { + let logWarnStub; + let logErrorStub; + + beforeEach(() => { + logWarnStub = sinon.stub(utils, 'logWarn'); + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(() => { + logWarnStub.restore(); + logErrorStub.restore(); + }); + + it(`should be rejected if 'config.storage' property is missing`, () => { + let configMock1 = JSON.parse(JSON.stringify(configMock)); + delete configMock1.storage; + delete configMock1.params; + + ftrackIdSubmodule.isConfigOk(configMock1); + expect(logErrorStub.args[0][0]).to.equal(`FTRACK - config.storage required to be set.`); + }); + + it(`should be rejected if 'config.storage.name' property is missing`, () => { + let configMock1 = JSON.parse(JSON.stringify(configMock)); + delete configMock1.storage.name; + + ftrackIdSubmodule.isConfigOk(configMock1); + expect(logErrorStub.args[0][0]).to.equal(`FTRACK - config.storage required to be set.`); + }); + + it(`should be rejected if 'config.storage.name' is not 'ftrackId'`, () => { + let configMock1 = JSON.parse(JSON.stringify(configMock)); + configMock1.storage.name = 'not-ftrack'; + + ftrackIdSubmodule.isConfigOk(configMock1); + expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.storage.name recommended to be "ftrackId".`); + }); + + it(`should be rejected if 'congig.storage.type' property is missing`, () => { + let configMock1 = JSON.parse(JSON.stringify(configMock)); + delete configMock1.storage.type; + + ftrackIdSubmodule.isConfigOk(configMock1); + expect(logErrorStub.args[0][0]).to.equal(`FTRACK - config.storage required to be set.`); + }); + + it(`should be rejected if 'config.storage.type' is not 'html5'`, () => { + let configMock1 = JSON.parse(JSON.stringify(configMock)); + configMock1.storage.type = 'not-html5'; + + ftrackIdSubmodule.isConfigOk(configMock1); + expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.storage.type recommended to be "html5".`); + }); + + it(`should be rejected if 'config.params.url' does not exist`, () => { + let configMock1 = JSON.parse(JSON.stringify(configMock)); + delete configMock1.params.url; + + ftrackIdSubmodule.isConfigOk(configMock1); + expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run. Url should be "https://d9.flashtalking.com/d9core".`); + }); + + it(`should be rejected if 'storage.param.url' does not exist or is not 'https://d9.flashtalking.com/d9core'`, () => { + let configMock1 = JSON.parse(JSON.stringify(configMock)); + configMock1.params.url = 'https://d9.NOT.flashtalking.com/d9core'; + + ftrackIdSubmodule.isConfigOk(configMock1); + expect(logWarnStub.args[0][0]).to.equal(`FTRACK - config.params.url is required for ftrack to run. Url should be "https://d9.flashtalking.com/d9core".`); + }); + }); + + describe(`ftrackIdSubmodule.isThereConsent():`, () => { + let uspDataHandlerStub; + beforeEach(() => { + uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + }); + + afterEach(() => { + uspDataHandlerStub.restore(); + }); + + describe(`returns 'false' if:`, () => { + it(`GDPR: if gdprApplies is truthy`, () => { + expect(ftrackIdSubmodule.isThereConsent({gdprApplies: 1})).to.not.be.ok; + expect(ftrackIdSubmodule.isThereConsent({gdprApplies: true})).to.not.be.ok; + }); + + it(`US_PRIVACY version 1: if 'Opt Out Sale' is 'Y'`, () => { + uspDataHandlerStub.returns('1YYY'); + expect(ftrackIdSubmodule.isThereConsent({})).to.not.be.ok; + }); + }); + + describe(`returns 'true' if`, () => { + it(`GDPR: if gdprApplies is undefined, false or 0`, () => { + expect(ftrackIdSubmodule.isThereConsent({gdprApplies: 0})).to.be.ok; + expect(ftrackIdSubmodule.isThereConsent({gdprApplies: false})).to.be.ok; + expect(ftrackIdSubmodule.isThereConsent({gdprApplies: null})).to.be.ok; + expect(ftrackIdSubmodule.isThereConsent({})).to.be.ok; + }); + + it(`US_PRIVACY version 1: if 'Opt Out Sale' is not 'Y' ('N','-')`, () => { + uspDataHandlerStub.returns('1NNN'); + expect(ftrackIdSubmodule.isThereConsent(null)).to.be.ok; + + uspDataHandlerStub.returns('1---'); + expect(ftrackIdSubmodule.isThereConsent(null)).to.be.ok; + }); + }); + }); + + describe('getId() method', () => { + it(`should be using the StorageManager to set cookies or localstorage, as opposed to doing it directly`, () => { + expect((/localStorage/gi).test(JSON.stringify(ftrackIdSubmodule))).to.not.be.ok; + expect((/cookie/gi).test(JSON.stringify(ftrackIdSubmodule))).to.not.be.ok; + }); + + it(`should be the only method that gets a new ID aka hits the D9 endpoint`, () => { + let appendChildStub = sinon.stub(window.document.body, 'appendChild'); + + ftrackIdSubmodule.getId(configMock, null, null).callback(); + expect(window.document.body.appendChild.called).to.be.ok; + let actualScriptTag = window.document.body.appendChild.args[0][0]; + expect(actualScriptTag.tagName.toLowerCase()).to.equal('script'); + expect(actualScriptTag.getAttribute('src')).to.equal('https://d9.flashtalking.com/d9core'); + appendChildStub.resetHistory(); + + ftrackIdSubmodule.decode('value', configMock); + expect(window.document.body.appendChild.called).to.not.be.ok; + expect(window.document.body.appendChild.args).to.deep.equal([]); + appendChildStub.resetHistory(); + + ftrackIdSubmodule.extendId(configMock, null, {cache: {id: ''}}); + expect(window.document.body.appendChild.called).to.not.be.ok; + expect(window.document.body.appendChild.args).to.deep.equal([]); + + appendChildStub.restore(); + }); + + it(`should populate localstorage and return the IDS (end-to-end test)`, () => { + let ftrackId, + ftrackIdExp, + forceCallback = false; + + // Confirm that our item is not in localStorage yet + expect(window.localStorage.getItem('ftrack-rtd')).to.not.be.ok; + expect(window.localStorage.getItem('ftrack-rtd_exp')).to.not.be.ok; + + ftrackIdSubmodule.getId(configMock, consentDataMock, null).callback(); + return new Promise(function(resolve, reject) { + window.testTimer = function () { + // Sinon fake server is NOT changing the readyState to 4, so instead + // we are forcing the callback to run and just passing in the expected Object + if (!forceCallback && window.hasOwnProperty('D9r')) { + window.D9r.callback({ 'DeviceID': [''], 'SingleDeviceID': [''] }); + forceCallback = true; + } + + ftrackId = window.localStorage.getItem('ftrackId'); + ftrackIdExp = window.localStorage.getItem('ftrackId_exp'); + + if (!!ftrackId && !!ftrackIdExp) { + expect(window.localStorage.getItem('ftrackId')).to.be.ok; + expect(window.localStorage.getItem('ftrackId_exp')).to.be.ok; + resolve(); + } else { + window.setTimeout(window.testTimer, 25); + } + }; + window.testTimer(); + }); + }); + }); + + describe(`decode() method`, () => { + it(`should respond with an object with the key 'ftrackId'`, () => { + expect(ftrackIdSubmodule.decode('value', configMock)).to.deep.equal({ftrackId: 'value'}); + }); + + it(`should not be making requests to retrieve a new ID, it should just be decoding a response`, () => { + server = sinon.createFakeServer(); + ftrackIdSubmodule.decode('value', configMock); + + expect(server.requests).to.have.length(0); + + server.restore(); + }) + }); + + describe(`extendId() method`, () => { + it(`should not be making requests to retrieve a new ID, it should just be adding additional data to the id object`, () => { + server = sinon.createFakeServer(); + ftrackIdSubmodule.extendId(configMock, null, {cache: {id: ''}}); + + expect(server.requests).to.have.length(0); + + server.restore(); + }); + + it(`should return cacheIdObj`, () => { + expect(ftrackIdSubmodule.extendId(configMock, null, {cache: {id: ''}})).to.deep.equal({cache: {id: ''}}); + }); + }); +}); diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 82cb70f42be..a78f5ac948e 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -16,7 +16,7 @@ import { config } from 'src/config.js'; import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { validateStorageEnforcement } from 'src/storageManager.js'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; describe('gdpr enforcement', function () { let nextFnSpy; diff --git a/test/spec/modules/glimpseBidAdapter_spec.js b/test/spec/modules/glimpseBidAdapter_spec.js index 98e1a1bb451..02f5b502a1b 100644 --- a/test/spec/modules/glimpseBidAdapter_spec.js +++ b/test/spec/modules/glimpseBidAdapter_spec.js @@ -1,3 +1,4 @@ +import { BANNER } from '../../../src/mediaTypes' import { expect } from 'chai' import { newBidder } from 'src/adapters/bidderFactory.js' import { spec } from 'modules/glimpseBidAdapter.js' @@ -79,6 +80,20 @@ function getDeepCopy(object) { describe('GlimpseProtocolAdapter', () => { const glimpseAdapter = newBidder(spec) + describe('spec', () => { + it('Has defined the glimpse gvlid', () => { + expect(spec.gvlid).to.equal(1012) + }) + + it('Has defined glimpse as the bidder', () => { + expect(spec.code).to.equal('glimpse') + }) + + it('Has defined valid mediaTypes', () => { + expect(spec.supportedMediaTypes).to.deep.equal([BANNER]) + }) + }) + describe('Inherited functions', () => { it('Functions exist and are valid types', () => { expect(glimpseAdapter.callBids).to.exist.and.to.be.a('function') diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js index c22badb9b4c..87c87600b97 100644 --- a/test/spec/modules/gmosspBidAdapter_spec.js +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/gmosspBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import {getStorageManager} from 'src/storageManager'; import * as utils from 'src/utils.js'; const ENDPOINT = 'https://sp.gmossp-sp.jp/hb/prebid/query.ad'; @@ -36,8 +35,7 @@ describe('GmosspAdapter', function () { }); describe('buildRequests', function () { - const storage = getStorageManager(); - const bidRequests = [ + let bidRequests = [ { bidder: 'gmossp', params: { @@ -52,7 +50,12 @@ describe('GmosspAdapter', function () { bidId: '2b84475b5b636e', bidderRequestId: '1f4001782ac16c', auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', - transactionId: '791e9d84-af92-4903-94da-24c7426d9d0c' + transactionId: '791e9d84-af92-4903-94da-24c7426d9d0c', + userId: { + imuid: 'h.0a4749e7ffe09fa6', + pubcid: '1111', + idl_env: '1111', + } } ]; @@ -62,24 +65,24 @@ describe('GmosspAdapter', function () { referer: 'https://hoge.com' } }; - storage.setCookie('_im_uid.1000283', 'h.0a4749e7ffe09fa6'); - 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('tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&im_uid=h.0a4749e7ffe09fa6&url=https%3A%2F%2Fhoge.com&cur=JPY&dnt=0&'); + expect(requests[0].data).to.equal('tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&im_uid=h.0a4749e7ffe09fa6&shared_id=1111&idl_env=1111&url=https%3A%2F%2Fhoge.com' + '&ref=' + encodeURIComponent(document.referrer) + '&cur=JPY&dnt=0&'); }); - it('should use fallback if refererInfo.referer in bid request is empty and _im_uid.1000283 cookie is empty', function () { + it('should use fallback if refererInfo.referer in bid request is empty and im_uid ,shared_id, idl_env cookie is empty', function () { const bidderRequest = { refererInfo: { referer: '' - } + }, }; - storage.setCookie('_im_uid.1000283', ''); + bidRequests[0].userId.imuid = ''; + bidRequests[0].userId.pubcid = ''; + bidRequests[0].userId.idl_env = ''; const requests = spec.buildRequests(bidRequests, bidderRequest); - const result = 'tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&url=' + encodeURIComponent(window.top.location.href) + '&cur=JPY&dnt=0&'; + const result = 'tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&ref=' + encodeURIComponent(document.referrer) + '&cur=JPY&dnt=0&'; expect(requests[0].data).to.equal(result); }); }); @@ -104,7 +107,7 @@ describe('GmosspAdapter', function () { } ]; - it('should get correct banner bid response', function() { + it('should get correct banner bid response', function () { const response = { bid: '2b84475b5b636e', price: 20, diff --git a/test/spec/modules/gnetBidAdapter_spec.js b/test/spec/modules/gnetBidAdapter_spec.js index eeb33418a82..a69b196bc5c 100644 --- a/test/spec/modules/gnetBidAdapter_spec.js +++ b/test/spec/modules/gnetBidAdapter_spec.js @@ -8,7 +8,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; -const ENDPOINT = 'https://adserver.gnetproject.com/prebid.php'; +const ENDPOINT = 'https://service.gnetrtb.com/api/adrequest'; describe('gnetAdapter', function () { const adapter = newBidder(spec); @@ -23,7 +23,7 @@ describe('gnetAdapter', function () { let bid = { bidder: 'gnet', params: { - websiteId: '4' + websiteId: '1', adunitId: '1' } }; @@ -39,11 +39,22 @@ describe('gnetAdapter', function () { }); }); + describe('onBidWon', function () { + const bid = { + requestId: '29d5b1d3a520f8' + }; + + it('return success adserver won bid endpoint', () => { + const result = spec.onBidWon(bid); + assert.ok(result); + }); + }); + describe('buildRequests', function () { const bidRequests = [{ bidder: 'gnet', params: { - websiteId: '4' + websiteId: '1', adunitId: '1' }, adUnitCode: '/150790500/4_ZONA_IAB_300x250_5', sizes: [ @@ -52,12 +63,13 @@ describe('gnetAdapter', function () { bidId: '2a19afd5173318', bidderRequestId: '1f4001782ac16c', auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', - transactionId: '894bdff6-61ec-4bec-a5a9-f36a5bfccef5' + transactionId: '894bdff6-61ec-4bec-a5a9-f36a5bfccef5', + gftuid: null }]; const bidderRequest = { refererInfo: { - referer: 'https://gnetproject.com/' + referer: 'https://gnetrtb.com' } }; @@ -66,13 +78,14 @@ describe('gnetAdapter', function () { expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('POST'); expect(requests[0].data).to.equal(JSON.stringify({ - 'referer': 'https://gnetproject.com/', + 'referer': 'https://gnetrtb.com', 'adUnitCode': '/150790500/4_ZONA_IAB_300x250_5', 'bidId': '2a19afd5173318', 'transactionId': '894bdff6-61ec-4bec-a5a9-f36a5bfccef5', + 'gftuid': null, 'sizes': ['300x250'], 'params': { - 'websiteId': '4' + 'websiteId': '1', 'adunitId': '1' } })); }); diff --git a/test/spec/modules/goldbachBidAdapter_spec.js b/test/spec/modules/goldbachBidAdapter_spec.js new file mode 100644 index 00000000000..459cda7958f --- /dev/null +++ b/test/spec/modules/goldbachBidAdapter_spec.js @@ -0,0 +1,1359 @@ +import { expect } from 'chai'; +import { spec } from 'modules/goldbachBidAdapter.js'; +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 { config } from 'src/config.js'; + +const ENDPOINT = 'https://ib.adnxs.com/ut/v3/prebid'; +const PRICING_ENDPOINT = 'https://templates.da-services.ch/01_universal/burda_prebid/1.0/json/sizeCPMMapping.json'; + +describe('GoldbachXandrAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'goldbach', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'member': '1234', + 'invCode': 'ABCD' + }; + + 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 = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let getAdUnitsStub; + let bidRequests = [ + { + 'bidder': 'goldbach', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + + beforeEach(function() { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + return []; + }); + }); + + afterEach(function() { + getAdUnitsStub.restore(); + }); + + it('should parse out private sizes', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + privateSizes: [300, 250] + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + 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 publisher_id in request', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + publisherId: '1231234' + } + }); + const request = spec.buildRequests([bidRequest])[1]; + 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)[1]; + const payload = JSON.parse(request.data); + expect(payload.sdk).to.exist; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + }); + + it('should populate the ad_types array on all requests', function () { + let adUnits = [{ + code: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'goldbach', + params: { + placementId: '10433394' + } + }], + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + }]; + + ['banner', 'video', 'native'].forEach(type => { + getAdUnitsStub.callsFake(function(...args) { + return adUnits; + }); + + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes[type] = {}; + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal([type]); + + if (type === 'banner') { + delete adUnits[0].mediaTypes; + } + }); + }); + + it('should not populate the ad_types array when adUnit.mediaTypes is undefined', function() { + const bidRequest = Object.assign({}, bidRequests[0]); + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.not.exist; + }); + + it('should populate the ad_types array on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests)[1]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(PRICING_ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('should attach valid video params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + video: { + id: 123, + minduration: 100, + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('should include ORTB video values when video params were not set', function() { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + + it('should add video property when adUnit includes a renderer', function () { + const videoData = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + }, + params: { + placementId: '10433394', + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + }; + + let bidRequest1 = deepClone(bidRequests[0]); + bidRequest1 = Object.assign({}, bidRequest1, videoData, { + renderer: { + url: 'https://test.renderer.url', + render: function () {} + } + }); + + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + bidRequest2 = Object.assign({}, bidRequest2, videoData); + + const request = spec.buildRequests([bidRequest1, bidRequest2])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + skippable: true, + playback_method: 2, + custom_renderer_present: true + }); + expect(payload.tags[1].video).to.deep.equal({ + skippable: true, + playback_method: 2 + }); + }); + + it('should attach valid user params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + user: { + externalUid: '123', + segments: [123, { id: 987, value: 876 }], + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.user).to.exist; + expect(payload.user).to.deep.equal({ + external_uid: '123', + segments: [{id: 123}, {id: 987, value: 876}] + }); + }); + + it('should attach reserve param when either bid param or getFloor function exists', function () { + let getFloorResponse = { currency: 'USD', floor: 3 }; + let request, payload = null; + let bidRequest = deepClone(bidRequests[0]); + + // 1 -> reserve not defined, getFloor not defined > empty + request = spec.buildRequests([bidRequest])[1]; + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.not.exist; + + // 2 -> reserve is defined, getFloor not defined > reserve is used + bidRequest.params = { + 'placementId': '10433394', + 'reserve': 0.5 + }; + request = spec.buildRequests([bidRequest])[1]; + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.exist.and.to.equal(0.5); + + // 3 -> reserve is defined, getFloor is defined > getFloor is used + bidRequest.getFloor = () => getFloorResponse; + + request = spec.buildRequests([bidRequest])[1]; + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.exist.and.to.equal(3); + }); + + it('should duplicate adpod placements into batches and set correct maxduration', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + + // 300 / 15 = 20 total + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload1.tags[0].video.maxduration).to.equal(30); + + expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload2.tags[0].video.maxduration).to.equal(30); + }); + + it('should round down adpod placements when numbers are uneven', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 123, + durationRangeSec: [45], + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(2); + }); + + it('should duplicate adpod placements when requireExactDuration is set', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true, + } + } + } + ); + + // 20 total placements with 15 max impressions = 2 requests + const request = spec.buildRequests([bidRequest])[1]; + expect(request.length).to.equal(2); + + // 20 spread over 2 requests = 15 in first request, 5 in second + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + // 10 placements should have max/min at 15 + // 10 placemenst should have max/min at 30 + const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); + const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); + expect(payload1tagsWith15.length).to.equal(10); + expect(payload1tagsWith30.length).to.equal(5); + + // 5 placemenst with min/max at 30 were in the first request + // so 5 remaining should be in the second + const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); + expect(payload2tagsWith30.length).to.equal(5); + }); + + it('should set durations for placements when requireExactDuration is set and numbers are uneven', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 105, + durationRangeSec: [15, 30, 60], + requireExactDuration: true, + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(7); + + const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); + const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); + const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); + expect(tagsWith15.length).to.equal(3); + expect(tagsWith30.length).to.equal(3); + expect(tagsWith60.length).to.equal(1); + }); + + it('should break adpod request into batches', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 225, + durationRangeSec: [5], + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + const payload3 = JSON.parse(request[2].data); + + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(15); + expect(payload3.tags.length).to.equal(15); + }); + + // it('should contain hb_source value for adpod', function() { + // let bidRequest = Object.assign({}, + // bidRequests[0], + // { + // params: { placementId: '14542875' } + // }, + // { + // mediaTypes: { + // video: { + // context: 'adpod', + // playerSize: [640, 480], + // adPodDurationSec: 300, + // durationRangeSec: [15, 30], + // } + // } + // } + // ); + // const request = spec.buildRequests([bidRequest])[1]; + // const payload = JSON.parse(request.data); + // expect(payload.tags[0].hb_source).to.deep.equal(7); + // }); + + it('should contain hb_source value for other media', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'banner', + params: { + sizes: [[300, 250], [300, 600]], + placementId: 13144370 + } + } + ); + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('adds brand_category_exclusion to request when set', function() { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon + .stub(config, 'getConfig') + .withArgs('adpod.brandCategoryExclusion') + .returns(true); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.brand_category_uniqueness).to.equal(true); + + config.getConfig.restore(); + }); + + it('should attach native params to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + title: {required: true}, + body: {required: true}, + body2: {required: true}, + image: {required: true, sizes: [100, 100]}, + icon: {required: true}, + cta: {required: false}, + rating: {required: true}, + sponsoredBy: {required: true}, + privacyLink: {required: true}, + displayUrl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + salePrice: {required: true} + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].native.layouts[0]).to.deep.equal({ + title: {required: true}, + description: {required: true}, + desc2: {required: true}, + main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, + icon: {required: true}, + ctatext: {required: false}, + rating: {required: true}, + sponsored_by: {required: true}, + privacy_link: {required: true}, + displayurl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + saleprice: {required: true}, + privacy_supported: true + }); + expect(payload.tags[0].hb_source).to.equal(1); + }); + + it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + image: { required: true } + } + } + ); + bidRequest.sizes = [[150, 100], [300, 250]]; + + let request = spec.buildRequests([bidRequest])[1]; + let payload = JSON.parse(request.data); + expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); + + delete bidRequest.sizes; + + request = spec.buildRequests([bidRequest])[1]; + payload = JSON.parse(request.data); + + expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); + }); + + it('should convert keyword params to proper form and attaches to request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [5], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + emptyStr: '', + emptyArr: [''], + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['5'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' + }]); + }); + + it('should add payment rules to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + usePaymentRule: true + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + 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]); + bidRequest.ortb2Imp = { ext: { data: { pbadslot: testGpid } } }; + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].gpid).to.exist.and.equal(testGpid) + }); + + it('should add gdpr consent information to the request', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'goldbach', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true, + addtlConsent: '1~7.12.35.62.66.70.89.93.108' + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest)[1]; + expect(request.options).to.deep.equal({withCredentials: true}); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_consent).to.exist; + expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); + expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; + expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); + }); + + it('should add us privacy string to payload', function() { + let consentString = '1YA-'; + let bidderRequest = { + 'bidderCode': 'goldbach', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': consentString + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest)[1]; + const payload = JSON.parse(request.data); + + expect(payload.us_privacy).to.exist; + expect(payload.us_privacy).to.exist.and.to.equal(consentString); + }); + + it('supports sending hybrid mobile app parameters', function () { + let appRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + app: { + id: 'B1O2W3M4AN.com.prebid.webview', + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', // Apple advertising identifier + aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', // Android advertising identifier + md5udid: '5756ae9022b2ea1e47d84fead75220c8', // MD5 hash of the ANDROID_ID + sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', // SHA1 hash of the ANDROID_ID + windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' // Windows advertising identifier + } + } + } + } + ); + const request = spec.buildRequests([appRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.app).to.exist; + expect(payload.app).to.deep.equal({ + appid: 'B1O2W3M4AN.com.prebid.webview' + }); + expect(payload.device.device_id).to.exist; + expect(payload.device.device_id).to.deep.equal({ + aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', + idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', + md5udid: '5756ae9022b2ea1e47d84fead75220c8', + sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', + windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' + }); + expect(payload.device.geo).to.exist; + expect(payload.device.geo).to.deep.equal({ + lat: 40.0964439, + lng: -75.3009142 + }); + }); + + it('should add referer info to payload', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'https://example.com/page.html', + 'https://example.com/iframe1.html', + 'https://example.com/iframe2.html' + ] + } + } + const request = spec.buildRequests([bidRequest], bidderRequest)[1]; + const payload = JSON.parse(request.data); + + expect(payload.referrer_detection).to.exist; + expect(payload.referrer_detection).to.deep.equal({ + rd_ref: 'https%3A%2F%2Fexample.com%2Fpage.html', + rd_top: true, + rd_ifs: 2, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + }); + }); + + it('should populate schain if available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'blob.com', + 'sid': '001', + 'hp': 1 + } + ] + } + }); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'blob.com', + 'sid': '001', + 'hp': 1 + } + ] + }); + }); + + it('should populate coppa if set in config', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.user.coppa).to.equal(true); + + config.getConfig.restore(); + }); + + it('should set the X-Is-Test customHeader if test flag is enabled', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('apn_test') + .returns(true); + + const request = spec.buildRequests([bidRequest])[1]; + expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + + config.getConfig.restore(); + }); + + it('should always set withCredentials: true on the request.options', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + const request = spec.buildRequests([bidRequest])[1]; + expect(request.options.withCredentials).to.equal(true); + }); + + it('should set simple domain variant if purpose 1 consent is not given', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'goldbach', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true, + apiVersion: 2, + vendorData: { + purpose: { + consents: { + 1: false + } + } + } + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest)[1]; + expect(request.url).to.equal('https://ib.adnxs-simple.com/ut/v3/prebid'); + }); + + it('should populate eids when supported userIds are available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + userId: { + tdid: 'sample-userid', + uid2: { id: 'sample-uid2-value' }, + criteoId: 'sample-criteo-userid', + netId: 'sample-netId-userid', + idl_env: 'sample-idl-userid', + flocId: { + id: 'sample-flocid-value', + version: 'chrome.1.0' + } + } + }); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.eids).to.deep.include({ + source: 'adserver.org', + id: 'sample-userid', + rti_partner: 'TDID' + }); + + expect(payload.eids).to.deep.include({ + source: 'criteo.com', + id: 'sample-criteo-userid', + }); + + expect(payload.eids).to.deep.include({ + source: 'chrome.com', + id: 'sample-flocid-value' + }); + + expect(payload.eids).to.deep.include({ + source: 'netid.de', + id: 'sample-netId-userid', + }); + + expect(payload.eids).to.deep.include({ + source: 'liveramp.com', + id: 'sample-idl-userid' + }); + + expect(payload.eids).to.deep.include({ + source: 'uidapi.com', + id: 'sample-uid2-value', + rti_partner: 'UID2' + }); + }); + + it('should populate iab_support object at the root level if omid support is detected', function () { + // with bid.params.frameworks + let bidRequest_A = Object.assign({}, bidRequests[0], { + params: { + frameworks: [1, 2, 5, 6], + video: { + frameworks: [1, 2, 5, 6] + } + } + }); + let request = spec.buildRequests([bidRequest_A])[1]; + let payload = JSON.parse(request.data); + expect(payload.iab_support).to.be.an('object'); + expect(payload.iab_support).to.deep.equal({ + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + }); + expect(payload.tags[0].banner_frameworks).to.be.an('array'); + expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video_frameworks).to.be.an('array'); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video.frameworks).to.not.exist; + + // without bid.params.frameworks + const bidRequest_B = Object.assign({}, bidRequests[0]); + request = spec.buildRequests([bidRequest_B])[1]; + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + + // with video.frameworks but it is not an array + const bidRequest_C = Object.assign({}, bidRequests[0], { + params: { + video: { + frameworks: "'1', '2', '3', '6'" + } + } + }); + request = spec.buildRequests([bidRequest_C])[1]; + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + }); + }) + + describe('interpretResponse', function () { + let bfStub; + before(function() { + bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + }); + + after(function() { + bfStub.restore(); + }); + + let response = { + 'version': '3.0.0', + 'tags': [ + { + 'uuid': '3db3773286ee59', + 'tag_id': 10433394, + 'auction_id': '4534722592064951574', + 'nobid': false, + 'no_ad_url': 'https://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 10000, + 'ad_profile_id': 27079, + 'ads': [ + { + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 29681110, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.5, + 'cpm_publisher_currency': 0.5, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'viewability': { + 'config': '' + }, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'https://lax1-ib.adnxs.com/impression' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'adUnitCode': 'code', + 'appnexus': { + 'buyerMemberId': 958 + } + } + ]; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + let response = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + + it('handles outstream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles instream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/vid' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles adpod responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'brand_category_id': 10, + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/adpod', + 'duration_ms': 30000, + } + }, + 'viewability': { + 'config': '' + } + }] + }] + }; + + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } + } + }] + }; + bfStub.returns('1'); + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0].video.context).to.equal('adpod'); + expect(result[0].video.durationSeconds).to.equal(30); + }); + + it('handles native responses', function () { + let response1 = deepClone(response); + response1.tags[0].ads[0].ad_type = 'native'; + response1.tags[0].ads[0].rtb.native = { + 'title': 'Native Creative', + 'desc': 'Cool description great stuff', + 'desc2': 'Additional body text', + 'ctatext': 'Do it', + 'sponsored': 'AppNexus', + 'icon': { + 'width': 0, + 'height': 0, + 'url': 'https://cdn.adnxs.com/icon.png' + }, + 'main_img': { + 'width': 2352, + 'height': 1516, + 'url': 'https://cdn.adnxs.com/img.png' + }, + 'link': { + 'url': 'https://www.appnexus.com', + 'fallback_url': '', + 'click_trackers': ['https://nym1-ib.adnxs.com/click'] + }, + 'impression_trackers': ['https://example.com'], + 'rating': '5', + 'displayurl': 'https://AppNexus.com/?url=display_url', + 'likes': '38908320', + 'downloads': '874983', + 'price': '9.99', + 'saleprice': 'FREE', + 'phone': '1234567890', + 'address': '28 W 23rd St, New York, NY 10010', + 'privacy_link': 'https://appnexus.com/?url=privacy_url', + 'javascriptTrackers': '' + }; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + + let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); + expect(result[0].native.title).to.equal('Native Creative'); + 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'); + }); + + it('supports configuring outstream renderers', function () { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + renderer: { + options: { + adText: 'configured' + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + }; + + const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); + + it('should add deal_priority and deal_code', function() { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].ad_type = 'video'; + responseWithDeal.tags[0].ads[0].deal_priority = 5; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + responseWithDeal.tags[0].ads[0].rtb.video = { + duration_ms: 1500, + player_width: 640, + player_height: 340, + }; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } + } + }] + } + let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); + expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + expect(result[0].video.dealTier).to.equal(5); + }); + + it('should add advertiser id', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); + }); + + it('should add advertiserDomains', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + 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([]); + }); + }); +}); diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 3e8dbfe8d92..2bfce71806e 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -20,7 +20,9 @@ describe('GPT pre-auction module', () => { const testSlots = [ makeSlot({ code: 'slotCode1', divId: 'div1' }), makeSlot({ code: 'slotCode2', divId: 'div2' }), - makeSlot({ code: 'slotCode3', divId: 'div3' }) + makeSlot({ code: 'slotCode3', divId: 'div3' }), + makeSlot({ code: 'slotCode4', divId: 'div4' }), + makeSlot({ code: 'slotCode4', divId: 'div5' }) ]; describe('appendPbAdSlot', () => { @@ -172,7 +174,9 @@ describe('GPT pre-auction module', () => { expect(_currentConfig).to.deep.equal({ enabled: true, customGptSlotMatching: false, - customPbAdSlot: false + customPbAdSlot: false, + customPreAuction: false, + useDefaultPreAuction: false }); }); }); @@ -197,15 +201,240 @@ describe('GPT pre-auction module', () => { code: 'slotCode3', }]; + // first two adUnits directly pass in pbadslot => gpid is same const expectedAdUnits = [{ code: 'adUnit1', - ortb2Imp: { ext: { data: { pbadslot: '12345' } } } - }, { + ortb2Imp: { + ext: { + data: { + pbadslot: '12345' + }, + gpid: '12345' + } + } + }, + // second adunit + { code: 'slotCode1', - ortb2Imp: { ext: { data: { pbadslot: '67890', adserver: { name: 'gam', adslot: 'slotCode1' } } } } + ortb2Imp: { + ext: { + data: { + pbadslot: '67890', + adserver: { + name: 'gam', + adslot: 'slotCode1' + } + }, + gpid: '67890' + } + } }, { code: 'slotCode3', - ortb2Imp: { ext: { data: { pbadslot: 'slotCode3', adserver: { name: 'gam', adslot: 'slotCode3' } } } } + ortb2Imp: { + ext: { + data: { + pbadslot: 'slotCode3', + adserver: { + name: 'gam', + adslot: 'slotCode3' + } + }, + gpid: 'slotCode3' + } + } + }]; + + window.googletag.pubads().setSlots(testSlots); + runMakeBidRequests(testAdUnits); + expect(returnedAdUnits).to.deep.equal(expectedAdUnits); + }); + + it('should not apply gpid if pbadslot was set by adUnitCode', () => { + const testAdUnits = [{ + code: 'noMatchCode', + }]; + + // first two adUnits directly pass in pbadslot => gpid is same + const expectedAdUnits = [{ + code: 'noMatchCode', + ortb2Imp: { + ext: { + data: { + pbadslot: 'noMatchCode' + }, + } + } + }]; + + window.googletag.pubads().setSlots(testSlots); + runMakeBidRequests(testAdUnits); + expect(returnedAdUnits).to.deep.equal(expectedAdUnits); + }); + + it('should use the passed customPreAuction logic', () => { + let counter = 0; + config.setConfig({ + gptPreAuction: { + enabled: true, + customPreAuction: (adUnit, slotName) => { + counter += 1; + return `${adUnit.code}-${slotName || counter}`; + } + } + }); + const testAdUnits = [ + { + code: 'adUnit1', + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } + }, + { + code: 'adUnit2', + }, + { + code: 'slotCode3', + }, + { + code: 'div4', + } + ]; + + // all slots should be passed in same time and have slot-${index} + const expectedAdUnits = [{ + code: 'adUnit1', + ortb2Imp: { + ext: { + // no slotname match so uses adUnit.code-counter + data: { + pbadslot: 'adUnit1-1' + }, + gpid: 'adUnit1-1' + } + } + }, + // second adunit + { + code: 'adUnit2', + ortb2Imp: { + ext: { + // no slotname match so uses adUnit.code-counter + data: { + pbadslot: 'adUnit2-2' + }, + gpid: 'adUnit2-2' + } + } + }, { + code: 'slotCode3', + ortb2Imp: { + ext: { + // slotname found, so uses code + slotname (which is same) + data: { + pbadslot: 'slotCode3-slotCode3', + adserver: { + name: 'gam', + adslot: 'slotCode3' + } + }, + gpid: 'slotCode3-slotCode3' + } + } + }, { + code: 'div4', + ortb2Imp: { + ext: { + // slotname found, so uses code + slotname + data: { + pbadslot: 'div4-slotCode4', + adserver: { + name: 'gam', + adslot: 'slotCode4' + } + }, + gpid: 'div4-slotCode4' + } + } + }]; + + window.googletag.pubads().setSlots(testSlots); + runMakeBidRequests(testAdUnits); + expect(returnedAdUnits).to.deep.equal(expectedAdUnits); + }); + + it('should use useDefaultPreAuction logic', () => { + config.setConfig({ + gptPreAuction: { + enabled: true, + useDefaultPreAuction: true + } + }); + const testAdUnits = [ + // First adUnit should use the preset pbadslot + { + code: 'adUnit1', + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } + }, + // Second adUnit should not match a gam slot, so no slot set + { + code: 'adUnit2', + }, + // third adunit matches a single slot so uses it + { + code: 'slotCode3', + }, + // fourth adunit matches multiple slots so combination + { + code: 'div4', + } + ]; + + const expectedAdUnits = [{ + code: 'adUnit1', + ortb2Imp: { + ext: { + data: { + pbadslot: '12345' + }, + gpid: '12345' + } + } + }, + // second adunit + { + code: 'adUnit2', + ortb2Imp: { + ext: { + data: { + }, + } + } + }, { + code: 'slotCode3', + ortb2Imp: { + ext: { + data: { + pbadslot: 'slotCode3', + adserver: { + name: 'gam', + adslot: 'slotCode3' + } + }, + gpid: 'slotCode3' + } + } + }, { + code: 'div4', + ortb2Imp: { + ext: { + data: { + pbadslot: 'slotCode4#div4', + adserver: { + name: 'gam', + adslot: 'slotCode4' + } + }, + gpid: 'slotCode4#div4' + } + } }]; window.googletag.pubads().setSlots(testSlots); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index f31b8f16ef7..9e393a5823d 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -118,6 +118,24 @@ describe('TheMediaGrid Adapter', function () { } ]; + it('should be content categories and genre', function () { + const site = { + cat: ['IAB2'], + pagecat: ['IAB2-2'], + content: { + genre: 'Adventure' + } + }; + + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.site' ? site : null); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const payload = parseRequest(request.data); + expect(payload.site.cat).to.deep.equal([...site.cat, ...site.pagecat]); + expect(payload.site.content.genre).to.deep.equal(site.content.genre); + getConfigStub.restore(); + }); + it('should attach valid params to the tag', function () { const fpdUserIdVal = '0b0f84a1-1596-4165-9742-2e1a7dfac57f'; const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake( @@ -381,6 +399,24 @@ describe('TheMediaGrid Adapter', function () { expect(payload.user.ext.eids).to.deep.equal(eids); }); + it('if userId is present payload must have user.ext param with right keys', function () { + const ortb2UserExtDevice = { + screenWidth: 1200, + screenHeight: 800, + language: 'ru' + }; + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user.ext.device' ? ortb2UserExtDevice : null); + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext.device).to.deep.equal(ortb2UserExtDevice); + getConfigStub.restore(); + }); + it('if schain is present payload must have source.ext.schain param', function () { const schain = { complete: 1, @@ -510,7 +546,71 @@ describe('TheMediaGrid Adapter', function () { getConfigStub.restore(); }); - it('shold be right tmax when timeout in config is less then timeout in bidderRequest', function() { + it('should have user.data filled from config ortb2.user.data', function () { + const userData = [ + { + name: 'someName', + segment: [1, 2, { anyKey: 'anyVal' }, 'segVal', { id: 'segId' }, { value: 'segValue' }, { id: 'segId2', name: 'segName' }] + }, + { + name: 'permutive.com', + segment: [1, 2, 'segVal', { id: 'segId' }, { anyKey: 'anyVal' }, { value: 'segValue' }, { id: 'segId2', name: 'segName' }] + }, + { + someKey: 'another data' + } + ]; + + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user.data' ? userData : null); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const payload = parseRequest(request.data); + expect(payload.user.data).to.deep.equal(userData); + getConfigStub.restore(); + }); + + it('should have right value in user.data when jwpsegments are present', function () { + const userData = [ + { + name: 'someName', + segment: [1, 2, { anyKey: 'anyVal' }, 'segVal', { id: 'segId' }, { value: 'segValue' }, { id: 'segId2', name: 'segName' }] + }, + { + name: 'permutive.com', + segment: [1, 2, 'segVal', { id: 'segId' }, { anyKey: 'anyVal' }, { value: 'segValue' }, { id: 'segId2', name: 'segName' }] + }, + { + someKey: 'another data' + } + ]; + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user.data' ? userData : null); + + const jsContent = {id: 'test_jw_content_id'}; + const jsSegments = ['test_seg_1', 'test_seg_2']; + const bidRequestsWithJwTargeting = Object.assign({}, bidRequests[0], { + rtd: { + jwplayer: { + targeting: { + segments: jsSegments, + content: jsContent + } + } + } + }); + const request = spec.buildRequests([bidRequestsWithJwTargeting], bidderRequest); + const payload = parseRequest(request.data); + expect(payload.user.data).to.deep.equal([{ + name: 'iow_labs_pub_data', + segment: [ + {name: 'jwpseg', value: jsSegments[0]}, + {name: 'jwpseg', value: jsSegments[1]} + ] + }, ...userData]); + getConfigStub.restore(); + }); + + 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); @@ -519,7 +619,7 @@ describe('TheMediaGrid Adapter', function () { expect(payload.tmax).to.equal(2000); getConfigStub.restore(); }); - it('shold be right tmax when timeout in bidderRequest is less then timeout in config', function() { + 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); @@ -581,6 +681,32 @@ describe('TheMediaGrid Adapter', function () { }); }); + it('should contain imp[].instl if available', function() { + const ortb2Imp = [{ + instl: 1 + }, { + instl: 2, + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/222222/slot' + }, + pbadslot: '/222222/slot' + } + } + }]; + const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { + return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); + }); + const request = spec.buildRequests(bidRequestsWithOrtb2Imp, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].instl).to.equal(1); + expect(payload.imp[1].instl).to.equal(2); + expect(payload.imp[2].instl).to.be.undefined; + }); + it('all id must be a string', function() { const fpdUserIdNumVal = 2345543345; const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake( diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index dfd7db7d922..ebbc1c230f1 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -152,6 +152,12 @@ describe('gumgumAdapter', function () { const zoneParam = { 'zone': '123a' }; const pubIdParam = { 'pubId': 123 }; + it('should set aun if the adUnitCode is available', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.aun).to.equal(bidRequests[0].adUnitCode); + }); + it('should set pubId param if found', function () { const request = { ...bidRequests[0], params: pubIdParam }; const bidRequest = spec.buildRequests([request])[0]; @@ -192,8 +198,8 @@ describe('gumgumAdapter', function () { slotRequest.params.slot = invalidSlotId; legacySlotRequest.params.inSlot = invalidSlotId; - req = spec.buildRequests([ slotRequest ])[0]; - legReq = spec.buildRequests([ legacySlotRequest ])[0]; + req = spec.buildRequests([slotRequest])[0]; + legReq = spec.buildRequests([legacySlotRequest])[0]; expect(req.data.si).to.equal(invalidSlotId); expect(legReq.data.si).to.equal(invalidSlotId); @@ -538,6 +544,52 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(!!bidRequest.data.lt).to.be.true; }); + + it('should handle no gg params', function () { + const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { referer: 'https://www.prebid.org/?param1=foo¶m2=bar¶m3=baz' } })[0]; + + // no params are in object + expect(bidRequest.data.hasOwnProperty('eAdBuyId')).to.be.false; + expect(bidRequest.data.hasOwnProperty('adBuyId')).to.be.false; + expect(bidRequest.data.hasOwnProperty('ggdeal')).to.be.false; + }); + + it('should handle encrypted ad buy id', function () { + const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { referer: 'https://www.prebid.org/?param1=foo&ggad=bar¶m3=baz' } })[0]; + + // correct params are in object + expect(bidRequest.data.hasOwnProperty('eAdBuyId')).to.be.true; + expect(bidRequest.data.hasOwnProperty('adBuyId')).to.be.false; + expect(bidRequest.data.hasOwnProperty('ggdeal')).to.be.false; + + // params are stripped from pu property + expect(bidRequest.data.pu.includes('ggad')).to.be.false; + }); + + it('should handle unencrypted ad buy id', function () { + const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { referer: 'https://www.prebid.org/?param1=foo&ggad=123¶m3=baz' } })[0]; + + // correct params are in object + expect(bidRequest.data.hasOwnProperty('eAdBuyId')).to.be.false; + expect(bidRequest.data.hasOwnProperty('adBuyId')).to.be.true; + expect(bidRequest.data.hasOwnProperty('ggdeal')).to.be.false; + + // params are stripped from pu property + expect(bidRequest.data.pu.includes('ggad')).to.be.false; + }); + + it('should handle multiple gg params', function () { + const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { referer: 'https://www.prebid.org/?ggdeal=foo&ggad=bar¶m3=baz' } })[0]; + + // correct params are in object + expect(bidRequest.data.hasOwnProperty('eAdBuyId')).to.be.true; + expect(bidRequest.data.hasOwnProperty('adBuyId')).to.be.false; + expect(bidRequest.data.hasOwnProperty('ggdeal')).to.be.true; + + // params are stripped from pu property + expect(bidRequest.data.pu.includes('ggad')).to.be.false; + expect(bidRequest.data.pu.includes('ggdeal')).to.be.false; + }); }) describe('interpretResponse', function () { @@ -690,7 +742,7 @@ describe('gumgumAdapter', function () { it('uses request size that nearest matches response size for in-screen', function () { const request = { ...bidRequest }; const body = { ...serverResponse }; - const expectedSize = [ 300, 50 ]; + const expectedSize = [300, 50]; let result; request.pi = 2; diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js new file mode 100644 index 00000000000..77eeb70326b --- /dev/null +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -0,0 +1,57 @@ +import { hadronIdSubmodule, storage } from 'modules/hadronIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import * as utils from 'src/utils.js'; + +describe('HadronIdSystem', function () { + describe('getId', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + it('gets a hadronId', function() { + const config = { + params: {} + }; + const callbackSpy = sinon.spy(); + const callback = hadronIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq(`https://id.hadron.ad.gt/api/v1/pbhid`); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + }); + + it('gets a cached hadronid', function() { + const config = { + params: {} + }; + 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'}); + }); + + it('allows configurable id url', function() { + const config = { + params: { + url: 'https://hadronid.publync.com' + } + }; + const callbackSpy = sinon.spy(); + const callback = hadronIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq('https://hadronid.publync.com'); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + }); + }); +}); diff --git a/test/spec/modules/hadronRtdProvider_spec.js b/test/spec/modules/hadronRtdProvider_spec.js new file mode 100644 index 00000000000..30e4947566f --- /dev/null +++ b/test/spec/modules/hadronRtdProvider_spec.js @@ -0,0 +1,762 @@ +import {config} from 'src/config.js'; +import {HALOID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, hadronSubmodule, storage} from 'modules/hadronRtdProvider.js'; +import {server} from 'test/mocks/xhr.js'; + +const responseHeader = {'Content-Type': 'application/json'}; + +describe('hadronRtdProvider', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + describe('hadronSubmodule', function() { + it('successfully instantiates', function () { + expect(hadronSubmodule.init()).to.equal(true); + }); + }); + + describe('Add Real-Time Data', function() { + it('merges ortb2 data', function() { + let rtdConfig = {}; + let bidConfig = {}; + + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + const setConfigUserObj2 = { + name: 'www.dataprovider2.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1914' + }] + }; + + const setConfigSiteObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + } + + config.setConfig({ + ortb2: { + user: { + data: [setConfigUserObj1, setConfigUserObj2] + }, + site: { + content: { + data: [setConfigSiteObj1] + } + } + } + }); + + const rtdUserObj1 = { + name: 'www.dataprovider4.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const rtdSiteObj1 = { + name: 'www.dataprovider5.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1945' + }, + { + id: '2003' + } + ] + }; + + const rtd = { + ortb2: { + user: { + data: [rtdUserObj1] + }, + site: { + content: { + data: [rtdSiteObj1] + } + } + } + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + let ortb2Config = config.getConfig().ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1, setConfigUserObj2, rtdUserObj1]); + expect(ortb2Config.site.content.data).to.deep.include.members([setConfigSiteObj1, rtdSiteObj1]); + }); + + it('merges ortb2 data without duplication', function() { + let rtdConfig = {}; + let bidConfig = {}; + + const userObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + const userObj2 = { + name: 'www.dataprovider2.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1914' + }] + }; + + const siteObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + } + + config.setConfig({ + ortb2: { + user: { + data: [userObj1, userObj2] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }); + + const rtd = { + ortb2: { + user: { + data: [userObj1] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + let ortb2Config = config.getConfig().ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([userObj1, userObj2]); + expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1]); + expect(ortb2Config.user.data).to.have.lengthOf(2); + expect(ortb2Config.site.content.data).to.have.lengthOf(1); + }); + + it('merges bidder-specific ortb2 data', function() { + let rtdConfig = {}; + let bidConfig = {}; + + const configUserObj1 = { + name: 'www.dataprovider1.com', + ext: { segtax: 3 }, + segment: [{ + id: '1776' + }] + }; + + const configUserObj2 = { + name: 'www.dataprovider2.com', + ext: { segtax: 3 }, + segment: [{ + id: '1914' + }] + }; + + const configUserObj3 = { + name: 'www.dataprovider1.com', + ext: { segtax: 3 }, + segment: [{ + id: '2003' + }] + }; + + const configSiteObj1 = { + name: 'www.dataprovider3.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + }; + + const configSiteObj2 = { + name: 'www.dataprovider3.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '1812' + } + ] + }; + + config.setBidderConfig({ + bidders: ['adbuzz'], + config: { + ortb2: { + user: { + data: [configUserObj1, configUserObj2] + }, + site: { + content: { + data: [configSiteObj1] + } + } + } + } + }); + + config.setBidderConfig({ + bidders: ['pubvisage'], + config: { + ortb2: { + user: { + data: [configUserObj3] + }, + site: { + content: { + data: [configSiteObj2] + } + } + } + } + }); + + const rtdUserObj1 = { + name: 'www.dataprovider4.com', + ext: { + segtax: 501 + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const rtdUserObj2 = { + name: 'www.dataprovider2.com', + ext: { + segtax: 502 + }, + segment: [ + { + id: '1939' + } + ] + }; + + const rtdSiteObj1 = { + name: 'www.dataprovider5.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '441' + }, + { + id: '442' + } + ] + }; + + const rtdSiteObj2 = { + name: 'www.dataprovider6.com', + ext: { + segtax: 2 + }, + segment: [ + { + id: '676' + } + ] + }; + + const rtd = { + ortb2b: { + adbuzz: { + ortb2: { + user: { + data: [rtdUserObj1] + }, + site: { + content: { + data: [rtdSiteObj1] + } + } + } + }, + pubvisage: { + ortb2: { + user: { + data: [rtdUserObj2] + }, + site: { + content: { + data: [rtdSiteObj2] + } + } + } + } + } + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + let ortb2Config = config.getBidderConfig().adbuzz.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([configUserObj1, configUserObj2, rtdUserObj1]); + expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj1, rtdSiteObj1]); + + ortb2Config = config.getBidderConfig().pubvisage.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([configUserObj3, rtdUserObj2]); + expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj2, rtdSiteObj2]); + }); + + it('merges bidder-specific ortb2 data without duplication', function() { + let rtdConfig = {}; + let bidConfig = {}; + + const userObj1 = { + name: 'www.dataprovider1.com', + ext: { segtax: 3 }, + segment: [{ + id: '1776' + }] + }; + + const userObj2 = { + name: 'www.dataprovider2.com', + ext: { segtax: 3 }, + segment: [{ + id: '1914' + }] + }; + + const userObj3 = { + name: 'www.dataprovider1.com', + ext: { segtax: 3 }, + segment: [{ + id: '2003' + }] + }; + + const siteObj1 = { + name: 'www.dataprovider3.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + }; + + const siteObj2 = { + name: 'www.dataprovider3.com', + ext: { + segtax: 1 + }, + segment: [ + { + id: '1812' + } + ] + }; + + config.setBidderConfig({ + bidders: ['adbuzz'], + config: { + ortb2: { + user: { + data: [userObj1, userObj2] + }, + site: { + content: { + data: [siteObj1] + } + } + } + } + }); + + config.setBidderConfig({ + bidders: ['pubvisage'], + config: { + ortb2: { + user: { + data: [userObj3] + }, + site: { + content: { + data: [siteObj2] + } + } + } + } + }); + + const rtd = { + ortb2b: { + adbuzz: { + ortb2: { + user: { + data: [userObj1] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }, + pubvisage: { + ortb2: { + user: { + data: [userObj2, userObj3] + }, + site: { + content: { + data: [siteObj1, siteObj2] + } + } + } + } + } + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + let ortb2Config = config.getBidderConfig().adbuzz.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([userObj1]); + expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1]); + + expect(ortb2Config.user.data).to.have.lengthOf(2); + expect(ortb2Config.site.content.data).to.have.lengthOf(1); + + ortb2Config = config.getBidderConfig().pubvisage.ortb2; + + expect(ortb2Config.user.data).to.deep.include.members([userObj3, userObj3]); + expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1, siteObj2]); + + expect(ortb2Config.user.data).to.have.lengthOf(2); + expect(ortb2Config.site.content.data).to.have.lengthOf(2); + }); + + it('allows publisher defined rtd ortb2 logic', function() { + const rtdConfig = { + params: { + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + if (rtd.ortb2.user.data[0].segment[0].id == '1776') { + pbConfig.setConfig({ortb2: rtd.ortb2}); + } else { + pbConfig.setConfig({ortb2: {}}); + } + } + } + }; + + let bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.dataprovider.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + let rtd = { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + }; + + config.resetConfig(); + + let pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + + const rtdUserObj2 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [{ + id: 'pubseg1' + }] + }; + + rtd = { + ortb2: { + user: { + data: [rtdUserObj2] + } + } + }; + + config.resetConfig(); + + pbConfig = config.getConfig(); + addRealTimeData(bidConfig, rtd, rtdConfig); + expect(config.getConfig().ortb2).to.deep.equal({}); + }); + + it('allows publisher defined adunit logic', function() { + const rtdConfig = { + params: { + handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + var adUnits = bidConfig.adUnits; + for (var i = 0; i < adUnits.length; i++) { + var adUnit = adUnits[i]; + for (var j = 0; j < adUnit.bids.length; j++) { + var bid = adUnit.bids[j]; + if (bid.bidder == 'adBuzz') { + for (var k = 0; k < rtd.adBuzz.length; k++) { + bid.adBuzzData.segments.adBuzz.push(rtd.adBuzz[k]); + } + } else if (bid.bidder == 'trueBid') { + for (var k = 0; k < rtd.trueBid.length; k++) { + bid.trueBidSegments.push(rtd.trueBid[k]); + } + } + } + } + } + } + }; + + let bidConfig = { + adUnits: [ + { + bids: [ + { + bidder: 'adBuzz', + adBuzzData: { + segments: { + adBuzz: [ + { + id: 'adBuzzSeg1' + } + ] + } + } + }, + { + bidder: 'trueBid', + trueBidSegments: [] + } + ] + } + ] + }; + + const rtd = { + adBuzz: [{id: 'adBuzzSeg2'}, {id: 'adBuzzSeg3'}], + trueBid: [{id: 'truebidSeg1'}, {id: 'truebidSeg2'}, {id: 'truebidSeg3'}] + }; + + addRealTimeData(bidConfig, rtd, rtdConfig); + + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[0].id).to.equal('adBuzzSeg1'); + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[1].id).to.equal('adBuzzSeg2'); + expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[2].id).to.equal('adBuzzSeg3'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[0].id).to.equal('truebidSeg1'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[1].id).to.equal('truebidSeg2'); + expect(bidConfig.adUnits[0].bids[1].trueBidSegments[2].id).to.equal('truebidSeg3'); + }); + }); + + describe('Get Real-Time Data', function() { + it('gets rtd from local storage cache', function() { + const rtdConfig = { + params: { + segmentCache: true + } + }; + + const bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + } + }; + + getDataFromLocalStorageStub.withArgs(RTD_LOCAL_NAME).returns(JSON.stringify(cachedRtd)); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + }); + + it('gets real-time data via async request', function() { + const setConfigSiteObj1 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [ + { + id: 'pubseg1' + }, + { + id: 'pubseg2' + } + ] + } + + config.setConfig({ + ortb2: { + site: { + content: { + data: [setConfigSiteObj1] + } + } + } + }); + + const rtdConfig = { + params: { + segmentCache: false, + usePubHadron: true, + requestParams: { + publisherId: 'testPub1' + } + } + }; + + let bidConfig = {}; + + const rtdUserObj1 = { + name: 'www.audigent.com', + ext: { + segtax: '1', + taxprovider: '1' + }, + segment: [ + { + id: 'pubseg1' + }, + { + id: 'pubseg2' + } + ] + }; + + const data = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + } + }; + + getDataFromLocalStorageStub.withArgs(HALOID_LOCAL_NAME).returns('testHadronId1'); + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + + let request = server.requests[0]; + let postData = JSON.parse(request.requestBody); + expect(postData.config).to.have.deep.property('publisherId', 'testPub1'); + expect(postData.userIds).to.have.deep.property('hadronId', 'testHadronId1'); + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + }); + }); +}); diff --git a/test/spec/modules/iasRtdProvider_spec.js b/test/spec/modules/iasRtdProvider_spec.js index 192b2c6e3c3..0d52c594fb5 100644 --- a/test/spec/modules/iasRtdProvider_spec.js +++ b/test/spec/modules/iasRtdProvider_spec.js @@ -29,7 +29,7 @@ describe('iasRtdProvider is a RTD provider that', function () { const value = iasSubModule.init(config); expect(value).to.equal(false); }); - it('returns false missing pubId param', function () { + it('returns true with only the pubId param', function () { const config = { name: 'ias', waitForIt: true, @@ -40,6 +40,20 @@ describe('iasRtdProvider is a RTD provider that', function () { const value = iasSubModule.init(config); expect(value).to.equal(true); }); + it('returns true with the pubId and keyMappings params', function () { + const config = { + name: 'ias', + waitForIt: true, + params: { + pubId: '123456', + keyMappings: { + 'id': 'ias_id' + } + } + }; + const value = iasSubModule.init(config); + expect(value).to.equal(true); + }); }); describe('has a method `getBidRequestData` that', function () { it('exists', function () { @@ -75,34 +89,43 @@ describe('iasRtdProvider is a RTD provider that', function () { it('exists', function () { expect(iasSubModule.getTargetingData).to.be.a('function'); }); - it('invoke method', function () { - const targeting = iasSubModule.getTargetingData(adUnitsCode, config); - expect(adUnitsCode).to.length(2); - expect(targeting).to.be.not.null; - expect(targeting).to.be.not.empty; - expect(targeting['one-div-id']).to.be.not.null; - const targetingKeys = Object.keys(targeting['one-div-id']); - expect(targetingKeys.length).to.equal(10); - expect(targetingKeys['adt']).to.be.not.null; - expect(targetingKeys['alc']).to.be.not.null; - expect(targetingKeys['dlm']).to.be.not.null; - expect(targetingKeys['drg']).to.be.not.null; - expect(targetingKeys['hat']).to.be.not.null; - expect(targetingKeys['off']).to.be.not.null; - expect(targetingKeys['vio']).to.be.not.null; - expect(targetingKeys['fr']).to.be.not.null; - expect(targetingKeys['ias-kw']).to.be.not.null; - expect(targetingKeys['id']).to.be.not.null; - expect(targeting['one-div-id']['adt']).to.be.eq('veryLow'); - expect(targeting['one-div-id']['alc']).to.be.eq('veryLow'); - expect(targeting['one-div-id']['dlm']).to.be.eq('veryLow'); - expect(targeting['one-div-id']['drg']).to.be.eq('veryLow'); - expect(targeting['one-div-id']['hat']).to.be.eq('veryLow'); - expect(targeting['one-div-id']['off']).to.be.eq('veryLow'); - expect(targeting['one-div-id']['vio']).to.be.eq('veryLow'); - expect(targeting['one-div-id']['fr']).to.be.eq('false'); - expect(targeting['one-div-id']['id']).to.be.eq('4813f7a2-1f22-11ec-9bfd-0a1107f94461'); - }); + describe('invoke method', function () { + it('returns a targeting object with the right shape', function () { + const targeting = iasSubModule.getTargetingData(adUnitsCode, config); + expect(adUnitsCode).to.length(2); + expect(targeting).to.be.not.null; + expect(targeting).to.be.not.empty; + expect(targeting['one-div-id']).to.be.not.null; + }); + it('returns the right keys', function () { + const targeting = iasSubModule.getTargetingData(adUnitsCode, config); + const targetingKeys = Object.keys(targeting['one-div-id']); + expect(targetingKeys.length).to.equal(10); + expect(targetingKeys).to.include('adt', 'adt key missing from the targeting object'); + expect(targetingKeys).to.include('alc', 'alc key missing from the targeting object'); + expect(targetingKeys).to.include('dlm', 'dlm key missing from the targeting object'); + expect(targetingKeys).to.include('drg', 'drg key missing from the targeting object'); + expect(targetingKeys).to.include('hat', 'hat key missing from the targeting object'); + expect(targetingKeys).to.include('off', 'off key missing from the targeting object'); + expect(targetingKeys).to.include('vio', 'vio key missing from the targeting object'); + expect(targetingKeys).to.include('fr', 'fr key missing from the targeting object'); + expect(targetingKeys).to.include('ias-kw', 'ias-kw key missing from the targeting object'); + expect(targetingKeys).to.not.include('id', 'id key present in the targeting object, should have been renamed to ias_id'); + expect(targetingKeys).to.include('ias_id', 'ias_id key missing from the targeting object'); + }); + it('returns the right values', function () { + const targeting = iasSubModule.getTargetingData(adUnitsCode, config); + expect(targeting['one-div-id']['adt']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['alc']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['dlm']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['drg']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['hat']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['off']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['vio']).to.be.eq('veryLow'); + expect(targeting['one-div-id']['fr']).to.be.eq('false'); + expect(targeting['one-div-id']['ias_id']).to.be.eq('4813f7a2-1f22-11ec-9bfd-0a1107f94461'); + }); + }) }); }); @@ -110,7 +133,10 @@ const config = { name: 'ias', waitForIt: true, params: { - pubId: 1234 + pubId: 1234, + keyMappings: { + 'id': 'ias_id' + } } }; diff --git a/test/spec/modules/id5AnalyticsAdapter_spec.js b/test/spec/modules/id5AnalyticsAdapter_spec.js index be5998967c9..83951c3a6e9 100644 --- a/test/spec/modules/id5AnalyticsAdapter_spec.js +++ b/test/spec/modules/id5AnalyticsAdapter_spec.js @@ -2,7 +2,7 @@ import adapterManager from '../../../src/adapterManager.js'; import id5AnalyticsAdapter from '../../../modules/id5AnalyticsAdapter.js'; import { expect } from 'chai'; import sinon from 'sinon'; -import events from '../../../src/events.js'; +import * as events from '../../../src/events.js'; import constants from '../../../src/constants.json'; import { generateUUID } from '../../../src/utils.js'; diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index debde20e4c0..02020fc2d3a 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -13,7 +13,7 @@ import { import { init, requestBidsHook, setSubmoduleRegistry, coreStorage } from 'modules/userId/index.js'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; diff --git a/test/spec/modules/idWardRtdProvider_spec.js b/test/spec/modules/idWardRtdProvider_spec.js new file mode 100644 index 00000000000..949365baec6 --- /dev/null +++ b/test/spec/modules/idWardRtdProvider_spec.js @@ -0,0 +1,113 @@ +import {config} from 'src/config.js'; +import {getRealTimeData, idWardRtdSubmodule, storage} from 'modules/idWardRtdProvider.js'; + +describe('idWardRtdProvider', function() { + let getDataFromLocalStorageStub; + + const testReqBidsConfigObj = { + adUnits: [ + { + bids: ['bid1', 'bid2'] + } + ] + }; + + const onDone = function() { return true }; + + const cmoduleConfig = { + 'name': 'idWard', + 'params': { + 'cohortStorageKey': 'cohort_ids' + } + } + + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + describe('idWardRtdSubmodule', function() { + it('successfully instantiates', function () { + expect(idWardRtdSubmodule.init()).to.equal(true); + }); + }); + + describe('Get Real-Time Data', function() { + it('gets rtd from local storage', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + segtax: 503 + } + }; + + const bidConfig = {}; + + const rtdUserObj1 = { + name: 'id-ward.com', + ext: { + segtax: 503 + }, + segment: [ + { + id: 'TCZPQOWPEJG3MJOTUQUF793A' + }, + { + id: '93SUG3H540WBJMYNT03KX8N3' + } + ] + }; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + }); + + it('do not set rtd if local storage empty', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + segtax: 503 + } + }; + + const bidConfig = {}; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(null); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2).to.be.undefined; + }); + + it('do not set rtd if local storage has incorrect value', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + segtax: 503 + } + }; + + const bidConfig = {}; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns('wrong cohort ids value'); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2).to.be.undefined; + }); + + it('should initalise and return with config', function () { + expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) + }); + }); +}); diff --git a/test/spec/modules/idxIdSystem_spec.js b/test/spec/modules/idxIdSystem_spec.js index 14cd9a88d13..5cecc12c253 100644 --- a/test/spec/modules/idxIdSystem_spec.js +++ b/test/spec/modules/idxIdSystem_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; import { config } from 'src/config.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, idxIdSubmodule } from 'modules/idxIdSystem.js'; diff --git a/test/spec/modules/imRtdProvider_spec.js b/test/spec/modules/imRtdProvider_spec.js index 58410dc0e38..6d92440a144 100644 --- a/test/spec/modules/imRtdProvider_spec.js +++ b/test/spec/modules/imRtdProvider_spec.js @@ -1,6 +1,7 @@ import { imRtdSubmodule, storage, + getBidderFunction, getCustomBidderFunction, setRealTimeData, getRealTimeData, @@ -47,6 +48,59 @@ describe('imRtdProvider', function () { }) }) + describe('getBidderFunction', function () { + const assumedBidder = [ + 'ix', + 'pubmatic', + 'fluct' + ]; + assumedBidder.forEach(bidderName => { + it(`should return bidderFunction with assumed bidder: ${bidderName}`, function () { + expect(getBidderFunction(bidderName)).to.exist.and.to.be.a('function'); + }); + + it(`should return bid with correct key data: ${bidderName}`, function () { + const bid = {bidder: bidderName}; + expect(getBidderFunction(bidderName)(bid, {'im_segments': ['12345', '67890']})).to.equal(bid); + }); + it(`should return bid without data: ${bidderName}`, function () { + const bid = {bidder: bidderName}; + expect(getBidderFunction(bidderName)(bid, '')).to.equal(bid); + }); + }); + it(`should return null with unexpected bidder`, function () { + expect(getBidderFunction('test')).to.equal(null); + }); + describe('fluct bidder function', function () { + it('should return a bid w/o im_segments if not any exists', function () { + const bid = {bidder: 'fluct'}; + expect(getBidderFunction('fluct')(bid, '')).to.eql(bid); + }); + it('should return a bid w/ im_segments if any exists', function () { + const bid = { + bidder: 'fluct', + params: { + kv: { + foo: ['foo1'] + } + } + }; + expect(getBidderFunction('fluct')(bid, {im_segments: ['12345', '67890']})) + .to.eql( + { + bidder: 'fluct', + params: { + kv: { + foo: ['foo1'], + imsids: ['12345', '67890'] + } + } + } + ); + }); + }); + }) + describe('getCustomBidderFunction', function () { it('should return config function', function () { const config = { diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 095e50f0c66..9dcc11f5aa1 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,14 +1,14 @@ import { expect } from 'chai'; -import { ImproveDigitalAdServerJSClient, spec } from 'modules/improvedigitalBidAdapter.js'; +import { spec } from 'modules/improvedigitalBidAdapter.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; describe('Improve Digital Adapter Tests', function () { - const idClient = new ImproveDigitalAdServerJSClient('hb'); - - const METHOD = 'GET'; - const URL = 'https://ice.360yield.com/hb'; - const PARAM_PREFIX = 'jsonp='; + const METHOD = 'POST'; + const URL = 'https://ad.360yield.com/pb'; + const INSTREAM_TYPE = 1; + const OUTSTREAM_TYPE = 3; const simpleBidRequest = { bidder: 'improvedigital', @@ -22,10 +22,10 @@ describe('Improve Digital Adapter Tests', function () { auctionId: '192721e36a0239', mediaTypes: { banner: { - sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] + sizes: [[300, 250], [160, 600]] } }, - sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] + sizes: [[300, 250], [160, 600]] }; const videoParams = { @@ -53,7 +53,7 @@ describe('Improve Digital Adapter Tests', function () { const multiFormatBidRequest = utils.deepClone(simpleBidRequest); multiFormatBidRequest.mediaTypes = { banner: { - sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] + sizes: [[300, 250], [160, 600]] }, video: { context: 'outstream', @@ -92,7 +92,8 @@ describe('Improve Digital Adapter Tests', function () { gdprConsent: { consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', vendorData: {}, - gdprApplies: true + gdprApplies: true, + addtlConsent: '1~1.35.41.101', }, }; @@ -144,66 +145,85 @@ describe('Improve Digital Adapter Tests', function () { describe('buildRequests', function () { it('should make a well-formed request objects', function () { - const requests = spec.buildRequests([simpleBidRequest], bidderRequest); - expect(requests).to.be.an('array'); - expect(requests.length).to.equal(1); - - const request = requests[0]; + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); + const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); expect(request.url).to.equal(URL); expect(request.bidderRequest).to.deep.equal(bidderRequest); - expect(request.data.substring(0, PARAM_PREFIX.length)).to.equal(PARAM_PREFIX); - - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request).to.be.an('object'); - expect(params.bid_request.id).to.be.a('string'); - expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); - expect(params.bid_request.gdpr).to.not.exist; - expect(params.bid_request.us_privacy).to.not.exist; - expect(params.bid_request.schain).to.not.exist; - expect(params.bid_request.user).to.not.exist; - expect(params.bid_request.imp).to.deep.equal([ + + 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.be.an('object'); + expect(payload.device).to.be.an('object'); + expect(payload.user).to.not.exist; + expect(payload.imp).to.deep.equal([ { id: '33e9500b21129f', - pid: 1053688, - tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', - banner: {} + secure: 0, + ext: { + bidder: { + placementId: 1053688, + } + }, + banner: { + format: [ + {w: 300, h: 250}, + {w: 160, h: 600}, + ] + } } ]); + getConfigStub.restore(); }); it('should make a well-formed request object for multi-format ad unit', function () { - const requests = spec.buildRequests([multiFormatBidRequest], multiFormatBidderRequest); - expect(requests).to.be.an('array'); - expect(requests.length).to.equal(1); - - const request = requests[0]; + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); + const request = spec.buildRequests([multiFormatBidRequest], multiFormatBidderRequest)[0]; + expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); expect(request.url).to.equal(URL); expect(request.bidderRequest).to.deep.equal(multiFormatBidderRequest); - expect(request.data.substring(0, PARAM_PREFIX.length)).to.equal(PARAM_PREFIX); - - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request).to.be.an('object'); - expect(params.bid_request.id).to.be.a('string'); - expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); - expect(params.bid_request.gdpr).to.not.exist; - expect(params.bid_request.us_privacy).to.not.exist; - expect(params.bid_request.imp).to.deep.equal([ + + const payload = JSON.parse(request.data); + expect(payload).to.be.an('object'); + expect(payload.imp).to.deep.equal([ { id: '33e9500b21129f', - pid: 1053688, - tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', - banner: {} + secure: 0, + ext: { + bidder: { + placementId: 1053688, + } + }, + video: { + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + }, + banner: { + format: [ + {w: 300, h: 250}, + {w: 160, h: 600}, + ] + } } ]); + getConfigStub.restore(); }); it('should set placementKey and publisherId for smart tags', function () { - const requests = spec.buildRequests([simpleSmartTagBidRequest], bidderRequest); - const params = JSON.parse(decodeURIComponent(requests[0].data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].pubid).to.equal(1032); - expect(params.bid_request.imp[0].pkey).to.equal('data_team_test_hb_smoke_test'); + const payload = JSON.parse(spec.buildRequests([simpleSmartTagBidRequest], bidderRequest)[0].data); + expect(payload.imp[0].ext.bidder.publisherId).to.equal(1032); + expect(payload.imp[0].ext.bidder.placementKey).to.equal('data_team_test_hb_smoke_test'); }); it('should add keyValues', function () { @@ -214,101 +234,92 @@ describe('Improve Digital Adapter Tests', function () { ] }; bidRequest.params.keyValues = keyValues; - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].kvw).to.deep.equal(keyValues); + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].ext.bidder.keyValues).to.deep.equal(keyValues); }); - it('should add single size filter', function () { - const bidRequest = Object.assign({}, simpleBidRequest); - const size = { - w: 800, - h: 600 - }; - bidRequest.params.size = size; - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner).to.deep.equal(size); - // When single size filter is set, format shouldn't be populated. This - // is to maintain backward compatibily - expect(params.bid_request.imp[0].banner.format).to.not.exist; - }); + // it('should add single size filter', function () { + // const bidRequest = Object.assign({}, simpleBidRequest); + // const size = { + // w: 800, + // h: 600 + // }; + // bidRequest.params.size = size; + // const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest).data); + // expect(payload.imp[0].banner).to.deep.equal(size); + // // When single size filter is set, format shouldn't be populated. This + // // is to maintain backward compatibily + // expect(payload.imp[0].banner.format).to.not.exist; + // }); it('should add currency', function () { const bidRequest = Object.assign({}, simpleBidRequest); const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].currency).to.equal('JPY'); + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.cur).to.deep.equal(['JPY']); getConfigStub.restore(); }); it('should add bid floor', function () { const bidRequest = Object.assign({}, simpleBidRequest); - let request = spec.buildRequests([bidRequest], bidderRequest)[0]; - let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); // Floor price currency shouldn't be populated without a floor price - expect(params.bid_request.imp[0].bidfloorcur).to.not.exist; + expect(payload.imp[0].bidfloorcur).to.not.exist; // Default floor price currency bidRequest.params.bidFloor = 0.05; - request = spec.buildRequests([bidRequest], bidderRequest)[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); - expect(params.bid_request.imp[0].bidfloorcur).to.equal('USD'); + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); // Floor price currency bidRequest.params.bidFloorCur = 'eUR'; - request = spec.buildRequests([bidRequest])[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); - expect(params.bid_request.imp[0].bidfloorcur).to.equal('EUR'); + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + expect(payload.imp[0].bidfloorcur).to.equal('EUR'); // getFloor defined -> use it over bidFloor let getFloorResponse = { currency: 'USD', floor: 3 }; bidRequest.getFloor = () => getFloorResponse; - request = spec.buildRequests([bidRequest])[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].bidfloor).to.equal(3); - expect(params.bid_request.imp[0].bidfloorcur).to.equal('USD'); + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].bidfloor).to.equal(3); + // expect(payload.imp[0].bidfloorcur).to.equal('USD'); }); it('should add GDPR consent string', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequestGdpr)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.gdpr).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data); + expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); + expect(payload.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(payload.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]); }); it('should add CCPA consent string', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], { uspConsent: '1YYY' })[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.us_privacy).to.equal('1YYY'); + const request = spec.buildRequests([bidRequest], {...bidderRequest, ...{ uspConsent: '1YYY' }}); + const payload = JSON.parse(request[0].data); + expect(payload.regs.ext.us_privacy).to.equal('1YYY'); }); it('should add referrer', function () { const bidRequest = Object.assign({}, simpleBidRequest); const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.referrer).to.equal('https://blah.com/test.html'); + const payload = JSON.parse(request.data); + expect(payload.site.page).to.equal('https://blah.com/test.html'); }); it('should not add video params for banner', function () { const bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.not.exist; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.not.exist; }); - it('should add ad type for instream video', function () { + it('should add correct placement value for instream and outstream video', function () { let bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); - bidRequest.mediaType = 'video'; - let request = spec.buildRequests([bidRequest], bidderRequest)[0]; - let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); - expect(params.bid_request.imp[0].video).to.not.exist; + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video).to.not.exist; bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); bidRequest.mediaTypes = { @@ -317,32 +328,45 @@ describe('Improve Digital Adapter Tests', function () { playerSize: [640, 480] } }; - request = spec.buildRequests([bidRequest], bidderRequest)[0]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); - expect(params.bid_request.imp[0].video).to.not.exist; - }); - - it('should not set ad type for outstream video', function() { - const request = spec.buildRequests([outstreamBidRequest])[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].ad_types).to.not.exist; - expect(params.bid_request.imp[0].video).to.not.exist; - }); - - it('should not set ad type for multi-format bids', function() { - const request = spec.buildRequests([multiFormatBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].ad_types).to.not.exist; - expect(params.bid_request.imp[0].video).to.not.exist; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(1); + bidRequest.mediaTypes.video.context = 'outstream'; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(3); }); it('should set video params for instream', function() { const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + delete bidRequest.mediaTypes.video.playerSize; + const videoParams = { + mimes: ['video/mp4'], + skip: 1, + skipmin: 5, + skipafter: 30, + minduration: 15, + maxduration: 60, + startdelay: 5, + minbitrate: 500, + maxbitrate: 2000, + w: 1024, + h: 640, + placement: INSTREAM_TYPE, + }; bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest])[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal(videoParams); + }); + + it('should set video playerSize over video params', () => { + const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + bidRequest.params.video = { + w: 1024, h: 640 + } + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video.h).equal(480); + expect(payload.imp[0].video.w).equal(640); }); it('should set skip params only if skip=1', function() { @@ -355,22 +379,28 @@ describe('Improve Digital Adapter Tests', function () { } bidRequest.params.video = videoTest; let request = spec.buildRequests([bidRequest])[0]; - let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoTest); + 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]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal({ skip: 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]; - params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.not.exist; + 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() { @@ -385,51 +415,36 @@ describe('Improve Digital Adapter Tests', function () { videoTestInvParam.blah = 1; bidRequest.params.video = videoTestInvParam; let request = spec.buildRequests([bidRequest])[0]; - let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoTest); + let payload = JSON.parse(request.data); + expect(payload.imp[0].video.blah).not.to.exist; }); it('should set video params for outstream', function() { const bidRequest = JSON.parse(JSON.stringify(outstreamBidRequest)); bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest])[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal({...{ + mimes: ['video/mp4'], + placement: OUTSTREAM_TYPE, + w: bidRequest.mediaTypes.video.playerSize[0], + h: bidRequest.mediaTypes.video.playerSize[1], + }, + ...videoParams}); }); - + // it('should set video params for multi-format', function() { const bidRequest = JSON.parse(JSON.stringify(multiFormatBidRequest)); bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest])[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); - }); - - it('should not set Prebid sizes in bid request for instream video', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const request = spec.buildRequests([instreamBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner.format).to.not.exist; - getConfigStub.restore(); - }); - - it('should not set Prebid sizes in bid request for outstream video', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const request = spec.buildRequests([outstreamBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner.format).to.not.exist; - getConfigStub.restore(); - }); - - it('should not set Prebid sizes in multi-format bid request', function () { - const getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const request = spec.buildRequests([multiFormatBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner.format).to.not.exist; - getConfigStub.restore(); + const payload = JSON.parse(request.data); + const testVideoParams = Object.assign({ + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + }, videoParams); + expect(payload.imp[0].video).to.deep.equal(testVideoParams); }); it('should add schain', function () { @@ -437,8 +452,8 @@ describe('Improve Digital Adapter Tests', function () { const bidRequest = Object.assign({}, simpleBidRequest); bidRequest.schain = schain; const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.schain).to.equal(schain); + const payload = JSON.parse(request.data); + expect(payload.source.ext.schain).to.equal(schain); }); it('should add eids', function () { @@ -453,8 +468,8 @@ describe('Improve Digital Adapter Tests', function () { const bidRequest = Object.assign({}, simpleBidRequest); bidRequest.userId = userId; const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.user).to.deep.equal(expectedUserObject); + const payload = JSON.parse(request.data); + expect(payload.user).to.deep.equal(expectedUserObject); }); it('should return 2 requests', function () { @@ -484,8 +499,8 @@ describe('Improve Digital Adapter Tests', function () { const getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner).to.deep.equal({ + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner).to.deep.equal({ format: [ { w: 300, h: 250 }, { w: 160, h: 600 } @@ -504,8 +519,8 @@ describe('Improve Digital Adapter Tests', function () { }; bidRequest.params.size = size; const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); - expect(params.bid_request.imp[0].banner).to.deep.equal({ + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner).to.deep.equal({ format: [ { w: 300, h: 250 }, { w: 160, h: 600 } @@ -513,347 +528,397 @@ describe('Improve Digital Adapter Tests', function () { }); getConfigStub.restore(); }); + + 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 getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('ortb2.app').returns({ content: 'XYZ' }); + let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let payload = JSON.parse(request.data); + expect(payload.site).does.not.exist; + expect(payload.app).does.exist; + expect(payload.app.content).does.exist.and.equal('XYZ'); + getConfigStub.restore(); + }); + + it('should not set site when app is defined in CONFIG', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('app').returns({ content: 'XYZ' }); + let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let payload = JSON.parse(request.data); + expect(payload.site).does.not.exist; + expect(payload.app).does.exist; + expect(payload.app.content).does.exist.and.equal('XYZ'); + getConfigStub.restore(); + }); + + it('should set correct site params', function () { + let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('site').returns({ + content: 'XYZ', + page: 'https://improveditigal.com/', + domain: 'improveditigal.com' + }); + let request = spec.buildRequests([simpleBidRequest], 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]; + 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'); + + getConfigStub.withArgs('ortb2.site').returns({ + content: 'ZZZ', + }); + request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[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'); + expect(payload.site.domain).does.exist.and.equal('blah.com'); + getConfigStub.restore(); + }); + + it('should set pageUrl as site param', function () { + let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('pageUrl').returns('https://improvidigital.com/test-page'); + let request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + let payload = JSON.parse(request.data); + expect(payload.site.page).does.exist.and.equal('https://improvidigital.com/test-page'); + expect(payload.site.domain).does.exist.and.equal('improvidigital.com'); + getConfigStub.reset(); + + getConfigStub.withArgs('pageUrl').returns(undefined); + request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + payload = JSON.parse(request.data); + expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); + expect(payload.site.domain).does.exist.and.equal('blah.com'); + getConfigStub.restore(); + }); + + it('should set site when app not available', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('app').returns(undefined); + let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let payload = JSON.parse(request.data); + expect(payload.site).does.exist; + expect(payload.app).does.not.exist; + getConfigStub.restore(); + }); }); const serverResponse = { 'body': { - 'id': '687a06c541d8d1', - 'site_id': 191642, - 'bid': [ + 'id': '99f9a9e6-5126-425b-822c-8b4edad2a719', + 'cur': 'EUR', + 'seatbid': [ { - 'isNet': false, - 'id': '33e9500b21129f', - 'advid': '5279', - 'price': 1.45888594164456, - 'nurl': 'https://ice.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', - 'h': 290, - 'pid': 1053688, - 'sync': [ - 'https://link1', - 'https://link2' + 'bid': [ + { + 'ext': { + 'improvedigital': { + 'line_item_id': 320896, + 'bidder_id': 0, + 'brand_name': '', + 'buying_type': 'classic', + 'agency_id': '0' + } + }, + 'exp': 120, + 'crid': '510265', + 'price': 1.9200543539802946, + 'id': '35adfe19-d6e9-46b9-9f7d-20da7026b965', + 'w': 728, + 'impid': '33e9500b21129f', + 'h': 90, + 'adm': '  ', + 'cid': '123159' + } ], - 'crid': '422031', - 'w': 600, - 'cid': '99006', - 'adm': 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' + 'seat': 'improvedigital' } - ], - 'debug': '' + ] } }; const serverResponseTwoBids = { 'body': { - 'id': '687a06c541d8d1', - 'site_id': 191642, - 'bid': [ - serverResponse.body.bid[0], + 'id': '99f9a9e6-5126-425b-822c-8b4edad2a719', + 'cur': 'EUR', + 'seatbid': [ { - 'isNet': true, - 'id': '1234', - 'advid': '5280', - 'price': 1.23, - 'nurl': 'https://link/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', - 'h': 400, - 'pid': 1053688, - 'sync': [ - 'https://link3' + 'bid': [ + { + 'ext': { + 'improvedigital': { + 'line_item_id': 320896, + 'bidder_id': 0, + 'brand_name': '', + 'buying_type': 'classic', + 'agency_id': '0' + } + }, + 'exp': 120, + 'crid': '510265', + 'price': 1.9200543539802946, + 'id': '35adfe19-d6e9-46b9-9f7d-20da7026b965', + 'w': 728, + 'impid': '33e9500b21129f', + 'h': 90, + 'adm': '  ', + 'cid': '123159' + }, + { + 'ext': { + 'improvedigital': { + 'line_item_id': 320896, + 'bidder_id': 0, + 'brand_name': '', + 'buying_type': 'classic', + 'agency_id': '0' + } + }, + 'exp': 120, + 'crid': '479163', + 'price': 1.9200543539802946, + 'id': '83c8d524-0955-4d0c-b558-4c9f3600e09b', + 'w': 300, + 'impid': '33e9500b21129f', + 'h': 250, + 'adm': '  ', + 'cid': '123159' + } ], - 'crid': '422033', - 'w': 700, - 'cid': '99006', - 'adm': 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' + 'seat': 'improvedigital_improvedigital' } ], - 'debug': '' + ext: { + improvedigital: { + sync: [ + 'https://link1', + 'https://link2', + 'https://link3', + ] + } + } } }; const serverResponseNative = { body: { - id: '687a06c541d8d1', - site_id: 191642, - bid: [ + 'id': '8201e669-bbbf-4f61-b9a2-4cb854033c82', + 'cur': 'EUR', + 'seatbid': [ { - isNet: false, - id: '33e9500b21129f', - advid: '5279', - price: 1.45888594164456, - nurl: 'https://ice.360yield.com/imp_pixel?ic=wVm', - h: 290, - pid: 1053688, - sync: [ - 'https://link1', - 'https://link2' - ], - crid: '422031', - w: 600, - cid: '99006', - native: { - assets: [ - { - title: { - text: 'Native title' - } - }, - { - data: { - type: 1, - value: 'Improve Digital' - } - }, - { - data: { - type: 2, - value: 'Native body' - } - }, - { - data: { - type: 3, - value: '4' // rating - } - }, - { - data: { - type: 4, - value: '10105' // likes - } - }, - { - data: { - type: 5, - value: '150000' // downloads - } - }, - { - data: { - type: 6, - value: '3.99' // price - } - }, - { - data: { - type: 7, - value: '4.49' // salePrice - } - }, - { - data: { - type: 8, - value: '(123) 456-7890' // phone - } - }, - { - data: { - type: 9, - value: '123 Main Street, Anywhere USA' // address - } - }, - { - data: { - type: 10, - value: 'body2' + 'bid': [ + { + 'ext': { + 'improvedigital': { + 'line_item_id': 411331, + 'bidder_id': 0, + 'brand_name': '', + 'buying_type': 'classic', + 'agency_id': '0', + 'is_net': true } }, - { - data: { - type: 11, - value: 'https://myurl.com' // displayUrl - } - }, - { - data: { - type: 12, - value: 'Do it' // cta - } - }, - { - img: { - type: 1, - url: 'Should get ignored', - h: 300, - w: 400 - } - }, - { - img: { - type: 2, - url: 'https://blah.com/icon.jpg', - h: 30, - w: 40 - } - - }, - { - img: { - type: 3, - url: 'https://blah.com/image.jpg', - h: 200, - w: 800 - } - } - ], - link: { - url: 'https://advertiser.com', - clicktrackers: [ - 'https://click.tracker.com/click?impid=123' - ] - }, - imptrackers: [ - 'https://imptrack1.com', - 'https://imptrack2.com' - ], - jstracker: '', - privacy: 'https://www.myprivacyurl.com' - } + '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\":\"\"}", + 'impid': '2d7a7db325c6f', + 'cid': '196108' + } + ], + 'seat': 'improvedigital' } - ], - debug: '' + ] } }; const serverResponseVideo = { 'body': { - 'id': '687a06c541d8d1', - 'site_id': 191642, - 'bid': [ + 'id': '8ed20675-8934-430c-b645-1ccd17b35839', + 'cur': 'EUR', + 'seatbid': [ { - 'isNet': false, - 'id': '33e9500b21129f', - 'advid': '5279', - 'price': 1.45888594164456, - 'nurl': 'http://ice.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', - 'h': 290, - 'pid': 1053688, - 'sync': [ - 'http://link1', - 'http://link2' + 'bid': [ + { + 'ext': { + 'improvedigital': { + 'line_item_id': 321329, + 'bidder_id': 0, + 'brand_name': '', + 'buying_type': 'classic', + 'agency_id': '0' + } + }, + 'exp': 120, + 'crid': '484367', + 'price': 9.600271769901472, + 'id': 'b131fd7b-5759-4b72-800e-60e69291e7d9', + 'adomain': [ + 'improvedigital.com' + ], + 'impid': '33e9500b21129f', + 'adm': '', + 'w': 640, + 'h': 480, + 'cid': '123159' + } ], - 'crid': '422031', - 'w': 600, - 'cid': '99006', - 'adm': '', - 'ad_type': 'video' + 'seat': 'improvedigital' } ], - 'debug': '' } }; - const nativeEventtrackers = [ - { - event: 1, - method: 1, - url: 'https://www.mytracker.com/imptracker' - }, - { - event: 1, - method: 2, - url: 'https://www.mytracker.com/tracker.js' + const serverResponseRazr = { + body: { + 'id': '2adac6a5fe04df', + 'cur': 'EUR', + 'ext': { + 'improvedigital': { + 'sync': [ + 'https://d5p.de17a.com/getuid/improve_digital?publisher_user_id=ce26f11e-567a-4eb7-bf94-51752e293ca5&publisher_dsp_id=61&publisher_call_type=redirect&gdpr=1&gdpr_consent=CPU22FrPU22FrAcABBENCDCsAP_AAH_AAChQIltf_X__b3_j-_5_f_t0eY1P9_7_v-0zjhfdt-8N3f_X_L8X42M7vF36pq4KuR4Eu3LBIQdlHOHcTUmw6okVrzPsbk2cr7NKJ7PEmnMbO2dYGH9_n93TuZKY7______z_v-v_v____f_7-3_3__5_3---_e_V_99zLv9____39nP___9v-_9_____4IhgEmGpeQBdmWODJtGlUKIEYVhIdAKACigGFoisIHVwU7K4CfUELABCagJwIgQYgowYBAAIJAEhEQEgB4IBEARAIAAQAqwEIACNgEFgBYGAQACgGhYgRQBCBIQZHBUcpgQFSLRQT2ViCUHexphCGWeBFAo_oqEBGs0QLAyEhYOY4AkBLxZIHmKF8gAAAAA.f_gAD_gAAAAA&publisher_redirecturl=https://euw-ice.360yield.com/match' + ] + } + }, + 'seatbid': [ + { + 'bid': [ + { + 'ext': { + 'improvedigital': { + 'line_item_id': 410573, + 'bidder_id': 0, + 'brand_name': '', + 'buying_type': 'classic', + 'agency_id': '0' + } + }, + 'exp': 120, + 'crid': '544063', + 'price': 1.9199364935359489, + 'id': '1fcf4dd8-a783-48ed-b59c-8fc8eeccb024', + 'adomain': [ + 'improvedigital.com' + ], + 'w': 970, + 'impid': '33e9500b21129f', + 'h': 250, + 'adm': '\n\n\n\n\n\n\n\n ', + 'cid': '187354' + } + ], + 'seat': 'improvedigital' + } + ] } - ]; + }; describe('interpretResponse', function () { const expectedBid = [ { - 'ad': '', - 'creativeId': '422031', - 'cpm': 1.45888594164456, - 'currency': 'USD', - 'height': 290, - 'mediaType': 'banner', - 'netRevenue': false, - 'requestId': '33e9500b21129f', - 'ttl': 300, - 'width': 600 + requestId: '33e9500b21129f', + cpm: 1.9200543539802946, + currency: 'EUR', + width: 728, + height: 90, + ttl: 300, + ad: '  ', + creativeId: '510265', + dealId: 320896, + netRevenue: false, + mediaType: BANNER, + meta: { + advertiserDomains: [] + } } ]; const expectedTwoBids = [ expectedBid[0], { - 'ad': '', - 'creativeId': '422033', - 'cpm': 1.23, - 'currency': 'USD', - 'height': 400, - 'mediaType': 'banner', - 'netRevenue': true, - 'requestId': '1234', - 'ttl': 300, - 'width': 700 - } - ]; - - const expectedBidNative = [ - { - mediaType: 'native', - creativeId: '422031', - cpm: 1.45888594164456, - currency: 'USD', - height: 290, - netRevenue: false, requestId: '33e9500b21129f', + cpm: 1.9200543539802946, + currency: 'EUR', + width: 300, + height: 250, ttl: 300, - width: 600, - native: { - title: 'Native title', - body: 'Native body', - body2: 'body2', - cta: 'Do it', - sponsoredBy: 'Improve Digital', - rating: '4', - likes: '10105', - downloads: '150000', - price: '3.99', - salePrice: '4.49', - phone: '(123) 456-7890', - address: '123 Main Street, Anywhere USA', - displayUrl: 'https://myurl.com', - icon: { - url: 'https://blah.com/icon.jpg', - height: 30, - width: 40 - }, - image: { - url: 'https://blah.com/image.jpg', - height: 200, - width: 800 - }, - clickUrl: 'https://advertiser.com', - clickTrackers: ['https://click.tracker.com/click?impid=123'], - impressionTrackers: [ - 'https://ice.360yield.com/imp_pixel?ic=wVm', - 'https://imptrack1.com', - 'https://imptrack2.com' - ], - javascriptTrackers: '', - privacyLink: 'https://www.myprivacyurl.com' + ad: '  ', + creativeId: '479163', + dealId: 320896, + netRevenue: false, + mediaType: BANNER, + meta: { + advertiserDomains: [] } } ]; const expectedBidInstreamVideo = [ { - 'vastXml': '', - 'creativeId': '422031', - 'cpm': 1.45888594164456, - 'currency': 'USD', - 'height': 290, - 'mediaType': 'video', - 'netRevenue': false, - 'requestId': '33e9500b21129f', - 'ttl': 300, - 'width': 600 + requestId: '33e9500b21129f', + cpm: 9.600271769901472, + currency: 'EUR', + ttl: 300, + vastXml: '', + creativeId: '484367', + dealId: 321329, + netRevenue: false, + mediaType: VIDEO, + meta: { + advertiserDomains: ['improvedigital.com'], + } } ]; const expectedBidOutstreamVideo = utils.deepClone(expectedBidInstreamVideo); expectedBidOutstreamVideo[0].adResponse = { - content: expectedBidOutstreamVideo[0].vastXml, - height: expectedBidOutstreamVideo[0].height, - width: expectedBidOutstreamVideo[0].width + content: expectedBidOutstreamVideo[0].vastXml }; it('should return a well-formed display bid', function () { @@ -875,50 +940,35 @@ describe('Improve Digital Adapter Tests', function () { const response = JSON.parse(JSON.stringify(serverResponse)); let bids; - delete response.body.bid[0].lid; - response.body.bid[0].buying_type = 'deal_id'; + 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}); expect(bids[0].dealId).to.not.exist; - response.body.bid[0].lid = 268515; - delete response.body.bid[0].buying_type; + 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}); expect(bids[0].dealId).to.not.exist; - response.body.bid[0].lid = 268515; - response.body.bid[0].buying_type = 'rtb'; + 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}); expect(bids[0].dealId).to.not.exist; - response.body.bid[0].lid = 268515; - response.body.bid[0].buying_type = 'classic'; + 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}); expect(bids[0].dealId).to.equal(268515); - response.body.bid[0].lid = 268515; - response.body.bid[0].buying_type = 'deal_id'; + 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}); expect(bids[0].dealId).to.equal(268515); - - response.body.bid[0].lid = [ 268515, 12456, 34567 ]; - response.body.bid[0].buying_type = 'deal_id'; - bids = spec.interpretResponse(response, {bidderRequest}); - expect(bids[0].dealId).to.not.exist; - - response.body.bid[0].lid = [ 268515, 12456, 34567 ]; - response.body.bid[0].buying_type = [ 'deal_id', 'classic' ]; - bids = spec.interpretResponse(response, {bidderRequest}); - expect(bids[0].dealId).to.not.exist; - - response.body.bid[0].lid = [ 268515, 12456, 34567 ]; - response.body.bid[0].buying_type = [ 'rtb', 'deal_id', 'deal_id' ]; - bids = spec.interpretResponse(response, {bidderRequest}); - expect(bids[0].dealId).to.equal(12456); }); it('should set currency', function () { const response = JSON.parse(JSON.stringify(serverResponse)); - response.body.bid[0].currency = 'eur'; + response.body.cur = 'eur'; const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].currency).to.equal('EUR'); }); @@ -928,35 +978,35 @@ describe('Improve Digital Adapter Tests', function () { let bids; // Price missing or 0 - response.body.bid[0].price = 0; + response.body.seatbid[0].bid[0].price = 0; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); - delete response.body.bid[0].price; + delete response.body.seatbid[0].bid[0]; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); - response.body.bid[0].price = null; + response.body.seatbid[0].bid[0] = []; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); // errorCode present response = JSON.parse(JSON.stringify(serverResponse)); - response.body.bid[0].errorCode = undefined; + response.body.seatbid[0].bid[0].errorCode = undefined; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); // adm and native missing response = JSON.parse(JSON.stringify(serverResponse)); - delete response.body.bid[0].adm; + delete response.body.seatbid[0].bid[0].adm; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); - response.body.bid[0].adm = null; + response.body.seatbid[0].bid[0].adm = null; bids = spec.interpretResponse(response, {bidderRequest}); expect(bids).to.deep.equal([]); }); it('should set netRevenue', function () { const response = JSON.parse(JSON.stringify(serverResponse)); - response.body.bid[0].isNet = true; + response.body.seatbid[0].bid[0].ext.improvedigital.is_net = true; const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].netRevenue).to.equal(true); }); @@ -964,30 +1014,31 @@ describe('Improve Digital Adapter Tests', function () { it('should set advertiserDomains', function () { const adomain = ['domain.com']; const response = JSON.parse(JSON.stringify(serverResponse)); - response.body.bid[0].adomain = adomain; + response.body.seatbid[0].bid[0].adomain = adomain; const bids = spec.interpretResponse(response, {bidderRequest}); expect(bids[0].meta.advertiserDomains).to.equal(adomain); }); - + // // Native ads it('should return a well-formed native ad bid', function () { - let bids = spec.interpretResponse(serverResponseNative, {bidderRequest}); - expect(bids[0].ortbNative).to.deep.equal(serverResponseNative.body.bid[0].native); - delete bids[0].ortbNative; - expect(bids).to.deep.equal(expectedBidNative); - - // eventtrackers - const response = JSON.parse(JSON.stringify(serverResponseNative)); - const expectedBids = JSON.parse(JSON.stringify(expectedBidNative)); - response.body.bid[0].native.eventtrackers = nativeEventtrackers; - expectedBids[0].native.impressionTrackers = [ - 'https://ice.360yield.com/imp_pixel?ic=wVm', - 'https://www.mytracker.com/imptracker' - ]; - expectedBids[0].native.javascriptTrackers = ''; - bids = spec.interpretResponse(response, {bidderRequest}); - delete bids[0].ortbNative; - expect(bids).to.deep.equal(expectedBids); + const nativeBidderRequest = JSON.parse(JSON.stringify(bidderRequest)); + nativeBidderRequest.bids[0].bidId = '2d7a7db325c6f'; + delete nativeBidderRequest.bids[0].mediaTypes.banner; + nativeBidderRequest.bids[0].mediaTypes.native = {}; + 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.'); }); // Video @@ -1009,6 +1060,16 @@ describe('Improve Digital Adapter Tests', function () { delete (bids[0].renderer); expect(bids).to.deep.equal(expectedBidOutstreamVideo); }); + + it('should not affect non-RAZR bids', function () { + const bids = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(bids[0].renderer).to.not.exist; + }); + + it('should detect RAZR bids', function () { + const bids = spec.interpretResponse(serverResponseRazr, {bidderRequest}); + expect(bids[0].renderer).to.exist; + }); }); describe('getUserSyncs', function () { diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 305f39c13ac..e14207ba3e0 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, storage } from '../../../modules/insticatorBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js' +import { userSync } from '../../../src/userSync.js'; const USER_ID_KEY = 'hb_insticator_uid'; const USER_ID_DUMMY_VALUE = '74f78609-a92d-4cf1-869f-1b244bbfb5d2'; @@ -383,12 +384,14 @@ describe('InsticatorBidAdapter', function () { width: 300, height: 200, mediaType: 'banner', - ad: 'adm1', - adUnitCode: 'adunit-code-1', meta: { - advertiserDomains: ['test1.com'], + advertiserDomains: [ + 'test1.com' + ], test: 1 - } + }, + ad: 'adm1', + adUnitCode: 'adunit-code-1', }, { requestId: 'bid2', diff --git a/test/spec/modules/intersectionRtdProvider_spec.js b/test/spec/modules/intersectionRtdProvider_spec.js new file mode 100644 index 00000000000..0621c4f67e0 --- /dev/null +++ b/test/spec/modules/intersectionRtdProvider_spec.js @@ -0,0 +1,141 @@ +import {config as _config, config} from 'src/config.js'; +import { expect } from 'chai'; +import * as events from 'src/events.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; +import { intersectionSubmodule } from 'modules/intersectionRtdProvider.js'; +import * as utils from 'src/utils.js'; +import {getGlobal} from 'src/prebidGlobal.js'; +import 'src/prebid.js'; + +describe('Intersection RTD Provider', function () { + let sandbox; + let placeholder; + const pbjs = getGlobal(); + const adUnit = { + code: 'ad-slot-1', + mediaTypes: { + banner: { + sizes: [ [300, 250] ] + } + }, + bids: [ + { + bidder: 'fake' + } + ] + }; + const providerConfig = {name: 'intersection', waitForIt: true}; + const rtdConfig = {realTimeData: {auctionDelay: 200, dataProviders: [providerConfig]}} + describe('IntersectionObserver not supported', function() { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + sandbox = undefined; + }); + it('init should return false', function () { + sandbox.stub(window, 'IntersectionObserver').value(undefined); + expect(intersectionSubmodule.init({})).is.false; + }); + }); + describe('IntersectionObserver supported', function() { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + placeholder = createDiv(); + append(); + const __config = {}; + sandbox.stub(_config, 'getConfig').callsFake(function (path) { + return utils.deepAccess(__config, path); + }); + sandbox.stub(_config, 'setConfig').callsFake(function (obj) { + utils.mergeDeep(__config, obj); + }); + }); + afterEach(function() { + sandbox.restore(); + remove(); + sandbox = undefined; + placeholder = undefined; + pbjs.removeAdUnit(); + }); + it('init should return true', function () { + expect(intersectionSubmodule.init({})).is.true; + }); + it('should set intersection. (request with "adUnitCodes")', function(done) { + pbjs.addAdUnits([utils.deepClone(adUnit)]); + config.setConfig(rtdConfig); + const onDone = sandbox.stub(); + const requestBidObject = {adUnitCodes: [adUnit.code]}; + intersectionSubmodule.init({}); + intersectionSubmodule.getBidRequestData( + requestBidObject, + onDone, + providerConfig + ); + setTimeout(function() { + expect(pbjs.adUnits[0].bids[0]).to.have.property('intersection'); + done(); + }, 200); + }); + it('should set intersection. (request with "adUnits")', function(done) { + config.setConfig(rtdConfig); + const onDone = sandbox.stub(); + const requestBidObject = {adUnits: [utils.deepClone(adUnit)]}; + intersectionSubmodule.init(); + intersectionSubmodule.getBidRequestData( + requestBidObject, + onDone, + providerConfig + ); + setTimeout(function() { + expect(requestBidObject.adUnits[0].bids[0]).to.have.property('intersection'); + done(); + }, 200); + }); + it('should set intersection. (request all)', function(done) { + pbjs.addAdUnits([utils.deepClone(adUnit)]); + config.setConfig(rtdConfig); + const onDone = sandbox.stub(); + const requestBidObject = {}; + intersectionSubmodule.init({}); + intersectionSubmodule.getBidRequestData( + requestBidObject, + onDone, + providerConfig + ); + setTimeout(function() { + expect(pbjs.adUnits[0].bids[0]).to.have.property('intersection'); + done(); + }, 200); + }); + it('should call done due timeout', function(done) { + config.setConfig(rtdConfig); + remove(); + const onDone = sandbox.stub(); + const requestBidObject = {adUnits: [utils.deepClone(adUnit)]}; + intersectionSubmodule.init({}); + intersectionSubmodule.getBidRequestData( + requestBidObject, + onDone, + {...providerConfig, test: 1} + ); + setTimeout(function() { + sinon.assert.calledOnce(onDone); + expect(requestBidObject.adUnits[0].bids[0]).to.not.have.property('intersection'); + done(); + }, 300); + }); + }); + function createDiv() { + const div = document.createElement('div'); + div.id = adUnit.code; + return div; + } + function append() { + placeholder && document.body.appendChild(placeholder); + } + function remove() { + placeholder && placeholder.parentElement && placeholder.parentElement.removeChild(placeholder); + } +}); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 8b92e0ee81b..ae7b30c9f81 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -15,7 +15,7 @@ describe('invibesBidAdapter:', function () { params: { placementId: PLACEMENT_ID }, - adUnitCode: 'test-div', + adUnitCode: 'test-div1', auctionId: 'a1', sizes: [ [300, 250], @@ -30,7 +30,7 @@ describe('invibesBidAdapter:', function () { params: { placementId: 'abcde' }, - adUnitCode: 'test-div', + adUnitCode: 'test-div2', auctionId: 'a2', sizes: [ [300, 250], @@ -48,7 +48,7 @@ describe('invibesBidAdapter:', function () { params: { placementId: PLACEMENT_ID }, - adUnitCode: 'test-div', + adUnitCode: 'test-div1', auctionId: 'a1', sizes: [ [300, 250], @@ -67,7 +67,7 @@ describe('invibesBidAdapter:', function () { params: { placementId: 'abcde' }, - adUnitCode: 'test-div', + adUnitCode: 'test-div2', auctionId: 'a2', sizes: [ [300, 250], @@ -158,6 +158,125 @@ describe('invibesBidAdapter:', function () { expect(request.method).to.equal('GET'); }); + it('sends bid request to custom endpoint via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'placement', + customEndpoint: 'sub.domain.com/Bid/VideoAdContent' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('sub.domain.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to default endpoint when no placement', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to default endpoint when null placement', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: null + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to default endpoint 1 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'placement' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id endpoint 1 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'placement', + domainId: 1001 + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id endpoint 2 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'placement', + domainId: 1002 + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid2.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id by placement 1 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'infeed_ivbs1' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id by placement 2 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'infeed_ivbs2' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid2.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id by placement 10 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'infeed_ivbs10' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid10.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + it('sends cookies with the bid request', function () { const request = spec.buildRequests(bidRequests); expect(request.options.withCredentials).to.equal(true); @@ -223,6 +342,12 @@ describe('invibesBidAdapter:', function () { expect(JSON.parse(request.data.bidParamsJson).placementIds).to.contain(bidRequests[1].params.placementId); }); + it('sends all adUnitCodes', function () { + const request = spec.buildRequests(bidRequests); + expect(JSON.parse(request.data.bidParamsJson).adUnitCodes).to.contain(bidRequests[0].adUnitCode); + expect(JSON.parse(request.data.bidParamsJson).adUnitCodes).to.contain(bidRequests[1].adUnitCode); + }); + it('sends all Placement Ids and userId', function () { const request = spec.buildRequests(bidRequestsWithUserId); expect(JSON.parse(request.data.bidParamsJson).userId).to.exist; @@ -823,6 +948,20 @@ describe('invibesBidAdapter:', function () { } }; + let responseWithAdUnit = { + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }], + BidModel: { + BidVersion: 1, + PlacementId: '12345_test-div1', + AuctionStartTime: Date.now(), + CreativeHtml: '' + }, + UseAdUnitCode: true + }; + var buildResponse = function(placementId, cid, blcids, creativeId) { return { MultipositionEnabled: true, @@ -911,6 +1050,11 @@ describe('invibesBidAdapter:', function () { let secondResult = spec.interpretResponse({body: response}, {bidRequests}); expect(secondResult).to.be.empty; }); + + it('bids using the adUnitCode', function () { + let result = spec.interpretResponse({body: responseWithAdUnit}, {bidRequests}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); }); context('when the response has meta', function () { diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index 3c7da783728..de0459f4714 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -143,6 +143,7 @@ describe('IQZoneBidAdapter', function () { 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'); @@ -368,4 +369,29 @@ describe('IQZoneBidAdapter', function () { 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.smartssp.iqzone.com/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.smartssp.iqzone.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); }); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 59eb9e76c9a..0e1a854b67c 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -2,8 +2,9 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { spec } from 'modules/ixBidAdapter.js'; +import { spec, storage, ERROR_CODES } from '../../../modules/ixBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; +import { deepAccess } from '../../../src/utils.js'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; @@ -172,6 +173,26 @@ describe('IndexexchangeAdapter', function () { } ]; + const DEFAULT_BANNER_VALID_BID_PARAM_NO_SIZE = [ + { + bidder: 'ix', + params: { + siteId: '123' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', + bidId: '1a2b3c4d', + bidderRequestId: '11a22b33c44d', + auctionId: '1aa2bb3cc4dd', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_VIDEO_VALID_BID = [ { bidder: 'ix', @@ -353,6 +374,37 @@ describe('IndexexchangeAdapter', function () { ] }; + const DEFAULT_VIDEO_BID_RESPONSE_WITH_MTYPE_SET = { + cur: 'USD', + id: '1aa2bb3cc4de', + seatbid: [ + { + bid: [ + { + crid: '12346', + adomain: ['www.abcd.com'], + adid: '14851456', + impid: '1a2b3c4e', + cid: '3051267', + price: 110, + id: '2', + mtype: 2, + adm: ' Test In-Stream Video { + expect(imp.ext.siteID).to.be.a('string'); + }); + }); + describe('build requests with price floors', () => { const highFloor = 4.5; const lowFloor = 3.5; @@ -1607,7 +1736,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.banner.format[0].w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); expect(impression.banner.format[0].h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); expect(impression.banner.format[0].ext.sid).to.equal('50'); }); @@ -1621,11 +1750,15 @@ describe('IndexexchangeAdapter', function () { expect(impression.banner.format[0].w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); expect(impression.banner.format[0].h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); expect(impression.banner.format[0].ext.sid).to.equal('abc'); }); describe('first party data', () => { + beforeEach(() => { + config.resetConfig(); + }); + it('should add first party data to page url in bid request if it exists in config', function () { config.setConfig({ ix: { @@ -1719,7 +1852,7 @@ describe('IndexexchangeAdapter', function () { expect(w).to.equal(size[0]); expect(h).to.equal(size[1]); - expect(ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); expect(ext.sid).to.equal(sidValue); }); }); @@ -1884,7 +2017,7 @@ describe('IndexexchangeAdapter', function () { expect(w).to.equal(size[0]); expect(h).to.equal(size[1]); - expect(ext.siteID).to.equal(bids[impressionIndex].params.siteId.toString()); + expect(ext.siteID).to.equal(bids[impressionIndex].params.siteId); expect(ext.sid).to.equal(sidValue); }); }); @@ -2031,6 +2164,19 @@ describe('IndexexchangeAdapter', function () { expect(impression.video.api).to.equal(2); expect(impression.video.mimes[0]).to.equal('video/mp4'); }); + + it('should send gpid in request if ortb2Imp.ext.gpid exists', function () { + const GPID = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + gpid: GPID + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const { ext: { gpid } } = JSON.parse(requests[0].data.r).imp[0]; + expect(gpid).to.equal(GPID); + }); }); describe('buildRequestMultiFormat', function () { @@ -2088,7 +2234,7 @@ describe('IndexexchangeAdapter', function () { expect(w).to.equal(size[0]); expect(h).to.equal(size[1]); - expect(ext.siteID).to.equal(bid.params.siteId.toString()); + expect(ext.siteID).to.equal(bid.params.siteId); expect(ext.sid).to.equal(sidValue); }); }); @@ -2343,6 +2489,45 @@ describe('IndexexchangeAdapter', function () { expect(result[0]).to.deep.equal(expectedParse[0]); }); + it('should get correct bid response for video ad and set bid.vastXml 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, + vastXml: ' Test In-Stream Video { + let TODAY = new Date().toISOString().slice(0, 10); + const key = 'ixdiag'; + + let sandbox; + let setDataInLocalStorageStub; + let getDataFromLocalStorageStub; + let removeDataFromLocalStorageStub; + let localStorageValues = {}; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + setDataInLocalStorageStub = sandbox.stub(storage, 'setDataInLocalStorage').callsFake((key, value) => localStorageValues[key] = value) + getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => localStorageValues[key]) + removeDataFromLocalStorageStub = sandbox.stub(storage, 'removeDataFromLocalStorage').callsFake((key) => delete localStorageValues[key]) + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + }); + + afterEach(() => { + setDataInLocalStorageStub.restore(); + getDataFromLocalStorageStub.restore(); + removeDataFromLocalStorageStub.restore(); + localStorageValues = {}; + sandbox.restore(); + + config.setConfig({ + fpd: {}, + ix: {}, + }) + }); + + it('should not log error in LocalStorage when there is no logError called.', () => { + const bid = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; + expect(spec.isBidRequestValid(bid)).to.be.true; + expect(localStorageValues[key]).to.be.undefined; + }); + + it('should log ERROR_CODES.BID_SIZE_INVALID_FORMAT in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = ['400', 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); + }); + + it('should log ERROR_CODES.BID_SIZE_NOT_INCLUDED in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = [407, 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_NOT_INCLUDED]: 1 } }); + }); + + it('should log ERROR_CODES.PROPERTY_NOT_INCLUDED in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.video = {}; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); + }); + + it('should log ERROR_CODES.SITE_ID_INVALID_VALUE in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.siteId = false; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 } }); + }); + + it('should log ERROR_CODES.BID_FLOOR_INVALID_FORMAT in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.bidFloor = true; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_FLOOR_INVALID_FORMAT]: 1 } }); + }); + + it('should log ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + + config.setConfig({ + ix: { + firstPartyData: { + cd: Array(1700).join('#') + } + } + }); + + expect(spec.isBidRequestValid(bid)).to.be.true; + spec.buildRequests([bid]); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE]: 2 } }); + }); + + it('should log ERROR_CODES.EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.bidderRequestId = Array(8000).join('#'); + + expect(spec.isBidRequestValid(bid)).to.be.true; + spec.buildRequests([bid]); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.EXCEEDS_MAX_SIZE]: 2 } }); + }); + + it('should log ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + + config.setConfig({ + fpd: { + site: { + data: { + pageType: Array(5700).join('#') + } + } + } + }); + + expect(spec.isBidRequestValid(bid)).to.be.true; + spec.buildRequests([bid]); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE]: 2 } }); + }); + + it('should log ERROR_CODES.VIDEO_DURATION_INVALID in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.video.minduration = 1; + bid.params.video.maxduration = 0; + + expect(spec.isBidRequestValid(bid)).to.be.true; + spec.buildRequests([bid]); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.VIDEO_DURATION_INVALID]: 2 } }); + }); + + it('should increment errors for errorCode', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.video = {}; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 8 } }); + }); + + it('should add new errorCode to ixdiag.', () => { + let bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = ['400', 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); + + bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.siteId = false; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ + [TODAY]: { + [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1, + [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 + } + }); + }); + + it('should clear errors with successful response', () => { + const ixdiag = { [TODAY]: { '1': 1, '3': 8, '4': 1 } }; + setDataInLocalStorageStub(key, JSON.stringify(ixdiag)); + + expect(JSON.parse(localStorageValues[key])).to.deep.equal(ixdiag); + + const request = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; + 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 + } + } + } + }), + }; + + const validBidRequests = [ + DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], + DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0] + ]; + + spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data, validBidRequests }); + + expect(localStorageValues[key]).to.be.undefined; + }); + + it('should clear errors after 7 day expiry errorCode', () => { + const EXPIRED_DATE = '2019-12-12'; + + const ixdiag = { [EXPIRED_DATE]: { '1': 1, '3': 8, '4': 1 }, [TODAY]: { '3': 8, '4': 1 } }; + setDataInLocalStorageStub(key, JSON.stringify(ixdiag)); + + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = ['400', 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])[EXPIRED_DATE]).to.be.undefined; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { '1': 1, '3': 8, '4': 1 } }) + }); + + it('should not save error data into localstorage if consent is not given', () => { + config.setConfig({ deviceAccess: false }); + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = ['400', 100]; + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(localStorageValues[key]).to.be.undefined; + }); + }); }); diff --git a/test/spec/modules/justIdSystem_spec.js b/test/spec/modules/justIdSystem_spec.js new file mode 100644 index 00000000000..b6a8cd2d310 --- /dev/null +++ b/test/spec/modules/justIdSystem_spec.js @@ -0,0 +1,216 @@ +import { justIdSubmodule, ConfigWrapper, jtUtils, EX_URL_REQUIRED, EX_INVALID_MODE } from 'modules/justIdSystem.js'; +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; +import * as utils from 'src/utils.js'; + +const DEFAULT_PARTNER = 'pbjs-just-id-module'; + +const url = 'https://example.com/getId.js'; + +describe('JustIdSystem', function () { + describe('configWrapper', function() { + it('invalid mode', function() { + expect(() => new ConfigWrapper({ params: { mode: 'invalidmode' } })).to.throw(EX_INVALID_MODE); + }) + + it('url is required', function() { + expect(() => new ConfigWrapper(configModeCombined())).to.throw(EX_URL_REQUIRED); + }) + + it('defaultPartner', function() { + expect(new ConfigWrapper(configModeCombined(url)).getUrl()).to.eq(expectedUrl(url, DEFAULT_PARTNER)); + }) + + it('customPartner', function() { + const partner = 'abc'; + expect(new ConfigWrapper(configModeCombined(url, partner)).getUrl()).to.eq(expectedUrl(url, partner)); + }) + }); + + describe('decode', function() { + it('decode justId', function() { + const justId = 'aaa'; + expect(justIdSubmodule.decode({uid: justId})).to.deep.eq({justId: justId}); + }) + }); + + describe('getId basic', function() { + var atmMock = (cmd, param) => { + switch (cmd) { + case 'getReadyState': + param('ready') + return; + case 'getVersion': + return Promise.resolve('1.0'); + case 'getUid': + param('user123'); + } + } + + var currentAtm; + + var getAtmStub = sinon.stub(jtUtils, 'getAtm').callsFake(() => currentAtm); + + var logErrorStub; + + beforeEach(function() { + logErrorStub = sinon.spy(utils, 'logError'); + }); + + afterEach(function() { + logErrorStub.restore(); + }); + + it('all ok', function(done) { + currentAtm = atmMock; + const callbackSpy = sinon.stub(); + + callbackSpy.callsFake(idObj => { + try { + expect(idObj.uid).to.equal('user123'); + done(); + } catch (err) { + done(err); + } + }) + + const atmVarName = '__fakeAtm'; + + justIdSubmodule.getId({params: {atmVarName: atmVarName}}).callback(callbackSpy); + + expect(getAtmStub.lastCall.lastArg).to.equal(atmVarName); + }); + + it('unsuported version', function(done) { + currentAtm = (cmd, param) => { + switch (cmd) { + case 'getReadyState': + param('ready') + } + } + + const callbackSpy = sinon.stub(); + + callbackSpy.callsFake(idObj => { + try { + expect(logErrorStub.calledOnce).to.be.true; + expect(idObj).to.be.undefined + done(); + } catch (err) { + done(err); + } + }) + + justIdSubmodule.getId({}).callback(callbackSpy); + }); + + it('work with stub', function(done) { + var calls = []; + currentAtm = (cmd, param) => { + calls.push({cmd: cmd, param: param}); + } + + const callbackSpy = sinon.stub(); + + callbackSpy.callsFake(idObj => { + try { + expect(idObj.uid).to.equal('user123'); + done(); + } catch (err) { + done(err); + } + }) + + justIdSubmodule.getId({}).callback(callbackSpy); + + currentAtm = atmMock; + expect(calls.length).to.equal(1); + expect(calls[0].cmd).to.equal('getReadyState'); + calls[0].param('ready') + }); + }); + + describe('getId combined', function() { + const scriptTag = document.createElement('script'); + + const onPrebidGetId = sinon.stub().callsFake(event => { + var cacheIdObj = event.detail && event.detail.cacheIdObj; + var justId = (cacheIdObj && cacheIdObj.uid && cacheIdObj.uid + '-x') || 'user123'; + scriptTag.dispatchEvent(new CustomEvent('justIdReady', { detail: { justId: justId } })); + }); + + scriptTag.addEventListener('prebidGetId', onPrebidGetId) + + var scriptTagCallback; + + beforeEach(() => { + loadExternalScriptStub.callsFake((url, moduleCode, callback) => { + scriptTagCallback = callback; + return scriptTag; + }); + }) + + var logErrorStub; + + beforeEach(() => { + logErrorStub = sinon.spy(utils, 'logError'); + }); + + afterEach(() => { + logErrorStub.restore(); + }); + + it('url is required', function() { + expect(justIdSubmodule.getId(configModeCombined())).to.be.undefined; + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('without cachedIdObj', function() { + const callbackSpy = sinon.spy(); + justIdSubmodule.getId(configModeCombined(url)).callback(callbackSpy); + + scriptTagCallback(); + + expect(callbackSpy.lastCall.lastArg.uid).to.equal('user123'); + }); + + it('with cachedIdObj', function() { + const callbackSpy = sinon.spy(); + + justIdSubmodule.getId(configModeCombined(url), undefined, { uid: 'userABC' }).callback(callbackSpy); + + scriptTagCallback(); + + expect(callbackSpy.lastCall.lastArg.uid).to.equal('userABC-x'); + }); + + it('check if getId arguments are passed to prebidGetId event', function() { + const callbackSpy = sinon.spy(); + + const a = configModeCombined(url); + const b = { y: 'y' } + const c = { z: 'z' } + + justIdSubmodule.getId(a, b, c).callback(callbackSpy); + + scriptTagCallback(); + + expect(onPrebidGetId.lastCall.lastArg.detail).to.deep.eq({ config: a, consentData: b, cacheIdObj: c }); + }); + }); +}); + +function expectedUrl(url, srcId) { + return `${url}?sourceId=${srcId}` +} + +function configModeCombined(url, partner) { + var conf = { + params: { + mode: 'COMBINED' + } + } + url && (conf.params.url = url); + partner && (conf.params.partner = partner); + + return conf; +} diff --git a/test/spec/modules/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index edc5325def3..3686418a991 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -97,12 +97,13 @@ describe('justpremium adapter', function () { expect(jpxRequest.id).to.equal(adUnits[0].params.zone) expect(jpxRequest.mediaTypes && jpxRequest.mediaTypes.banner && jpxRequest.mediaTypes.banner.sizes).to.not.equal('undefined') expect(jpxRequest.version.prebid).to.equal('$prebid.version$') - expect(jpxRequest.version.jp_adapter).to.equal('1.8.1') + expect(jpxRequest.version.jp_adapter).to.equal('1.8.2') expect(jpxRequest.pubcid).to.equal('0000000') expect(jpxRequest.uids.tdid).to.equal('1111111') expect(jpxRequest.uids.id5id.uid).to.equal('2222222') expect(jpxRequest.uids.digitrustid.data.id).to.equal('3333333') expect(jpxRequest.us_privacy).to.equal('1YYN') + expect(jpxRequest.ggExt).to.be.null }) }) diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 458e45e8ae7..db17d18864b 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -1,7 +1,8 @@ import { fetchTargetingForMediaId, getVatFromCache, extractPublisherParams, formatTargetingResponse, getVatFromPlayer, enrichAdUnits, addTargetingToBid, - fetchTargetingInformation, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js'; + fetchTargetingInformation, jwplayerSubmodule, getContentId, getContentData } from 'modules/jwplayerRtdProvider.js'; import { server } from 'test/mocks/xhr.js'; +import {addOrtbSiteContent} from '../../../modules/jwplayerRtdProvider'; describe('jwplayerRtdProvider', function() { const testIdForSuccess = 'test_id_for_success'; @@ -412,7 +413,7 @@ describe('jwplayerRtdProvider', function() { }); }); - describe(' Extract Publisher Params', function () { + describe('Extract Publisher Params', function () { const config = { mediaID: 'test' }; it('should exclude adUnits that do not support instream video and do not specify jwTargeting', function () { @@ -480,6 +481,198 @@ describe('jwplayerRtdProvider', function() { }) }); + describe('Get content id', function() { + it('prefixes jw_ to the media id', function () { + const mediaId = 'mediaId'; + const contentId = getContentId(mediaId); + expect(contentId).to.equal('jw_mediaId'); + }); + + it('returns undefined when media id is empty', function () { + let contentId = getContentId(); + expect(contentId).to.be.undefined; + contentId = getContentId(''); + expect(contentId).to.be.undefined; + contentId = getContentId(null); + expect(contentId).to.be.undefined; + }); + }); + + describe('Get Content Data', function () { + it('returns undefined when segments are empty', function () { + let data = getContentData(null); + expect(data).to.be.undefined; + data = getContentData(undefined); + expect(data).to.be.undefined; + data = getContentData([]); + expect(data).to.be.undefined; + }); + + it('returns proper format', function () { + const segment1 = 'segment1'; + const segment2 = 'segment2'; + const segment3 = 'segment3'; + const data = getContentData([segment1, segment2, segment3]); + expect(data).to.have.property('name', 'jwplayer'); + expect(data.ext).to.have.property('segtax', 502); + expect(data.segment[0]).to.deep.equal({ id: segment1, value: segment1 }); + expect(data.segment[1]).to.deep.equal({ id: segment2, value: segment2 }); + expect(data.segment[2]).to.deep.equal({ id: segment3, value: segment3 }); + }); + }); + + describe(' Add Ortb Site Content', function () { + it('should maintain object structure when id and data params are empty', function () { + const bid = { + ortb2: { + site: { + content: { + id: 'randomId' + }, + random: { + random_sub: 'randomSub' + } + }, + app: { + content: { + id: 'appId' + } + } + } + }; + addOrtbSiteContent(bid); + expect(bid).to.have.nested.property('ortb2.site.content.id', 'randomId'); + expect(bid).to.have.nested.property('ortb2.site.random.random_sub', 'randomSub'); + expect(bid).to.have.nested.property('ortb2.app.content.id', 'appId'); + }); + + it('should create a structure compliant with the oRTB 2 spec', function() { + const bid = {}; + const expectedId = 'expectedId'; + const expectedData = { datum: 'datum' }; + addOrtbSiteContent(bid, expectedId, expectedData); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData); + }); + + it('should respect existing structure when adding adding fields', function () { + const bid = { + ortb2: { + site: { + content: { + id: 'oldId' + }, + random: { + random_sub: 'randomSub' + } + }, + app: { + content: { + id: 'appId' + } + } + } + }; + + const expectedId = 'expectedId'; + const expectedData = { datum: 'datum' }; + addOrtbSiteContent(bid, expectedId, expectedData); + expect(bid).to.have.nested.property('ortb2.site.random.random_sub', 'randomSub'); + expect(bid).to.have.nested.property('ortb2.app.content.id', 'appId'); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData); + }); + + it('should set content id', function () { + const bid = {}; + const expectedId = 'expectedId'; + addOrtbSiteContent(bid, expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + }); + + it('should override content id', function () { + const bid = { + ortb2: { + site: { + content: { + id: 'oldId' + } + } + } + }; + + const expectedId = 'expectedId'; + addOrtbSiteContent(bid, expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + }); + + it('should keep previous content id when not set', function () { + const previousId = 'oldId'; + const bid = { + ortb2: { + site: { + content: { + id: previousId, + data: [{ datum: 'first_datum' }] + } + } + } + }; + + addOrtbSiteContent(bid, null, { datum: 'new_datum' }); + expect(bid).to.have.nested.property('ortb2.site.content.id', previousId); + }); + + it('should set content data', function () { + const bid = {}; + const expectedData = { datum: 'datum' }; + addOrtbSiteContent(bid, null, expectedData); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data).to.have.length(1); + expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData); + }); + + it('should append content data', function () { + const bid = { + ortb2: { + site: { + content: { + data: [{ datum: 'first_datum' }] + } + } + } + }; + + const expectedData = { datum: 'datum' }; + addOrtbSiteContent(bid, null, expectedData); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data).to.have.length(2); + expect(bid.ortb2.site.content.data.pop()).to.be.deep.equal(expectedData); + }); + + it('should keep previous data when not set', function () { + const expectedId = 'expectedId'; + const expectedData = { datum: 'first_datum' }; + const bid = { + ortb2: { + site: { + content: { + data: [expectedData] + } + } + } + }; + + addOrtbSiteContent(bid, expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data).to.have.length(1); + expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + }); + }); + describe('Add Targeting to Bid', function () { const targeting = {foo: 'bar'}; diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 43968bbef5a..6f5a0008783 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -149,20 +149,12 @@ describe('kargo adapter tests', function () { }; } - function initializeKruxUser() { - setLocalStorageItem('kxkar_user', 'rsgr9pnij'); - } - - function initializeKruxSegments() { - setLocalStorageItem('kxkar_segs', 'qv9v984dy,rpx2gy365,qrd5u4axv,rnub9nmtd,reha00jnu'); - } - function getKrgCrb() { - return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwidXNlcklkIjoiNWYxMDg4MzEtMzAyZC0xMWU3LWJmNmItNDU5NWFjZDNiZjZjIiwiY2xpZW50SWQiOiIyNDEwZDhmMi1jMTExLTQ4MTEtODhhNS03YjVlMTkwZTQ3NWYiLCJvcHRPdXQiOmZhbHNlLCJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9'; + return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0='; } function getKrgCrbOldStyle() { - return '%7B%22v%22%3A%22eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwidXNlcklkIjoiNWYxMDg4MzEtMzAyZC0xMWU3LWJmNmItNDU5NWFjZDNiZjZjIiwiY2xpZW50SWQiOiIyNDEwZDhmMi1jMTExLTQ4MTEtODhhNS03YjVlMTkwZTQ3NWYiLCJvcHRPdXQiOmZhbHNlLCJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9%22%7D'; + return '%7B%22v%22%3A%22eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0=%22%7D'; } function initializeKrgCrb(cookieOnly) { @@ -236,7 +228,7 @@ describe('kargo adapter tests', function () { return spec._getSessionId(); } - function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { + function getExpectedKrakenParams(excludeUserIds, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { var base = { timeout: 200, requestCount: requestCount++, @@ -273,16 +265,6 @@ describe('kargo adapter tests', function () { optOut: false, usp: '1---' }, - krux: { - userID: 'rsgr9pnij', - segments: [ - 'qv9v984dy', - 'rpx2gy365', - 'qrd5u4axv', - 'rnub9nmtd', - 'reha00jnu' - ] - }, pageURL: window.location.href, prebidRawBidRequests: [ { @@ -326,13 +308,6 @@ describe('kargo adapter tests', function () { delete base.prebidRawBidRequests[0].userId.tdid; } - if (excludeKrux) { - base.krux = { - userID: null, - segments: [] - }; - } - return base; } @@ -367,101 +342,77 @@ describe('kargo adapter tests', function () { } it('works when all params and localstorage and cookies are correctly set', function() { - initializeKruxUser(); - initializeKruxSegments(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); + testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); }); it('works when all params and cookies are correctly set but no localstorage', function() { - initializeKruxUser(); - initializeKruxSegments(); initializeKrgCrb(true); - testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, null, getKrgCrbOldStyle())); + testBuildRequests(false, getExpectedKrakenParams(undefined, null, getKrgCrbOldStyle())); }); it('gracefully handles nothing being set', function() { - testBuildRequests(true, getExpectedKrakenParams(true, true, null, null)); + testBuildRequests(true, getExpectedKrakenParams(true, null, null)); }); it('gracefully handles browsers without localStorage', function() { simulateNoLocalStorage(); - testBuildRequests(true, getExpectedKrakenParams(true, true, null, null)); + testBuildRequests(true, getExpectedKrakenParams(true, null, null)); }); it('handles empty yet valid Kargo CRB', function() { - initializeKruxUser(); - initializeKruxSegments(); initializeEmptyKrgCrb(); initializeEmptyKrgCrbCookie(); - testBuildRequests(true, getExpectedKrakenParams(true, undefined, getEmptyKrgCrb(), getEmptyKrgCrbOldStyle())); + testBuildRequests(true, getExpectedKrakenParams(true, getEmptyKrgCrb(), getEmptyKrgCrbOldStyle())); }); it('handles broken Kargo CRBs where base64 encoding is invalid', function() { - initializeKruxUser(); - initializeKruxSegments(); initializeInvalidKrgCrbType1(); - testBuildRequests(true, getExpectedKrakenParams(true, undefined, getInvalidKrgCrbType1(), null)); + testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType1(), null)); }); it('handles broken Kargo CRBs where top level JSON is invalid on cookie', function() { - initializeKruxUser(); - initializeKruxSegments(); initializeInvalidKrgCrbType1Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType1())); + testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType1())); }); it('handles broken Kargo CRBs where decoded JSON is invalid', function() { - initializeKruxUser(); - initializeKruxSegments(); initializeInvalidKrgCrbType2(); - testBuildRequests(true, getExpectedKrakenParams(true, undefined, getInvalidKrgCrbType2(), null)); + testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType2(), null)); }); it('handles broken Kargo CRBs where inner base 64 is invalid on cookie', function() { - initializeKruxUser(); - initializeKruxSegments(); initializeInvalidKrgCrbType2Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType2OldStyle())); + testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType2OldStyle())); }); it('handles broken Kargo CRBs where inner JSON is invalid on cookie', function() { - initializeKruxUser(); - initializeKruxSegments(); initializeInvalidKrgCrbType3Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType3OldStyle())); + testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType3OldStyle())); }); it('handles broken Kargo CRBs where inner JSON is falsey', function() { - initializeKruxUser(); - initializeKruxSegments(); initializeInvalidKrgCrbType4Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType4OldStyle())); + testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType4OldStyle())); }); it('handles a non-existant currency object on the config', function() { simulateNoCurrencyObject(); - initializeKruxUser(); - initializeKruxSegments(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); + testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); }); it('handles no ad server currency being set on the currency object in the config', function() { simulateNoAdServerCurrency(); - initializeKruxUser(); - initializeKruxSegments(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); + testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); }); it('sends gdpr consent', function () { - initializeKruxUser(); - initializeKruxSegments(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); + testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); }); }); @@ -484,7 +435,7 @@ describe('kargo adapter tests', function () { height: 250, targetingCustom: 'dmpmptest1234', metadata: { - landingPageDomain: 'https://foobar.com' + landingPageDomain: ['https://foobar.com'] } }, 3: { @@ -493,6 +444,15 @@ describe('kargo adapter tests', function () { adm: '
', width: 300, height: 250 + }, + 4: { + id: 'bar', + cpm: 2.5, + adm: '
', + width: 300, + height: 250, + metadata: {}, + currency: 'EUR' } }}, { currency: 'USD', @@ -511,6 +471,11 @@ describe('kargo adapter tests', function () { params: { placementId: 'bar' } + }, { + bidId: 4, + params: { + placementId: 'bar' + } }] }); var expectation = [{ @@ -552,6 +517,18 @@ describe('kargo adapter tests', function () { netRevenue: true, currency: 'USD', meta: undefined + }, { + requestId: '4', + cpm: 2.5, + width: 300, + height: 250, + ad: '
', + ttl: 300, + creativeId: 'bar', + dealId: undefined, + netRevenue: true, + currency: 'EUR', + meta: undefined }]; expect(resp).to.deep.equal(expectation); }); diff --git a/test/spec/modules/konduitWrapper_spec.js b/test/spec/modules/konduitWrapper_spec.js index c88287c7066..506d2189049 100644 --- a/test/spec/modules/konduitWrapper_spec.js +++ b/test/spec/modules/konduitWrapper_spec.js @@ -53,7 +53,6 @@ describe('The Konduit vast wrapper module', function () { const callback = sinon.spy(); processBids({ bid, callback }); server.respond(); - expect(server.requests.length).to.equal(1); const requestBody = JSON.parse(server.requests[0].requestBody); diff --git a/test/spec/modules/kubientBidAdapter_spec.js b/test/spec/modules/kubientBidAdapter_spec.js index 5449de0c4de..f7afc709564 100644 --- a/test/spec/modules/kubientBidAdapter_spec.js +++ b/test/spec/modules/kubientBidAdapter_spec.js @@ -1,6 +1,13 @@ import { expect, assert } from 'chai'; import { spec } from 'modules/kubientBidAdapter.js'; import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {config} from '../../../src/config'; + +function encodeQueryData(data) { + return Object.keys(data).map(function(key) { + return [key, data[key]].map(encodeURIComponent).join('='); + }).join('&'); +} describe('KubientAdapter', function () { let bidBanner = { @@ -12,7 +19,7 @@ describe('KubientAdapter', function () { }, getFloor: function(params) { return { - floor: 0.05, + floor: 0, currency: 'USD' }; }, @@ -94,24 +101,20 @@ describe('KubientAdapter', function () { uspConsent: uspConsentData }; describe('buildRequestBanner', function () { - let serverRequests = spec.buildRequests([bidBanner], Object.assign({}, bidderRequest, {bids: [bidBanner]})); - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequests).to.be.an('array'); + beforeEach(function () { + config.resetConfig(); }); - for (let i = 0; i < serverRequests.length; i++) { - let serverRequest = serverRequests[i]; - it('Creates a ServerRequest object with method, URL and data', function () { + it('Creates Banner 1 ServerRequest object with method, URL and data', function () { + config.setConfig({'coppa': false}); + let serverRequests = spec.buildRequests([bidBanner], Object.assign({}, bidderRequest, {bids: [bidBanner]})); + expect(serverRequests).to.be.an('array'); + for (let i = 0; i < serverRequests.length; i++) { + let serverRequest = serverRequests[i]; expect(serverRequest.method).to.be.a('string'); expect(serverRequest.url).to.be.a('string'); expect(serverRequest.data).to.be.a('string'); - }); - it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); - }); - it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://kssp.kbntx.ch/kubprebidjs'); - }); - it('Returns valid data if array of bids is valid', function () { let data = JSON.parse(serverRequest.data); expect(data).to.be.an('object'); expect(data).to.have.all.keys('v', 'requestId', 'adSlots', 'gdpr', 'referer', 'tmax', 'consent', 'consentGiven', 'uspConsent'); @@ -124,35 +127,30 @@ describe('KubientAdapter', function () { expect(data.uspConsent).to.exist.and.to.equal(uspConsentData); for (let j = 0; j < data['adSlots'].length; j++) { let adSlot = data['adSlots'][i]; - expect(adSlot).to.have.all.keys('bidId', 'zoneId', 'floor', 'banner', 'schain'); + expect(adSlot).to.have.all.keys('bidId', 'zoneId', 'banner', 'schain'); expect(adSlot.bidId).to.be.a('string').and.to.equal(bidBanner.bidId); expect(adSlot.zoneId).to.be.a('string').and.to.equal(bidBanner.params.zoneid); - expect(adSlot.floor).to.be.a('number'); expect(adSlot.schain).to.be.an('object'); expect(adSlot.banner).to.be.an('object'); } - }); - } + } + }); }); describe('buildRequestVideo', function () { - let serverRequests = spec.buildRequests([bidVideo], Object.assign({}, bidderRequest, {bids: [bidVideo]})); - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequests).to.be.an('array'); + beforeEach(function () { + config.resetConfig(); }); - for (let i = 0; i < serverRequests.length; i++) { - let serverRequest = serverRequests[i]; - it('Creates a ServerRequest object with method, URL and data', function () { + it('Creates Video 1 ServerRequest object with method, URL and data', function () { + config.setConfig({'coppa': false}); + let serverRequests = spec.buildRequests([bidVideo], Object.assign({}, bidderRequest, {bids: [bidVideo]})); + expect(serverRequests).to.be.an('array'); + for (let i = 0; i < serverRequests.length; i++) { + let serverRequest = serverRequests[i]; expect(serverRequest.method).to.be.a('string'); expect(serverRequest.url).to.be.a('string'); expect(serverRequest.data).to.be.a('string'); - }); - it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); - }); - it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://kssp.kbntx.ch/kubprebidjs'); - }); - it('Returns valid data if array of bids is valid', function () { let data = JSON.parse(serverRequest.data); expect(data).to.be.an('object'); expect(data).to.have.all.keys('v', 'requestId', 'adSlots', 'gdpr', 'referer', 'tmax', 'consent', 'consentGiven', 'uspConsent'); @@ -172,11 +170,88 @@ describe('KubientAdapter', function () { expect(adSlot.schain).to.be.an('object'); expect(adSlot.video).to.be.an('object'); } - }); - } + } + }); + }); + describe('buildRequestBanner', function () { + beforeEach(function () { + config.resetConfig(); + }); + it('Creates Banner 2 ServerRequest object with method, URL and data with bidBanner', function () { + config.setConfig({'coppa': true}); + let serverRequests = spec.buildRequests([bidBanner], Object.assign({}, bidderRequest, {bids: [bidBanner]})); + expect(serverRequests).to.be.an('array'); + for (let i = 0; i < serverRequests.length; i++) { + let serverRequest = serverRequests[i]; + expect(serverRequest.method).to.be.a('string'); + expect(serverRequest.url).to.be.a('string'); + expect(serverRequest.data).to.be.a('string'); + expect(serverRequest.method).to.equal('POST'); + expect(serverRequest.url).to.equal('https://kssp.kbntx.ch/kubprebidjs'); + let data = JSON.parse(serverRequest.data); + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('v', 'requestId', 'adSlots', 'gdpr', 'coppa', 'referer', 'tmax', 'consent', 'consentGiven', 'uspConsent'); + expect(data.v).to.exist.and.to.be.a('string'); + expect(data.requestId).to.exist.and.to.be.a('string'); + expect(data.coppa).to.be.a('number').and.to.equal(1); + expect(data.referer).to.be.a('string'); + expect(data.tmax).to.exist.and.to.be.a('number'); + expect(data.gdpr).to.exist.and.to.be.within(0, 1); + expect(data.consent).to.equal(consentString); + expect(data.uspConsent).to.exist.and.to.equal(uspConsentData); + for (let j = 0; j < data['adSlots'].length; j++) { + let adSlot = data['adSlots'][i]; + expect(adSlot).to.have.all.keys('bidId', 'zoneId', 'banner', 'schain'); + expect(adSlot.bidId).to.be.a('string').and.to.equal(bidBanner.bidId); + expect(adSlot.zoneId).to.be.a('string').and.to.equal(bidBanner.params.zoneid); + expect(adSlot.schain).to.be.an('object'); + expect(adSlot.banner).to.be.an('object'); + } + } + }); + }); + describe('buildRequestVideo', function () { + beforeEach(function () { + config.resetConfig(); + }); + it('Creates Video 2 ServerRequest object with method, URL and data', function () { + config.setConfig({'coppa': true}); + let serverRequests = spec.buildRequests([bidVideo], Object.assign({}, bidderRequest, {bids: [bidVideo]})); + expect(serverRequests).to.be.an('array'); + for (let i = 0; i < serverRequests.length; i++) { + let serverRequest = serverRequests[i]; + expect(serverRequest.method).to.be.a('string'); + expect(serverRequest.url).to.be.a('string'); + expect(serverRequest.data).to.be.a('string'); + expect(serverRequest.method).to.equal('POST'); + expect(serverRequest.url).to.equal('https://kssp.kbntx.ch/kubprebidjs'); + let data = JSON.parse(serverRequest.data); + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('v', 'requestId', 'adSlots', 'gdpr', 'coppa', 'referer', 'tmax', 'consent', 'consentGiven', 'uspConsent'); + expect(data.v).to.exist.and.to.be.a('string'); + expect(data.requestId).to.exist.and.to.be.a('string'); + expect(data.coppa).to.be.a('number').and.to.equal(1); + expect(data.referer).to.be.a('string'); + expect(data.tmax).to.exist.and.to.be.a('number'); + expect(data.gdpr).to.exist.and.to.be.within(0, 1); + expect(data.consent).to.equal(consentString); + expect(data.uspConsent).to.exist.and.to.equal(uspConsentData); + for (let j = 0; j < data['adSlots'].length; j++) { + let adSlot = data['adSlots'][i]; + expect(adSlot).to.have.all.keys('bidId', 'zoneId', 'floor', 'video', 'schain'); + expect(adSlot.bidId).to.be.a('string').and.to.equal(bidVideo.bidId); + expect(adSlot.zoneId).to.be.a('string').and.to.equal(bidVideo.params.zoneid); + expect(adSlot.floor).to.be.a('number'); + expect(adSlot.schain).to.be.an('object'); + expect(adSlot.video).to.be.an('object'); + } + } + }); }); - describe('isBidRequestValid', function () { + beforeEach(function () { + config.resetConfig(); + }); it('Should return true when required params are found', function () { expect(spec.isBidRequestValid(bidBanner)).to.be.true; expect(spec.isBidRequestValid(bidVideo)).to.be.true; @@ -192,8 +267,10 @@ describe('KubientAdapter', function () { expect(spec.isBidRequestValid(bidVideo)).to.be.false; }); }); - describe('interpretResponse', function () { + beforeEach(function () { + config.resetConfig(); + }); it('Should interpret response', function () { const serverResponse = { body: @@ -234,7 +311,6 @@ describe('KubientAdapter', function () { expect(dataItem.meta).to.exist.and.to.be.a('object'); expect(dataItem.meta.advertiserDomains).to.exist.and.to.be.a('array').and.to.equal(serverResponse.body.seatbid[0].bid[0].meta.adomain); }); - it('Should return no ad when not given a server response', function () { const ads = spec.interpretResponse(null); expect(ads).to.be.an('array').and.to.have.length(0); @@ -242,6 +318,9 @@ describe('KubientAdapter', function () { }); describe('interpretResponse Video', function () { + beforeEach(function () { + config.resetConfig(); + }); it('Should interpret response', function () { const serverResponse = { body: @@ -285,7 +364,6 @@ describe('KubientAdapter', function () { expect(dataItem.mediaType).to.exist.and.to.equal(VIDEO); expect(dataItem.vastXml).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].adm); }); - it('Should return no ad when not given a server response', function () { const ads = spec.interpretResponse(null); expect(ads).to.be.an('array').and.to.have.length(0); @@ -293,89 +371,68 @@ describe('KubientAdapter', function () { }); describe('getUserSyncs', function () { - it('should register the sync iframe without gdpr', function () { - let syncOptions = { - iframeEnabled: true - }; - let serverResponses = null; - let gdprConsent = { - consentString: consentString - }; - let uspConsent = null; - let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); - expect(syncs).to.be.an('array').and.to.have.length(1); - expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&consent_given=0'); - }); - it('should register the sync iframe with gdpr', function () { - let syncOptions = { - iframeEnabled: true - }; - let serverResponses = null; - let gdprConsent = { - gdprApplies: true, - consentString: consentString - }; - let uspConsent = null; - let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); - expect(syncs).to.be.an('array').and.to.have.length(1); - expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=0'); - }); - it('should register the sync iframe with gdpr vendor', function () { - let syncOptions = { - iframeEnabled: true - }; - let serverResponses = null; - let gdprConsent = { - gdprApplies: true, - consentString: consentString, - apiVersion: 1, - vendorData: { - vendorConsents: { - 794: 1 - } - } - }; - let uspConsent = null; - let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); - expect(syncs).to.be.an('array').and.to.have.length(1); - expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + beforeEach(function () { + config.resetConfig(); }); it('should register the sync image without gdpr', function () { let syncOptions = { pixelEnabled: true }; + let values = {}; let serverResponses = null; let gdprConsent = { consentString: consentString }; let uspConsent = null; + config.setConfig({ + userSync: { + filterSettings: { + image: { + bidders: '*', + filter: 'include' + } + } + } + }); let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + values['consent'] = consentString; expect(syncs).to.be.an('array').and.to.have.length(1); expect(syncs[0].type).to.equal('image'); - expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&consent_given=0'); + expect(syncs[0].url).to.equal('https://matching.kubient.net/match/sp?' + encodeQueryData(values)); }); it('should register the sync image with gdpr', function () { let syncOptions = { pixelEnabled: true }; + let values = {}; let serverResponses = null; let gdprConsent = { gdprApplies: true, consentString: consentString }; let uspConsent = null; + config.setConfig({ + userSync: { + filterSettings: { + image: { + bidders: '*', + filter: 'include' + } + } + } + }); let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + values['gdpr'] = 1; + values['consent'] = consentString; expect(syncs).to.be.an('array').and.to.have.length(1); expect(syncs[0].type).to.equal('image'); - expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=0'); + expect(syncs[0].url).to.equal('https://matching.kubient.net/match/sp?' + encodeQueryData(values)); }); it('should register the sync image with gdpr vendor', function () { let syncOptions = { pixelEnabled: true }; + let values = {}; let serverResponses = null; let gdprConsent = { gdprApplies: true, @@ -390,10 +447,49 @@ describe('KubientAdapter', function () { } }; let uspConsent = null; + config.setConfig({ + userSync: { + filterSettings: { + image: { + bidders: '*', + filter: 'include' + } + } + } + }); + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + values['gdpr'] = 1; + values['consent'] = consentString; + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://matching.kubient.net/match/sp?' + encodeQueryData(values)); + }); + it('should register the sync image without gdpr and with uspConsent', function () { + let syncOptions = { + pixelEnabled: true + }; + let values = {}; + let serverResponses = null; + let gdprConsent = { + consentString: consentString + }; + let uspConsent = '1YNN'; + config.setConfig({ + userSync: { + filterSettings: { + image: { + bidders: '*', + filter: 'include' + } + } + } + }); let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + values['consent'] = consentString; + values['usp'] = uspConsent; expect(syncs).to.be.an('array').and.to.have.length(1); expect(syncs[0].type).to.equal('image'); - expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + expect(syncs[0].url).to.equal('https://matching.kubient.net/match/sp?' + encodeQueryData(values)); }); }) }); diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 1a33bbe6cb9..115acda722f 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -9,7 +9,8 @@ describe('limelightDigitalAdapter', function () { params: { host: 'exchange.ortb.net', adUnitId: 123, - adUnitType: 'banner' + adUnitType: 'banner', + publisherId: 'perfectPublisher' }, placementCode: 'placement_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', @@ -18,7 +19,17 @@ describe('limelightDigitalAdapter', function () { sizes: [[300, 250]] } }, - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + userIdAsEids: [ + { + source: 'test1.org', + uids: [ + { + id: '123', + } + ] + } + ] } const bid2 = { bidId: '58ee9870c3164a', @@ -32,7 +43,17 @@ describe('limelightDigitalAdapter', function () { placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', sizes: [[350, 200]], - transactionId: '068867d1-46ec-40bb-9fa0-e24611786fb4' + transactionId: '068867d1-46ec-40bb-9fa0-e24611786fb4', + userIdAsEids: [ + { + source: 'test2.org', + uids: [ + { + id: '234', + } + ] + } + ] } const bid3 = { bidId: '019645c7d69460', @@ -41,12 +62,26 @@ describe('limelightDigitalAdapter', function () { params: { host: 'exchange.ortb.net', adUnitId: 789, - adUnitType: 'video' + adUnitType: 'video', + publisherId: 'secondPerfectPublisher' }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', sizes: [[800, 600]], - transactionId: '738d5915-6651-43b9-9b6b-d50517350917' + transactionId: '738d5915-6651-43b9-9b6b-d50517350917', + userIdAsEids: [ + { + source: 'test3.org', + uids: [ + { + id: '345', + }, + { + id: '456', + } + ] + } + ] } const bid4 = { bidId: '019645c7d69460', @@ -62,7 +97,17 @@ describe('limelightDigitalAdapter', function () { video: { playerSize: [800, 600] }, - transactionId: '738d5915-6651-43b9-9b6b-d50517350917' + transactionId: '738d5915-6651-43b9-9b6b-d50517350917', + userIdAsEids: [ + { + source: 'test.org', + uids: [ + { + id: '111', + } + ] + } + ] } describe('buildRequests', function () { @@ -82,19 +127,33 @@ describe('limelightDigitalAdapter', function () { expect(serverRequest.method).to.equal('POST') }) 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', 'secure', 'adUnits') - expect(data.deviceWidth).to.be.a('number') - expect(data.deviceHeight).to.be.a('number') - expect(data.secure).to.be.a('boolean') + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'secure', + 'adUnits' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.secure).to.be.a('boolean'); data.adUnits.forEach(adUnit => { - expect(adUnit).to.have.all.keys('id', 'bidId', 'type', 'sizes', 'transactionId') - expect(adUnit.id).to.be.a('number') - expect(adUnit.bidId).to.be.a('string') - expect(adUnit.type).to.be.a('string') - expect(adUnit.transactionId).to.be.a('string') - expect(adUnit.sizes).to.be.an('array') + expect(adUnit).to.have.all.keys( + 'id', + 'bidId', + 'type', + 'sizes', + 'transactionId', + 'publisherId', + 'userIdAsEids' + ); + expect(adUnit.id).to.be.a('number'); + expect(adUnit.bidId).to.be.a('string'); + expect(adUnit.type).to.be.a('string'); + expect(adUnit.transactionId).to.be.a('string'); + expect(adUnit.sizes).to.be.an('array'); + expect(adUnit.userIdAsEids).to.be.an('array'); }) }) }) @@ -192,7 +251,7 @@ describe('limelightDigitalAdapter', function () { expect(dataItem.meta.advertiserDomains).to.be.an('array'); expect(dataItem.meta.mediaType).to.be.a('string'); } - it('Returns an empty array if invalid response is passed', function () { + it('should return an empty array if invalid response is passed', function () { serverResponses = spec.interpretResponse('invalid_response'); expect(serverResponses).to.be.an('array').that.is.empty; }); @@ -300,107 +359,124 @@ describe('limelightDigitalAdapter', function () { }); }); describe('getUserSyncs', function () { - const serverResponses = [ - { - body: [ - { - ext: { - sync: { - iframe: 'iframeUrl', - } - } - }, - { - ext: { - sync: { - pixel: 'pixelUrl' - } - } - }, - {}, - { - ext: {} - }, - { - ext: { - sync: {} - } - }, - { - ext: { - sync: { - iframe: 'iframeUrl2', - pixel: 'pixelUrl3' + it('should return trackers for lm(only iframe) if server responses contain lm user sync header and iframe and image enabled', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; } - } - } - ] - }, - { - body: [ - { - ext: { - sync: { - iframe: 'iframeUrl2', - pixel: 'pixelUrl2' + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; } } }, - { - ext: { - sync: { - iframe: 'iframeUrl3', - pixel: 'pixelUrl3' - } - } - } - ] - } - ]; - it('should return empty array if server responses do not contain sync urls', function () { + body: [] + } + ]; const syncOptions = { iframeEnabled: true, pixelEnabled: true }; - const serverResponsesWithoutSyncUrls = serverResponses.map(serverResponse => { - const serverResponseWithoutSyncUrls = Object.assign({}, serverResponse); - serverResponseWithoutSyncUrls.body = serverResponse.body.map(serverResponseBody => { - const serverResponseBodyWithoutSyncUrls = Object.assign({}, serverResponseBody); - delete serverResponseBodyWithoutSyncUrls.ext; - return serverResponseBodyWithoutSyncUrls; - }); - return serverResponseWithoutSyncUrls; - }); - expect(spec.getUserSyncs(syncOptions, serverResponsesWithoutSyncUrls)).to.be.an('array').that.is.empty; + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ + { + type: 'iframe', + url: 'https://tracker-lm.ortb.net/sync.html' + } + ]); }); it('should return empty array if all sync types are disabled', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-1.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-1.ortb.net/sync.html'; + } + } + }, + body: [] + } + ]; const syncOptions = { iframeEnabled: false, pixelEnabled: false }; expect(spec.getUserSyncs(syncOptions, serverResponses)).to.be.an('array').that.is.empty; }); - it('should return iframe sync urls if iframe sync is enabled', function () { + it('should return no pixels if iframe sync is enabled and headers are blank', function () { + const serverResponses = [ + { + headers: null, + body: [] + } + ]; const syncOptions = { iframeEnabled: true, pixelEnabled: false }; + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.be.an('array').that.is.empty; + }); + it('should return image sync urls for lm if pixel sync is enabled and headers have lm pixel', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; + } + } + }, + body: [] + } + ]; + const syncOptions = { + iframeEnabled: false, + pixelEnabled: true + }; expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ { - type: 'iframe', - url: 'iframeUrl' - }, + type: 'image', + url: 'https://tracker-lm.ortb.net/sync' + } + ]); + }); + it('should return image sync urls for client1 and clien2 if pixel sync is enabled and two responses and headers have two pixels', function () { + const serverResponses = [ { - type: 'iframe', - url: 'iframeUrl2' + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-1.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-1.ortb.net/sync.html'; + } + } + }, + body: [] }, { - type: 'iframe', - url: 'iframeUrl3' + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-2.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-2.ortb.net/sync.html'; + } + } + }, + body: [] } - ]); - }); - it('should return image sync urls if pixel sync is enabled', function () { + ]; const syncOptions = { iframeEnabled: false, pixelEnabled: true @@ -408,47 +484,78 @@ describe('limelightDigitalAdapter', function () { expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ { type: 'image', - url: 'pixelUrl' + url: 'https://tracker-1.ortb.net/sync' }, { type: 'image', - url: 'pixelUrl3' + url: 'https://tracker-2.ortb.net/sync' + } + ]); + }); + it('should return image sync url for pll if pixel sync is enabled and two responses and headers have two same pixels', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; + } + } + }, + body: [] }, + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; + } + } + }, + body: [] + } + ]; + const syncOptions = { + iframeEnabled: false, + pixelEnabled: true + }; + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ { type: 'image', - url: 'pixelUrl2' + url: 'https://tracker-lm.ortb.net/sync' } ]); }); - it('should return all sync urls if all sync types are enabled', function () { + it('should return iframe sync url for pll if pixel sync is enabled and iframe is enables and headers have both iframe and img pixels', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; + } + } + }, + body: [] + } + ]; const syncOptions = { iframeEnabled: true, pixelEnabled: true - } + }; expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ { type: 'iframe', - url: 'iframeUrl' - }, - { - type: 'iframe', - url: 'iframeUrl2' - }, - { - type: 'iframe', - url: 'iframeUrl3' - }, - { - type: 'image', - url: 'pixelUrl' - }, - { - type: 'image', - url: 'pixelUrl3' - }, - { - type: 'image', - url: 'pixelUrl2' + url: 'https://tracker-lm.ortb.net/sync.html' } ]); }); @@ -456,10 +563,10 @@ describe('limelightDigitalAdapter', function () { }); function validateAdUnit(adUnit, bid) { - expect(adUnit.id).to.equal(bid.params.adUnitId) - expect(adUnit.bidId).to.equal(bid.bidId) - expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()) - expect(adUnit.transactionId).to.equal(bid.transactionId) + expect(adUnit.id).to.equal(bid.params.adUnitId); + expect(adUnit.bidId).to.equal(bid.bidId); + expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()); + expect(adUnit.transactionId).to.equal(bid.transactionId); let bidSizes = []; if (bid.mediaTypes) { if (bid.mediaTypes.video && bid.mediaTypes.video.playerSize) { @@ -478,4 +585,6 @@ function validateAdUnit(adUnit, bid) { height: size[1] } })); + expect(adUnit.publisherId).to.equal(bid.params.publisherId); + expect(adUnit.userIdAsEids).to.deep.equal(bid.userIdAsEids); } diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index f1de2f3bf93..4893f7cbb30 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -66,7 +66,7 @@ describe('LiveIntentId', function() { consentString: 'consentDataString' }) liveIntentIdSubmodule.getId(defaultConfigParams); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?wpn=prebid.*us_privacy=1YNY.*&gdpr=1&gdpr_consent=consentDataString.*/); + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&gdpr_consent=consentDataString.*/); }); it('should fire an event when getId and a hash is provided', function() { @@ -88,7 +88,7 @@ describe('LiveIntentId', function() { } } }}); - expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?aid=a-0001&wpn=prebid.*/); + expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); }); it('should initialize LiveConnect and emit an event with a privacy string when decode', function() { @@ -195,7 +195,7 @@ describe('LiveIntentId', function() { it('should include the LiveConnect identifier when calling the LiveIntent Identity Exchange endpoint', function() { const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' - getDataFromLocalStorageStub.withArgs('_li_duid').returns(oldCookie); + getCookieStub.withArgs('_lc2_fpi').returns(oldCookie) let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); @@ -211,7 +211,7 @@ describe('LiveIntentId', function() { it('should include the LiveConnect identifier and additional Identifiers to resolve', function() { const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' - getDataFromLocalStorageStub.withArgs('_li_duid').returns(oldCookie); + getCookieStub.withArgs('_lc2_fpi').returns(oldCookie); getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc'); const configParams = { params: { ...defaultConfigParams.params, diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index 3e568c1175d..bab7a68287d 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -38,6 +38,10 @@ const BID1 = { adId: '2ecff0db240757', auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', mediaType: 'banner', + meta: { + data: 'value1' + }, + dealId: 'dealid', getStatusCode() { return CONSTANTS.STATUS.GOOD; } @@ -54,6 +58,10 @@ const BID2 = Object.assign({}, BID1, { bidId: '3ecff0db240757', requestId: '3ecff0db240757', adId: '3ecff0db240757', + meta: { + data: 'value2' + }, + dealId: undefined }); const BID3 = { @@ -190,7 +198,10 @@ const ANALYTICS_MESSAGE = { IsBid: true, mediaType: 1, gdpr: 0, - auctionId: 0 + auctionId: 0, + meta: { + data: 'value1' + } }, { timeStamp: 1519149562216, @@ -205,7 +216,10 @@ const ANALYTICS_MESSAGE = { IsBid: true, mediaType: 1, gdpr: 0, - auctionId: 0 + auctionId: 0, + meta: { + data: 'value2' + } }, { timeStamp: 1519149562216, @@ -231,7 +245,11 @@ const ANALYTICS_MESSAGE = { orgCpm: 120, mediaType: 1, gdpr: 0, - auctionId: 0 + auctionId: 0, + meta: { + data: 'value1' + }, + dealId: 'dealid' }, { timeStamp: 1519149562216, @@ -244,7 +262,10 @@ const ANALYTICS_MESSAGE = { orgCpm: 230, mediaType: 1, gdpr: 0, - auctionId: 0 + auctionId: 0, + meta: { + data: 'value2' + } } ], rf: [ @@ -552,6 +573,28 @@ describe('Livewrapped analytics adapter', function () { expect(message.wins[0].floor).to.equal(1.1); expect(message.wins[1].floor).to.equal(2.2); }); + + it('should forward runner-up data as given by Livewrapped wrapper', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_WON, Object.assign({}, + MOCK.BID_WON[0], + { + 'rUp': 'rUpObject' + })); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.wins.length).to.equal(1); + expect(message.wins[0].rUp).to.equal('rUpObject'); + }); }); describe('when given other endpoint', function () { diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 085848cb97d..fac1ae1d6a0 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -885,6 +885,37 @@ describe('Livewrapped adapter tests', function () { expect(data.rtbData.user.ext.eids).to.deep.equal(testbidRequest.bids[0].userIdAsEids); }); + it('should merge user ids with existing ortb2', 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 === 'ortb2') { + return {user: {ext: {prop: 'value'}}}; + } + return origGetConfig.apply(config, arguments); + }); + + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + testbidRequest.bids[0].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [{ + 'id': 'publisher-common-id', + 'atype': 1 + }] + } + ]; + + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + var expected = {user: {ext: {prop: 'value', eids: testbidRequest.bids[0].userIdAsEids}}} + + expect(data.rtbData).to.deep.equal(expected); + }); + it('should send schain object if available', function() { sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js new file mode 100644 index 00000000000..6e2c7fe8e2c --- /dev/null +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -0,0 +1,323 @@ +import { spec } from 'modules/lkqdBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { expect } from 'chai'; + +describe('lkqdBidAdapter', () => { + const BIDDER_CODE = 'lkqd'; + const SITE_ID = '662921'; + const PUBLISHER_ID = '263'; + const END_POINT = new URL('https://rtb.lkqd.net/ad'); + const ADAPTER = newBidder(spec); + + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + context('inherited functions', () => { + it('exists and is a function', () => { + expect(ADAPTER.callBids).to.exist.and.to.be.a('function'); + }); + }); + + context('isBidRequestValid', () => { + const bid = { + bidder: BIDDER_CODE, + params: { + 'siteId': SITE_ID, + 'placementId': PUBLISHER_ID + }, + adUnitCode: 'video1', + sizes: [[300, 250], [640, 480]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + requestId: 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + transactionId: 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + wrong: 'missing zone id' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + context('buildRequests', () => { + const bidRequests = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'siteId': SITE_ID, + 'placementId': PUBLISHER_ID, + 'c1': 'newWindow', + 'c20': 'lkqdCustom' + }, + 'adUnitCode': BIDDER_CODE, + 'sizes': [[300, 250], [640, 480]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + const bidRequest = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'siteId': SITE_ID, + 'placementId': PUBLISHER_ID, + 'schain': '1.0,1!exchange1.com,1234%21abcd,1,bid-request-1,publisher%2c%20Inc.,publisher.com' + }, + 'adUnitCode': BIDDER_CODE, + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + + it('should have correct url params, method', () => { + const requests = spec.buildRequests(bidRequests, {}); + expect(requests[0].method).to.eq('POST'); + + const url1 = new URL(requests[0].url); + expect(url1.origin).to.eq(END_POINT.origin); + + const params1 = new URLSearchParams(url1.search); + const object1 = Object.fromEntries(params1.entries()); + expect(object1.pid).to.eq(PUBLISHER_ID); + expect(object1.sid).to.eq(SITE_ID); + expect(object1.output).to.eq('rtb'); + expect(object1.prebid).to.eq('true'); + }); + + it('should populate height, width, c1, c20, coppa with 2 imp', () => { + sandbox.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + + const requests = spec.buildRequests(bidRequests, {}); + expect(requests.length).to.equal(1); + + const serverRequestObject = requests[0]; + expect(serverRequestObject.data.imp.length).to.equal(2); + expect(serverRequestObject.data.regs.coppa).to.be.a('number'); + expect(serverRequestObject.data.regs.coppa).to.eq(1); + + const imp1 = serverRequestObject.data.imp[0]; + expect(imp1.video.w).to.be.a('number'); + expect(imp1.video.w).to.eq(300); + expect(imp1.video.h).to.be.a('number'); + expect(imp1.video.h).to.eq(250); + expect(imp1.video.ext.lkqdcustomparameters.c1).to.be.a('string'); + expect(imp1.video.ext.lkqdcustomparameters.c1).to.eq('newWindow'); + expect(imp1.video.ext.lkqdcustomparameters.c20).to.be.a('string'); + expect(imp1.video.ext.lkqdcustomparameters.c20).to.eq('lkqdCustom'); + + const imp2 = serverRequestObject.data.imp[1]; + expect(imp2.video.w).to.be.a('number'); + expect(imp2.video.w).to.eq(640); + expect(imp2.video.h).to.be.a('number'); + expect(imp2.video.h).to.eq(480); + expect(imp2.video.ext.lkqdcustomparameters.c1).to.be.a('string'); + expect(imp2.video.ext.lkqdcustomparameters.c1).to.eq('newWindow'); + expect(imp2.video.ext.lkqdcustomparameters.c20).to.be.a('string'); + expect(imp2.video.ext.lkqdcustomparameters.c20).to.eq('lkqdCustom'); + }); + + it('should not populate unspecified parameters', () => { + const requests = spec.buildRequests(bidRequests); + + const serverRequestObject = requests[0]; + expect(serverRequestObject.data.device.dnt).to.be.a('undefined'); + expect(serverRequestObject.data.content).to.be.a('undefined'); + expect(serverRequestObject.data.regs.coppa).to.be.a('undefined'); + expect(serverRequestObject.data.source).to.be.a('undefined'); + + const imp1 = serverRequestObject.data.imp[0]; + expect(imp1.video.ext.lkqdcustomparameters.c10).to.be.a('undefined'); + + const imp2 = serverRequestObject.data.imp[1]; + expect(imp2.video.ext.lkqdcustomparameters.c39).to.be.a('undefined'); + }); + + it('should handle single size request', () => { + const requests = spec.buildRequests(bidRequest, {}); + const serverRequestObject = requests[0]; + expect(serverRequestObject.data.imp.length).to.equal(1); + expect(serverRequestObject.data.source).to.not.be.a('undefined'); + expect(serverRequestObject.data.source.ext).to.not.be.a('undefined'); + expect(serverRequestObject.data.source.ext.schain).to.not.be.a('undefined'); + + const imp1 = serverRequestObject.data.imp[0]; + expect(imp1.video.w).to.be.a('number'); + expect(imp1.video.w).to.eq(640); + expect(imp1.video.h).to.be.a('number'); + expect(imp1.video.h).to.eq(480); + + const schain = serverRequestObject.data.source.ext.schain; + expect(schain.validation).to.have.string('strict'); + expect(schain.config.ver).to.have.string('1.0'); + expect(schain.config.complete).to.eq(1); + expect(schain.config.nodes[0].asi).to.have.string('exchange1.com'); + expect(schain.config.nodes[0].sid).to.have.string('1234!abcd'); + expect(schain.config.nodes[0].hp).to.eq(1); + expect(schain.config.nodes[0].rid).to.have.string('bid-request-1'); + expect(schain.config.nodes[0].name).to.have.string('publisher, Inc.'); + expect(schain.config.nodes[0].domain).to.have.string('publisher.com'); + }); + }); + + context('interpretResponse', () => { + const bidRequest = { + 'method': 'POST', + 'url': `https://rtb.lkqd.net/ad?pid=${PUBLISHER_ID}&sid=${SITE_ID}&output=rtb&prebid=true`, + 'id': '5511262729333416592', + 'imp': [{ + 'id': '5511262729333416592', + 'displaymanager': 'LKQD SDK', + 'video': { + 'mimes': ['application/x-mpegURL', 'video/mp4', 'video/H264'], + 'protocols': [1, 2, 3, 4, 5, 6, 7, 8], + 'w': 1920, + 'h': 1080, + 'startdelay': 0, + 'placement': 1, + 'playbackmethod': [1], + 'ext': { + 'lkqdcustomparameters': { + 'custom1': 'cus1', + 'custom4': 'cus4', + 'custom7': 'cus7' + } + } + }, + 'bidfloorcur': 'USD', + 'secure': 1 + }], + 'app': { + 'id': '1112444-5742940365329809425', + 'name': 'lkqdappfortesting', + 'bundle': 'com.lkqdbundleid.fortesting', + 'content': { + 'id': '123456', + 'title': 'MyLkqdContent', + 'url': 'https://lkqd.com', + 'len': 600 + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (SMART-TV; Linux; Tizen 4.0) AppleWebKit/538.1 (KHTML, like Gecko) Version/4.0 TV Safari/538.1', + 'geo': { + 'utcoffset': -420, + }, + 'dnt': 0, + 'ip': '184.103.177.205', + 'ifa': 'f4254ada-4174-6cfd-83c7-2f999c457c1d' + }, + 'user': { + 'ext': {} + }, + 'test': 0, + 'at': 2, + 'tmax': 100, + 'cur': ['USD'], + 'source': { + 'ext': { + 'schain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [{ + 'asi': 'lkqd.net', + 'sid': '604', + 'hp': 1 + }] + } + } + }, + 'regs': { + 'coppa': 1, + 'ext': { + 'gdpr': 0, + 'us_privacy': '1NNN' + } + } + }; + + const serverResponse = { + body: { + 'id': '5511262729333416592', + 'seatbid': [{ + 'bid': [{ + 'id': '281063413403658952', + 'impid': '5511262729333416592', + 'price': 5.409, + 'adm': 'LKQD00:00:15', + 'adomain': ['lkqd.com'], + 'crid': 'lkqd-rtb-79-1030666', + 'protocol': 3, + 'w': 1920, + 'h': 1080, + 'ext': { + 'imptrackers': [] + } + }] + }], + 'cur': 'USD' + } + }; + + it('should correctly parse valid bid response', () => { + const bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).to.equal(1); + + const bidResponse = bidResponses[0]; + expect(bidResponse.requestId).to.equal('5511262729333416592'); + expect(bidResponse.ad).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(bidResponse.cpm).to.equal(5.409); + expect(bidResponse.width).to.equal(1920); + expect(bidResponse.height).to.equal(1080); + expect(bidResponse.ttl).to.equal(300); + expect(bidResponse.creativeId).to.equal('lkqd-rtb-79-1030666'); + expect(bidResponse.currency).to.equal('USD'); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.meta.mediaType).to.equal('video'); + }); + + it('safely handles invalid bid response', () => { + let invalidServerResponse = {}; + invalidServerResponse.body = ''; + + let result = spec.interpretResponse(invalidServerResponse, bidRequest); + expect(result.length).to.equal(0); + }); + + it('handles nobid responses', () => { + let nobidResponse = {}; + nobidResponse.body = { + seatbid: [ + { + bid: [] + } + ] + }; + + let result = spec.interpretResponse(nobidResponse, bidRequest); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/loglyliftBidAdapter_spec.js b/test/spec/modules/loglyliftBidAdapter_spec.js new file mode 100644 index 00000000000..6a68ef856ff --- /dev/null +++ b/test/spec/modules/loglyliftBidAdapter_spec.js @@ -0,0 +1,172 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/loglyliftBidAdapter'; +import * as utils from 'src/utils.js'; + +describe('loglyliftBidAdapter', function () { + const nativeBidRequests = [{ + bidder: 'loglylift', + bidId: '254304ac29e265', + params: { + adspotId: 16 + }, + adUnitCode: '/19968336/prebid_native_example_1', + transactionId: '10aee457-617c-4572-ab5b-99df1d73ccb4', + sizes: [ + [] + ], + bidderRequestId: '15da3afd9632d7', + auctionId: 'f890b7d9-e787-4237-ac21-6d8554abac9f', + mediaTypes: { + native: { + body: { + required: true + }, + icon: { + required: false + }, + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + } + }]; + + const bidderRequest = { + refererInfo: { + referer: 'fakeReferer', + reachedTop: true, + numIframes: 1, + stack: [] + }, + auctionStart: 1632194172781, + bidderCode: 'loglylift', + bidderRequestId: '15da3afd9632d7', + auctionId: 'f890b7d9-e787-4237-ac21-6d8554abac9f', + timeout: 3000 + }; + + const nativeServerResponse = { + body: { + bids: [{ + requestId: '254304ac29e265', + cpm: 10.123, + width: 360, + height: 360, + creativeId: '123456789', + currency: 'JPY', + netRevenue: true, + ttl: 30, + meta: { + advertiserDomains: ['advertiserexample.com'] + }, + native: { + clickUrl: 'https://dsp.logly.co.jp/click?ad=EXAMPECLICKURL', + image: { + url: 'https://cdn.logly.co.jp/images/000/194/300/normal.jpg', + width: '360', + height: '360' + }, + impressionTrackers: [ + 'https://b.logly.co.jp/sorry.html' + ], + sponsoredBy: 'logly', + title: 'Native Title', + } + }], + } + }; + + describe('isBidRequestValid', function () { + it('should return true if the adspotId parameter is present', function () { + expect(spec.isBidRequestValid(nativeBidRequests[0])).to.be.true; + }); + + it('should return false if the adspotId parameter is not present', function () { + let bidRequest = utils.deepClone(nativeBidRequests[0]); + delete bidRequest.params.adspotId; + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }); + }); + + describe('buildRequests', function () { + it('should generate a valid single POST request for multiple bid requests', function () { + const request = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.logly.co.jp/prebid/client/v1?adspot_id=16'); + expect(request.data).to.exist; + + const data = JSON.parse(request.data); + expect(data.auctionId).to.equal(nativeBidRequests[0].auctionId); + expect(data.bidderRequestId).to.equal(nativeBidRequests[0].bidderRequestId); + expect(data.transactionId).to.equal(nativeBidRequests[0].transactionId); + expect(data.adUnitCode).to.equal(nativeBidRequests[0].adUnitCode); + expect(data.bidId).to.equal(nativeBidRequests[0].bidId); + expect(data.mediaTypes).to.deep.equal(nativeBidRequests[0].mediaTypes); + expect(data.params).to.deep.equal(nativeBidRequests[0].params); + expect(data.prebidJsVersion).to.equal('$prebid.version$'); + expect(data.url).to.exist; + expect(data.domain).to.exist; + expect(data.referer).to.equal(bidderRequest.refererInfo.referer); + expect(data.auctionStartTime).to.equal(bidderRequest.auctionStart); + expect(data.currency).to.exist; + expect(data.timeout).to.equal(bidderRequest.timeout); + }); + }); + + describe('interpretResponse', function () { + it('should return an empty array if an invalid response is passed', function () { + const interpretedResponse = spec.interpretResponse({}, {}); + expect(interpretedResponse).to.be.an('array').that.is.empty; + }); + + it('should return valid response when passed valid server response', function () { + const request = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; + const interpretedResponse = spec.interpretResponse(nativeServerResponse, request); + + expect(interpretedResponse).to.have.lengthOf(1); + expect(interpretedResponse[0].cpm).to.equal(nativeServerResponse.body.bids[0].cpm); + expect(interpretedResponse[0].width).to.equal(nativeServerResponse.body.bids[0].width); + expect(interpretedResponse[0].height).to.equal(nativeServerResponse.body.bids[0].height); + expect(interpretedResponse[0].creativeId).to.equal(nativeServerResponse.body.bids[0].creativeId); + expect(interpretedResponse[0].currency).to.equal(nativeServerResponse.body.bids[0].currency); + expect(interpretedResponse[0].netRevenue).to.equal(nativeServerResponse.body.bids[0].netRevenue); + expect(interpretedResponse[0].ttl).to.equal(nativeServerResponse.body.bids[0].ttl); + expect(interpretedResponse[0].native).to.deep.equal(nativeServerResponse.body.bids[0].native); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal(nativeServerResponse.body.bids[0].meta.advertiserDomains[0]); + }); + }); + + describe('getUserSync tests', function () { + it('UserSync test : check type = iframe, check usermatch URL', function () { + const syncOptions = { + 'iframeEnabled': true + } + let userSync = spec.getUserSyncs(syncOptions, [nativeServerResponse]); + expect(userSync[0].type).to.equal('iframe'); + const USER_SYNC_URL = 'https://sync.logly.co.jp/sync/sync.html'; + expect(userSync[0].url).to.equal(USER_SYNC_URL); + }); + + it('When iframeEnabled is false, no userSync should be returned', function () { + const syncOptions = { + 'iframeEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions, [nativeServerResponse]); + expect(userSync).to.be.an('array').that.is.empty; + }); + + it('When nativeServerResponses empty, no userSync should be returned', function () { + const syncOptions = { + 'iframeEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions, []); + expect(userSync).to.be.an('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index 5b55adffa9e..090144ab158 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -2,6 +2,7 @@ import { lotamePanoramaIdSubmodule, storage, } from 'modules/lotamePanoramaIdSystem.js'; +import { uspDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; import sinon from 'sinon'; @@ -16,6 +17,7 @@ describe('LotameId', function() { let setLocalStorageStub; let removeFromLocalStorageStub; let timeStampStub; + let uspConsentDataStub; const nowTimestamp = new Date().getTime(); @@ -30,6 +32,7 @@ describe('LotameId', function() { 'removeDataFromLocalStorage' ); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); }); afterEach(function () { @@ -40,6 +43,7 @@ describe('LotameId', function() { setLocalStorageStub.restore(); removeFromLocalStorageStub.restore(); timeStampStub.restore(); + uspConsentDataStub.restore(); }); describe('caching initial data received from the remote server', function () { @@ -726,4 +730,227 @@ describe('LotameId', function() { ); }); }); + + describe('with a custom client id', function () { + describe('with a client expiry set', function () { + beforeEach(function () { + getCookieStub + .withArgs('panoramaId_expiry_1234') + .returns(String(Date.now() + 500 * 1000)); + }); + + describe('and an existing pano id', function() { + let submoduleCallback; + beforeEach(function () { + getCookieStub + .withArgs('panoramaId_expiry') + .returns(String(Date.now() + 100000)); + getCookieStub + .withArgs('panoramaId') + .returns( + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87c' + ); + submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ); + }); + + it('should not call the remote server when getId is called nor get an id', function () { + expect(submoduleCallback).to.be.eql({ + id: undefined, + reason: 'NO_CLIENT_CONSENT', + }); + }); + }); + + describe('and no existing pano id', function () { + let submoduleCallback; + + beforeEach(function () { + // Let the panoramaId_expiry be empty + + submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ); + }); + + it('should not call the remote server nor return an id', function () { + expect(submoduleCallback).to.be.eql({ + id: undefined, + reason: 'NO_CLIENT_CONSENT', + }); + }); + }); + }); + + describe('with no client expiry set', function () { + describe('and no existing pano id', function () { + let request; + let callBackSpy = sinon.spy(); + + beforeEach(function () { + uspConsentDataStub.returns('1NNN'); + let submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ).callback; + submoduleCallback(callBackSpy); + + request = server.requests[0]; + + request.respond( + 200, + responseHeader, + JSON.stringify({ + profile_id: '4ec137245858469eb94a4e248f238694', + core_id: + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87f', + expiry_ts: 3600000, + }) + ); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + 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' + ); + }); + + it('should NOT set an expiry for the client', function () { + sinon.assert.neverCalledWith( + setCookieStub, + 'panoramaId_expiry_1234', + sinon.match.number + ); + }); + }); + + describe('and an existing pano id', function () { + let submoduleCallback; + + beforeEach(function () { + getCookieStub + .withArgs('panoramaId_expiry') + .returns(String(Date.now() + 100000)); + getCookieStub + .withArgs('panoramaId') + .returns( + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87c' + ); + submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ); + }); + + it('should not call the remote server but use the cached value', function () { + expect(submoduleCallback).to.be.eql({ + id: 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87c', + }); + }); + + it('should NOT set an expiry for the client', function () { + sinon.assert.neverCalledWith( + setCookieStub, + 'panoramaId_expiry_1234', + sinon.match.number + ); + }); + }); + }); + describe('when client consent has errors', function () { + let request; + let callBackSpy = sinon.spy(); + + beforeEach(function () { + let submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ).callback; + submoduleCallback(callBackSpy); + + request = server.requests[0]; + + request.respond( + 200, + responseHeader, + JSON.stringify({ + expiry_ts: 3600000, + errors: [111], + no_consent: 'CLIENT', + }) + ); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should pass client id back', function () { + expect(request.url).to.be.eq( + 'https://id.crwdcntrl.net/id?gdpr_applies=false&c=1234' + ); + }); + + it('should set the received expiry for the client', function() { + sinon.assert.calledWith( + setCookieStub, + 'panoramaId_expiry_1234', + 3600000 + ); + }); + + it('should not clear the cache for the panorama id', function() { + sinon.assert.neverCalledWith( + setCookieStub, + 'panoramaId', + sinon.match.any + ); + }); + + it('should not clear the cache for the panorama id expiry', function () { + sinon.assert.neverCalledWith( + setCookieStub, + 'panoramaId_expiry', + sinon.match.any + ); + }); + }); + }); }); diff --git a/test/spec/modules/lunamediahbBidAdapter_spec.js b/test/spec/modules/lunamediahbBidAdapter_spec.js index e9f88935ed5..181b6e75fe7 100644 --- a/test/spec/modules/lunamediahbBidAdapter_spec.js +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -80,7 +80,7 @@ describe('LunamediaHBBidAdapter', function () { expect(data).to.be.an('object'); let placement = data['placements'][0]; expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain', 'videoContext'); expect(placement.traffic).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); expect(placement.hPlayer).to.equal(playerSize[1]); @@ -301,4 +301,30 @@ describe('LunamediaHBBidAdapter', function () { 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://cookie.lmgssp.com/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://cookie.lmgssp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); }); diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js new file mode 100755 index 00000000000..8aeecc87c98 --- /dev/null +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -0,0 +1,412 @@ +import { resetUserSync, spec, hasValidSupplyChainParams } from 'modules/luponmediaBidAdapter.js'; +const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; + +describe('luponmediaBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 12345, + 'keyId': '4o2c4' + }, + 'adUnitCode': 'test-div', + 'sizes': [[300, 250]], + 'bidId': 'g1987234bjkads', + 'bidderRequestId': '290348ksdhkas89324', + 'auctionId': '20384rlek235', + }; + + it('should return true when required params are 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 = { + 'siteId': 12345 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c4' + }, + 'crumbs': { + 'pubcid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'transactionId': '585d96a5-bd93-4a89-b8ea-0f546f3aaa82', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '268a30af10dd6f', + 'bidderRequestId': '140411b5010a2a', + 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + } + } + ]; + + let bidderRequest = { + 'bidderCode': 'luponmedia', + 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', + 'bidderRequestId': '140411b5010a2a', + 'bids': [ + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c4' + }, + 'crumbs': { + 'pubcid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'transactionId': '585d96a5-bd93-4a89-b8ea-0f546f3aaa82', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '268a30af10dd6f', + 'bidderRequestId': '140411b5010a2a', + 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + } + } + ], + 'auctionStart': 1587413920820, + 'timeout': 2000, + 'refererInfo': { + 'referer': 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines' + ] + }, + 'start': 1587413920835 + }; + + it('sends bid request to ENDPOINT via POST', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + let dynRes = JSON.parse(requests.data); + expect(requests.url).to.equal(ENDPOINT_URL); + expect(requests.method).to.equal('POST'); + expect(requests.data).to.equal('{"id":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","test":0,"source":{"tid":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"novi.ba","sid":"199424","hp":1}]}}},"tmax":1500,"imp":[{"id":"268a30af10dd6f","secure":1,"ext":{"luponmedia":{"siteId":303522,"keyId":"4o2c4"}},"banner":{"format":[{"w":300,"h":250}]}}],"ext":{"prebid":{"targeting":{"includewinners":true,"includebidderkeys":false}}},"user":{"id":"' + dynRes.user.id + '","buyeruid":"8d8b16cb-1383-4a0f-b4bb-0be28464d974"},"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}'); + }); + }); + + describe('interpretResponse', function () { + it('should get correct banner bid response', function () { + let response = { + 'id': '4776d680-15a2-45c3-bad5-db6bebd94a06', + 'seatbid': [ + { + 'bid': [ + { + 'id': '2a122246ef72ea', + 'impid': '2a122246ef72ea', + 'price': 0.43, + 'adm': ' ', + 'adid': '56380110', + 'cid': '44724710', + 'crid': '443801010', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'targeting': { + 'hb_bidder': 'luponmedia', + 'hb_pb': '0.40', + 'hb_size': '300x250' + }, + 'type': 'banner' + } + } + } + ], + 'seat': 'luponmedia' + } + ], + 'cur': 'USD', + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'ok', + 'bidder_status': [] + } + } + }; + + let expectedResponse = [ + { + 'requestId': '2a122246ef72ea', + 'cpm': '0.43', + 'width': 300, + 'height': 250, + 'creativeId': '443801010', + 'currency': 'USD', + 'dealId': '23425', + 'netRevenue': false, + 'ttl': 300, + 'referrer': '', + 'ad': ' ' + } + ]; + + let bidderRequest = { + 'data': '{"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}' + }; + + let result = spec.interpretResponse({ body: response }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + let noBidResponse = []; + + let noBidBidderRequest = { + 'data': '{"site":{"page":""}}' + } + let noBidResult = spec.interpretResponse({ body: noBidResponse }, noBidBidderRequest); + expect(noBidResult.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + const bidResponse1 = { + 'body': { + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'ok', + 'bidder_status': [ + { + 'bidder': 'luponmedia', + 'no_cookie': true, + 'usersync': { + 'url': 'https://adxpremium.services/api/usersync', + 'type': 'redirect' + } + }, + { + 'bidder': 'luponmedia', + 'no_cookie': true, + 'usersync': { + 'url': 'https://adxpremium.services/api/iframeusersync', + 'type': 'iframe' + } + } + ] + } + } + } + }; + + const bidResponse2 = { + 'body': { + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'no_cookie', + 'bidder_status': [] + } + } + } + }; + + it('should use a sync url from first response (pixel and iframe)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [bidResponse1, bidResponse2]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://adxpremium.services/api/usersync' + }, + { + type: 'iframe', + url: 'https://adxpremium.services/api/iframeusersync' + } + ]); + }); + + it('handle empty response (e.g. timeout)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('returns empty syncs when not pixel enabled and not iframe enabled', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([]); + }); + + it('returns pixel syncs when pixel enabled and not iframe enabled', function() { + resetUserSync(); + + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://adxpremium.services/api/usersync' + } + ]); + }); + + it('returns iframe syncs when not pixel enabled and iframe enabled', function() { + resetUserSync(); + + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: true }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://adxpremium.services/api/iframeusersync' + } + ]); + }); + }); + + describe('hasValidSupplyChainParams', function () { + it('returns true if schain is valid', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + }; + + const checkSchain = hasValidSupplyChainParams(schain); + expect(checkSchain).to.equal(true); + }); + + it('returns false if schain is invalid', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'invalid': 'novi.ba' + } + ] + }; + + const checkSchain = hasValidSupplyChainParams(schain); + expect(checkSchain).to.equal(false); + }); + }); + + describe('onBidWon', function () { + const bidWonEvent = { + 'bidderCode': 'luponmedia', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '105bbf8c54453ff', + 'requestId': '934b8752185955', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.364, + 'creativeId': '443801010', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 300, + 'referrer': '', + 'ad': '', + 'auctionId': '926a8ea3-3dd4-4bf2-95ab-c85c2ce7e99b', + 'responseTimestamp': 1598527728026, + 'requestTimestamp': 1598527727629, + 'bidder': 'luponmedia', + 'adUnitCode': 'div-gpt-ad-1533155193780-5', + 'timeToRespond': 397, + 'size': '300x250', + 'status': 'rendered' + }; + + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'sendWinningsToServer') + }) + + afterEach(() => { + ajaxStub.restore() + }) + + it('calls luponmedia\'s callback endpoint', () => { + const result = spec.onBidWon(bidWonEvent); + expect(result).to.equal(undefined); + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.deep.equal(JSON.stringify(bidWonEvent)); + }); + }); +}); diff --git a/test/spec/modules/malltvAnalyticsAdapter_spec.js b/test/spec/modules/malltvAnalyticsAdapter_spec.js index 599ac6e4256..c96069df0f9 100644 --- a/test/spec/modules/malltvAnalyticsAdapter_spec.js +++ b/test/spec/modules/malltvAnalyticsAdapter_spec.js @@ -4,7 +4,7 @@ import { } from 'modules/malltvAnalyticsAdapter.js' import { expect } from 'chai' import { getCpmInEur } from '../../../modules/malltvAnalyticsAdapter' -import events from 'src/events' +import * as events from 'src/events' import constants from 'src/constants.json' const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e' diff --git a/test/spec/modules/mass_spec.js b/test/spec/modules/mass_spec.js index a4a87ce113f..3b5d89b0a8c 100644 --- a/test/spec/modules/mass_spec.js +++ b/test/spec/modules/mass_spec.js @@ -60,17 +60,9 @@ const mockedNonMassBids = [ }, ]; -// mock bidder request: -const mockedBidderRequest = { - bidderCode: 'ix', - bidderRequestId: 'bidder-request-id-1' -}; - const noop = function() {}; describe('MASS Module', function() { - let bidderRequest = Object.assign({}, mockedBidderRequest); - it('should be enabled by default', function() { expect(isEnabled).to.equal(true); }); @@ -86,9 +78,7 @@ describe('MASS Module', function() { const originalBid = Object.assign({}, mockedBid); const bid = Object.assign({}, originalBid); - bidderRequest.bids = [bid]; - - addBidResponseHook.call({bidderRequest}, noop, 'ad-code-id', bid); + addBidResponseHook(noop, 'ad-code-id', bid); expect(bid).to.deep.equal(originalBid); }); @@ -100,9 +90,7 @@ describe('MASS Module', function() { const originalBid = Object.assign({}, mockedBid); const bid = Object.assign({}, originalBid); - bidderRequest.bids = [bid]; - - addBidResponseHook.call({bidderRequest}, noop, 'ad-code-id', bid); + addBidResponseHook(noop, 'ad-code-id', bid); expect(bid.ad).to.not.equal(originalBid.ad); diff --git a/test/spec/modules/mediafuseBidAdapter_spec.js b/test/spec/modules/mediafuseBidAdapter_spec.js new file mode 100644 index 00000000000..f4defa293e1 --- /dev/null +++ b/test/spec/modules/mediafuseBidAdapter_spec.js @@ -0,0 +1,1442 @@ +import { expect } from 'chai'; +import { spec } from 'modules/mediafuseBidAdapter.js'; +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 { config } from 'src/config.js'; + +const ENDPOINT = 'https://ib.adnxs.com/ut/v3/prebid'; + +describe('MediaFuseAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'mediafuse', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'member': '1234', + 'invCode': 'ABCD' + }; + + 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 = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let getAdUnitsStub; + let bidRequests = [ + { + 'bidder': 'mediafuse', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + + beforeEach(function() { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + return []; + }); + }); + + afterEach(function() { + getAdUnitsStub.restore(); + }); + + it('should parse out private sizes', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + privateSizes: [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 publisher_id in request', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + publisherId: '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); + expect(payload.sdk).to.exist; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + }); + + it('should populate the ad_types array on all requests', function () { + let adUnits = [{ + code: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'mediafuse', + params: { + placementId: '10433394' + } + }], + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + }]; + + ['banner', 'video', 'native'].forEach(type => { + getAdUnitsStub.callsFake(function(...args) { + return adUnits; + }); + + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes[type] = {}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal([type]); + + if (type === 'banner') { + delete adUnits[0].mediaTypes; + } + }); + }); + + it('should not populate the ad_types array when adUnit.mediaTypes is undefined', function() { + const bidRequest = Object.assign({}, bidRequests[0]); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.not.exist; + }); + + it('should populate the ad_types array on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should attach valid video params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + video: { + id: 123, + minduration: 100, + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('should include ORTB video values when video params were not set', function() { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + + it('should add video property when adUnit includes a renderer', function () { + const videoData = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + }, + params: { + placementId: '10433394', + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + }; + + let bidRequest1 = deepClone(bidRequests[0]); + bidRequest1 = Object.assign({}, bidRequest1, videoData, { + renderer: { + url: 'https://test.renderer.url', + render: function () {} + } + }); + + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + bidRequest2 = Object.assign({}, bidRequest2, videoData); + + const request = spec.buildRequests([bidRequest1, bidRequest2]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + skippable: true, + playback_method: 2, + custom_renderer_present: true + }); + expect(payload.tags[1].video).to.deep.equal({ + skippable: true, + playback_method: 2 + }); + }); + + it('should attach valid user params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + user: { + externalUid: '123', + segments: [123, { id: 987, value: 876 }], + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.user).to.exist; + expect(payload.user).to.deep.equal({ + external_uid: '123', + segments: [{id: 123}, {id: 987, value: 876}] + }); + }); + + it('should attach reserve param when either bid param or getFloor function exists', function () { + let getFloorResponse = { currency: 'USD', floor: 3 }; + let request, payload = null; + let bidRequest = deepClone(bidRequests[0]); + + // 1 -> reserve not defined, getFloor not defined > empty + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.not.exist; + + // 2 -> reserve is defined, getFloor not defined > reserve is used + bidRequest.params = { + 'placementId': '10433394', + 'reserve': 0.5 + }; + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.exist.and.to.equal(0.5); + + // 3 -> reserve is defined, getFloor is defined > getFloor is used + bidRequest.getFloor = () => getFloorResponse; + + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.exist.and.to.equal(3); + }); + + it('should duplicate adpod placements into batches and set correct maxduration', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + + // 300 / 15 = 20 total + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload1.tags[0].video.maxduration).to.equal(30); + + expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload2.tags[0].video.maxduration).to.equal(30); + }); + + it('should round down adpod placements when numbers are uneven', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 123, + durationRangeSec: [45], + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(2); + }); + + it('should duplicate adpod placements when requireExactDuration is set', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true, + } + } + } + ); + + // 20 total placements with 15 max impressions = 2 requests + const request = spec.buildRequests([bidRequest]); + expect(request.length).to.equal(2); + + // 20 spread over 2 requests = 15 in first request, 5 in second + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + // 10 placements should have max/min at 15 + // 10 placemenst should have max/min at 30 + const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); + const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); + expect(payload1tagsWith15.length).to.equal(10); + expect(payload1tagsWith30.length).to.equal(5); + + // 5 placemenst with min/max at 30 were in the first request + // so 5 remaining should be in the second + const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); + expect(payload2tagsWith30.length).to.equal(5); + }); + + it('should set durations for placements when requireExactDuration is set and numbers are uneven', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 105, + durationRangeSec: [15, 30, 60], + requireExactDuration: true, + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(7); + + const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); + const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); + const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); + expect(tagsWith15.length).to.equal(3); + expect(tagsWith30.length).to.equal(3); + expect(tagsWith60.length).to.equal(1); + }); + + it('should break adpod request into batches', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 225, + durationRangeSec: [5], + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + const payload3 = JSON.parse(request[2].data); + + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(15); + expect(payload3.tags.length).to.equal(15); + }); + + it('should contain hb_source value for adpod', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + const request = spec.buildRequests([bidRequest])[0]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(7); + }); + + it('should contain hb_source value for other media', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'banner', + params: { + sizes: [[300, 250], [300, 600]], + placementId: 13144370 + } + } + ); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('adds brand_category_exclusion to request when set', function() { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon + .stub(config, 'getConfig') + .withArgs('adpod.brandCategoryExclusion') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.brand_category_uniqueness).to.equal(true); + + config.getConfig.restore(); + }); + + it('adds auction level keywords to request when set', function() { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon + .stub(config, 'getConfig') + .withArgs('mediafuseAuctionKeywords') + .returns({ + gender: 'm', + music: ['rock', 'pop'], + test: '' + }); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.keywords).to.deep.equal([{ + 'key': 'gender', + 'value': ['m'] + }, { + 'key': 'music', + 'value': ['rock', 'pop'] + }, { + 'key': 'test' + }]); + + config.getConfig.restore(); + }); + + it('should attach native params to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + title: {required: true}, + body: {required: true}, + body2: {required: true}, + image: {required: true, sizes: [100, 100]}, + icon: {required: true}, + cta: {required: false}, + rating: {required: true}, + sponsoredBy: {required: true}, + privacyLink: {required: true}, + displayUrl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + salePrice: {required: true} + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].native.layouts[0]).to.deep.equal({ + title: {required: true}, + description: {required: true}, + desc2: {required: true}, + main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, + icon: {required: true}, + ctatext: {required: false}, + rating: {required: true}, + sponsored_by: {required: true}, + privacy_link: {required: true}, + displayurl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + saleprice: {required: true}, + privacy_supported: true + }); + expect(payload.tags[0].hb_source).to.equal(1); + }); + + it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + image: { required: true } + } + } + ); + bidRequest.sizes = [[150, 100], [300, 250]]; + + let request = spec.buildRequests([bidRequest]); + let payload = JSON.parse(request.data); + expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); + + delete bidRequest.sizes; + + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); + + expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); + }); + + it('should convert keyword params to proper form and attaches to request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [5], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + emptyStr: '', + emptyArr: [''], + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['5'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' + }]); + }); + + it('should add payment rules to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + usePaymentRule: 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]); + bidRequest.ortb2Imp = { ext: { data: { pbadslot: testGpid } } }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].gpid).to.exist.and.equal(testGpid) + }); + + it('should add gdpr consent information to the request', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'mediafuse', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true, + addtlConsent: '1~7.12.35.62.66.70.89.93.108' + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.options).to.deep.equal({withCredentials: true}); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_consent).to.exist; + expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); + expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; + expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); + }); + + it('should add us privacy string to payload', function() { + let consentString = '1YA-'; + let bidderRequest = { + 'bidderCode': 'mediafuse', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': consentString + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.us_privacy).to.exist; + expect(payload.us_privacy).to.exist.and.to.equal(consentString); + }); + + it('supports sending hybrid mobile app parameters', function () { + let appRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + app: { + id: 'B1O2W3M4AN.com.prebid.webview', + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', // Apple advertising identifier + aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', // Android advertising identifier + md5udid: '5756ae9022b2ea1e47d84fead75220c8', // MD5 hash of the ANDROID_ID + sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', // SHA1 hash of the ANDROID_ID + windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' // Windows advertising identifier + } + } + } + } + ); + const request = spec.buildRequests([appRequest]); + const payload = JSON.parse(request.data); + expect(payload.app).to.exist; + expect(payload.app).to.deep.equal({ + appid: 'B1O2W3M4AN.com.prebid.webview' + }); + expect(payload.device.device_id).to.exist; + expect(payload.device.device_id).to.deep.equal({ + aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', + idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', + md5udid: '5756ae9022b2ea1e47d84fead75220c8', + sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', + windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' + }); + expect(payload.device.geo).to.exist; + expect(payload.device.geo).to.deep.equal({ + lat: 40.0964439, + lng: -75.3009142 + }); + }); + + it('should add referer info to payload', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'https://example.com/page.html', + 'https://example.com/iframe1.html', + 'https://example.com/iframe2.html' + ] + } + } + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.referrer_detection).to.exist; + expect(payload.referrer_detection).to.deep.equal({ + rd_ref: 'https%3A%2F%2Fexample.com%2Fpage.html', + rd_top: true, + rd_ifs: 2, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + }); + }); + + it('should populate schain if available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'blob.com', + 'sid': '001', + 'hp': 1 + } + ] + } + }); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'blob.com', + 'sid': '001', + 'hp': 1 + } + ] + }); + }); + + it('should populate coppa if set in config', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.user.coppa).to.equal(true); + + config.getConfig.restore(); + }); + + it('should set the X-Is-Test customHeader if test flag is enabled', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('apn_test') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + + config.getConfig.restore(); + }); + + it('should always set withCredentials: true on the request.options', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + const request = spec.buildRequests([bidRequest]); + expect(request.options.withCredentials).to.equal(true); + }); + + it('should set simple domain variant if purpose 1 consent is not given', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'mediafuse', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true, + apiVersion: 2, + vendorData: { + purpose: { + consents: { + 1: false + } + } + } + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal('https://ib.adnxs-simple.com/ut/v3/prebid'); + }); + + it('should populate eids when supported userIds are available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + userId: { + tdid: 'sample-userid', + uid2: { id: 'sample-uid2-value' }, + criteoId: 'sample-criteo-userid', + netId: 'sample-netId-userid', + idl_env: 'sample-idl-userid', + flocId: { + id: 'sample-flocid-value', + version: 'chrome.1.0' + } + } + }); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.eids).to.deep.include({ + source: 'adserver.org', + id: 'sample-userid', + rti_partner: 'TDID' + }); + + expect(payload.eids).to.deep.include({ + source: 'criteo.com', + id: 'sample-criteo-userid', + }); + + expect(payload.eids).to.deep.include({ + source: 'chrome.com', + id: 'sample-flocid-value' + }); + + expect(payload.eids).to.deep.include({ + source: 'netid.de', + id: 'sample-netId-userid', + }); + + expect(payload.eids).to.deep.include({ + source: 'liveramp.com', + id: 'sample-idl-userid' + }); + + expect(payload.eids).to.deep.include({ + source: 'uidapi.com', + id: 'sample-uid2-value', + rti_partner: 'UID2' + }); + }); + + it('should populate iab_support object at the root level if omid support is detected', function () { + // with bid.params.frameworks + let bidRequest_A = Object.assign({}, bidRequests[0], { + params: { + frameworks: [1, 2, 5, 6], + video: { + frameworks: [1, 2, 5, 6] + } + } + }); + let request = spec.buildRequests([bidRequest_A]); + let payload = JSON.parse(request.data); + expect(payload.iab_support).to.be.an('object'); + expect(payload.iab_support).to.deep.equal({ + omidpn: 'Mediafuse', + omidpv: '$prebid.version$' + }); + expect(payload.tags[0].banner_frameworks).to.be.an('array'); + expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video_frameworks).to.be.an('array'); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video.frameworks).to.not.exist; + + // without bid.params.frameworks + const bidRequest_B = Object.assign({}, bidRequests[0]); + request = spec.buildRequests([bidRequest_B]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + + // with video.frameworks but it is not an array + const bidRequest_C = Object.assign({}, bidRequests[0], { + params: { + video: { + frameworks: "'1', '2', '3', '6'" + } + } + }); + request = spec.buildRequests([bidRequest_C]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + }); + }) + + describe('interpretResponse', function () { + let bfStub; + let bidderSettingsStorage; + + before(function() { + bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + bidderSettingsStorage = $$PREBID_GLOBAL$$.bidderSettings; + }); + + after(function() { + bfStub.restore(); + $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsStorage; + }); + + let response = { + 'version': '3.0.0', + 'tags': [ + { + 'uuid': '3db3773286ee59', + 'tag_id': 10433394, + 'auction_id': '4534722592064951574', + 'nobid': false, + 'no_ad_url': 'https://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 10000, + 'ad_profile_id': 27079, + 'ads': [ + { + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 29681110, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.5, + 'cpm_publisher_currency': 0.5, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'viewability': { + 'config': '' + }, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'https://lax1-ib.adnxs.com/impression', + 'https://www.test.com/tracker' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'adUnitCode': 'code', + 'mediafuse': { + 'buyerMemberId': 958 + }, + 'meta': { + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [{ + 'bsid': '958' + }] + } + } + } + ]; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('should reject 0 cpm bids', function () { + let zeroCpmResponse = deepClone(response); + zeroCpmResponse.tags[0].ads[0].cpm = 0; + + let bidderRequest = { + bidderCode: 'mediafuse' + }; + + let result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); + expect(result.length).to.equal(0); + }); + + it('should allow 0 cpm bids if allowZeroCpmBids setConfig is true', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mediafuse: { + allowZeroCpmBids: true + } + }; + + let zeroCpmResponse = deepClone(response); + zeroCpmResponse.tags[0].ads[0].cpm = 0; + + let bidderRequest = { + bidderCode: 'mediafuse', + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + + let result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); + expect(result.length).to.equal(1); + expect(result[0].cpm).to.equal(0); + }); + + it('handles nobid responses', function () { + let response = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + + it('handles outstream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles instream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/vid' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles adpod responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'brand_category_id': 10, + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/adpod', + 'duration_ms': 30000, + } + }, + 'viewability': { + 'config': '' + } + }] + }] + }; + + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } + } + }] + }; + bfStub.returns('1'); + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0].video.context).to.equal('adpod'); + expect(result[0].video.durationSeconds).to.equal(30); + }); + + it('handles native responses', function () { + let response1 = deepClone(response); + response1.tags[0].ads[0].ad_type = 'native'; + response1.tags[0].ads[0].rtb.native = { + 'title': 'Native Creative', + 'desc': 'Cool description great stuff', + 'desc2': 'Additional body text', + 'ctatext': 'Do it', + 'sponsored': 'MediaFuse', + 'icon': { + 'width': 0, + 'height': 0, + 'url': 'https://cdn.adnxs.com/icon.png' + }, + 'main_img': { + 'width': 2352, + 'height': 1516, + 'url': 'https://cdn.adnxs.com/img.png' + }, + 'link': { + 'url': 'https://www.mediafuse.com', + 'fallback_url': '', + 'click_trackers': ['https://nym1-ib.adnxs.com/click'] + }, + 'impression_trackers': ['https://example.com'], + 'rating': '5', + 'displayurl': 'https://mediafuse.com/?url=display_url', + 'likes': '38908320', + 'downloads': '874983', + 'price': '9.99', + 'saleprice': 'FREE', + 'phone': '1234567890', + 'address': '28 W 23rd St, New York, NY 10010', + 'privacy_link': 'https://www.mediafuse.com/privacy-policy-agreement/', + 'javascriptTrackers': '' + }; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + + let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); + expect(result[0].native.title).to.equal('Native Creative'); + 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'); + }); + + it('supports configuring outstream renderers', function () { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + renderer: { + options: { + adText: 'configured' + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + }; + + const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); + + it('should add deal_priority and deal_code', function() { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].ad_type = 'video'; + responseWithDeal.tags[0].ads[0].deal_priority = 5; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + responseWithDeal.tags[0].ads[0].rtb.video = { + duration_ms: 1500, + player_width: 640, + player_height: 340, + }; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } + } + }] + } + let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); + expect(Object.keys(result[0].mediafuse)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + expect(result[0].video.dealTier).to.equal(5); + }); + + it('should add advertiser id', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); + }); + + it('should add brand id', function() { + let responseBrandId = deepClone(response); + responseBrandId.tags[0].ads[0].brand_id = 123; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseBrandId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['brandId']); + }); + + it('should add advertiserDomains', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + 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([]); + }); + }); +}); diff --git a/test/spec/modules/mediakeysBidAdapter_spec.js b/test/spec/modules/mediakeysBidAdapter_spec.js index 602524e6eb3..38f2a3f9de3 100644 --- a/test/spec/modules/mediakeysBidAdapter_spec.js +++ b/test/spec/modules/mediakeysBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; import { spec } from 'modules/mediakeysBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from 'src/utils.js'; @@ -575,12 +575,15 @@ describe('mediakeysBidAdapter', function () { }, user: { yob: 1985, - gender: 'm' - }, - device: { + gender: 'm', geo: { country: 'FR', city: 'Marseille' + }, + ext: { + data: { + registered: true + } } } } @@ -596,8 +599,9 @@ describe('mediakeysBidAdapter', function () { expect(data.site.ext.data.category).to.equal('sport'); expect(data.user.yob).to.equal(1985); expect(data.user.gender).to.equal('m'); - expect(data.device.geo.country).to.equal('FR'); - expect(data.device.geo.city).to.equal('Marseille'); + expect(data.user.geo.country).to.equal('FR'); + expect(data.user.geo.city).to.equal('Marseille'); + expect(data.user.ext.data.registered).to.be.true; }); }); diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index a0a62710a56..0ce26ab4863 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; import medianetAnalytics from 'modules/medianetAnalyticsAdapter.js'; import * as utils from 'src/utils.js'; import CONSTANTS from 'src/constants.json'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; const { EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, NO_BID, BID_TIMEOUT, AUCTION_END, SET_TARGETING, BID_WON } @@ -23,7 +23,9 @@ const MOCK = { 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'}]}, 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}] + 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'}]}] } function performAuctionWithFloorConfig() { @@ -76,6 +78,15 @@ function performStandardAuctionWithTimeout() { events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); } +function performStandardAuctionMultiBidWithSameRequestId(bidRespArray) { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + bidRespArray.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON); +} + function getQueryData(url, decode = false) { const queryArgs = url.split('?')[1].split('&'); return queryArgs.reduce((data, arg) => { @@ -265,5 +276,29 @@ describe('Media.net Analytics Adapter', function() { expect(timeoutLog.mpvid).to.have.ordered.members(['', '']); expect(timeoutLog.crid).to.have.ordered.members(['', '451466393']); }); + + 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'); + 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'); + }); + + 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'); + 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'); + }); }); }); diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index f3f09a8ddf8..40d0c44e4f9 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -61,7 +61,25 @@ describe('MediaSquare bid adapter tests', function () { code: 'publishername_atf_desktop_rg_pave' }, }]; - + var FLOORS_PARAMS = [{ + adUnitCode: 'banner-div', + bidId: 'aaaa1234', + auctionId: 'bbbb1234', + transactionId: 'cccc1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bidder: 'mediasquare', + params: { + owner: 'test', + code: 'publishername_atf_desktop_rg_pave' + }, + getFloor: function (a) { return { currency: 'EUR', floor: 1.0 }; }, + }]; var BID_RESPONSE = {'body': { 'responses': [{ 'transaction_id': 'cccc1234', @@ -117,6 +135,12 @@ describe('MediaSquare bid adapter tests', function () { expect(requestContent.codes[0]).to.have.property('auctionId').and.to.equal('bbbb1234'); expect(requestContent.codes[0]).to.have.property('transactionId').and.to.equal('cccc1234'); expect(requestContent.codes[0]).to.have.property('mediatypes').exist; + expect(requestContent.codes[0]).to.have.property('floor').exist; + expect(requestContent.codes[0].floor).to.deep.equal({}); + 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); }); it('Verify parse response', function () { @@ -148,6 +172,14 @@ describe('MediaSquare bid adapter tests', function () { expect(bid.mediasquare.match).to.exist; expect(bid.mediasquare.match).to.equal(true); }); + it('Verifies hasConsent', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + BID_RESPONSE.body.responses[0].hasConsent = true; + const response = spec.interpretResponse(BID_RESPONSE, request); + const bid = response[0]; + expect(bid.mediasquare.hasConsent).to.exist; + expect(bid.mediasquare.hasConsent).to.equal(true); + }); it('Verifies bidder code', function () { expect(spec.code).to.equal('mediasquare'); }); @@ -161,13 +193,15 @@ describe('MediaSquare bid adapter tests', function () { }); it('Verifies bid won', function () { const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + BID_RESPONSE.body.responses[0].match = true + BID_RESPONSE.body.responses[0].hasConsent = true; const response = spec.interpretResponse(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({}, [BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); }); it('Verifies user sync with cookies in bid response', function () { BID_RESPONSE.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; @@ -178,13 +212,13 @@ describe('MediaSquare bid adapter tests', function () { }); it('Verifies user sync with no bid response', function() { var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + 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.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); }); it('Verifies native in bid response', function () { const request = spec.buildRequests(NATIVE_PARAMS, DEFAULT_OPTIONS); diff --git a/test/spec/modules/merkleIdSystem_spec.js b/test/spec/modules/merkleIdSystem_spec.js index 64dfc0d463c..63a2791ba3c 100644 --- a/test/spec/modules/merkleIdSystem_spec.js +++ b/test/spec/modules/merkleIdSystem_spec.js @@ -192,6 +192,24 @@ describe('Merkle System', function () { expect(id.id).to.exist.and.to.equal(storedId); }); + it('extendId() should warn on missing endpoint', function () { + let config = { + params: { + ...CONFIG_PARAMS, + endpoint: undefined + }, + storage: STORAGE_PARAMS + }; + + let yesterday = new Date(Date.now() - 86400000).toUTCString(); + let storedId = {value: 'Merkle_Stored_ID', date: yesterday}; + + let submoduleCallback = merkleIdSubmodule.extendId(config, undefined, + storedId).callback; + submoduleCallback(callbackSpy); + expect(callbackSpy.calledOnce).to.be.true; + expect(utils.logWarn.args[0][0]).to.exist.and.to.equal('User ID - merkleId submodule endpoint string is not defined'); + }); it('extendId() callback on configured storageParam.refreshInSeconds', function () { let config = { diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js new file mode 100644 index 00000000000..b1ad7f96bc4 --- /dev/null +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -0,0 +1,405 @@ +import { expect } from 'chai'; +import { spec } from 'modules/minutemediaBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { VIDEO } from '../../../src/mediaTypes.js'; +import { deepClone } from 'src/utils.js'; + +const ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm'; +const TEST_ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm-test'; +const TTL = 360; + +describe('minutemediaAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'org': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001', + 'testMode': true + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const bidderRequest = { + bidderCode: 'minutemedia', + } + const placementId = '12345678'; + + it('sends the placementId as a query param', function () { + bidRequests[0].params.placementId = placementId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.placement_id).to.equal(placementId); + } + }); + + it('sends bid request to ENDPOINT via GET', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('sends bid request to test ENDPOINT via GET', function () { + const requests = spec.buildRequests(testModeBidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('should send the correct bid Id', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.bid_id).to.equal('299ffc8cca0b87'); + } + }); + + it('should send the correct width and height', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('width', 640); + expect(request.data).to.have.property('height', 480); + } + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.setConfig({ + userSync: { + syncEnabled: true + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'pixel'); + } + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('us_privacy', '1YNN'); + } + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('us_privacy'); + } + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('gdpr'); + expect(request.data).to.not.have.property('gdpr_consent'); + } + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('gdpr', true); + expect(request.data).to.have.property('gdpr_consent', 'test-consent-string'); + } + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); + } + }); + + it('should set floor_price to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 3.32 + } + } + bid.params.floorPrice = 0.64; + const request = spec.buildRequests([bid], bidderRequest)[0]; + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('floor_price', 3.32); + }); + + it('should set floor_price to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 0.8 + } + } + bid.params.floorPrice = 1.5; + const request = spec.buildRequests([bid], bidderRequest)[0]; + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('floor_price', 1.5); + }); + }); + + describe('interpretResponse', function () { + const response = { + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + netRevenue: true, + currency: 'USD', + adomain: ['abc.com'] + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '21e12606d47ba7', + cpm: 12.5, + width: 640, + height: 480, + creativeId: '21e12606d47ba7', + currency: 'USD', + netRevenue: true, + ttl: TTL, + vastXml: '', + mediaType: VIDEO, + meta: { + advertiserDomains: ['abc.com'] + } + } + ]; + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + }; + + const iframeSyncResponse = { + body: { + userSyncURL: 'https://iframe-sync-url.test' + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) +}); diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index 026e79c6d5a..86b967cca5b 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -87,6 +87,9 @@ describe('Missena Adapter', function () { cpm: 0.5, currency: 'USD', ad: '', + meta: { + advertiserDomains: ['missena.com'] + }, }; const serverTimeoutResponse = { diff --git a/test/spec/modules/multibid_spec.js b/test/spec/modules/multibid_spec.js index 86365eb520f..eaf8fa33a66 100644 --- a/test/spec/modules/multibid_spec.js +++ b/test/spec/modules/multibid_spec.js @@ -11,7 +11,6 @@ import { import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; -import find from 'core-js-pure/features/array/find.js'; describe('multibid adapter', function () { let bidArray = [{ diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index dfc9f5b99b3..c552090cf6e 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -1,36 +1,49 @@ import { expect } from 'chai' import { spec } from 'modules/nativoBidAdapter.js' -// import { newBidder } from 'src/adapters/bidderFactory.js' -// import * as bidderFactory from 'src/adapters/bidderFactory.js' -// import { deepClone } from 'src/utils.js' -// import { config } from 'src/config.js' describe('nativoBidAdapterTests', function () { describe('isBidRequestValid', function () { let bid = { bidder: 'nativo', - params: { - placementId: '10433394', - }, - adUnitCode: 'adunit-code', - sizes: [ - [300, 250], - [300, 600], - ], - bidId: '27b02036ccfa6e', - bidderRequestId: '1372cd8bd8d6a8', - auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', } - it('should return true when required params found', function () { + it('should return true if no params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true) }) - it('should return true when params are not passed', function () { - let bid2 = Object.assign({}, bid) - delete bid2.params - bid2.params = {} - expect(spec.isBidRequestValid(bid2)).to.equal(true) + it('should return true for valid placementId value', function () { + bid.params = { + placementId: '10433394', + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true for valid placementId value', function () { + bid.params = { + placementId: 10433394, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false for invalid placementId value', function () { + bid.params = { + placementId: true, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should return true for valid placementId value', function () { + bid.params = { + url: 'www.test.com', + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false for invalid placementId value', function () { + bid.params = { + url: 4567890, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) }) }) @@ -256,9 +269,7 @@ describe('getAdUnitData', () => { } const data = spec.getAdUnitData(9876543, { impid: 12345 }) - expect(Object.keys(data)).to.have.deep.members( - Object.keys(adUnitData) - ) + expect(Object.keys(data)).to.have.deep.members(Object.keys(adUnitData)) }) it('Falls back to ad unit code value', () => { @@ -273,9 +284,158 @@ describe('getAdUnitData', () => { }, } - const data = spec.getAdUnitData(9876543, { impid: 12345, ext: { ad_unit_code: '#test-code' } }) - expect(Object.keys(data)).to.have.deep.members( - Object.keys(adUnitData) - ) + const data = spec.getAdUnitData(9876543, { + impid: 12345, + ext: { ad_unit_code: '#test-code' }, + }) + expect(Object.keys(data)).to.have.deep.members(Object.keys(adUnitData)) + }) +}) + +describe('Response to Request Filter Flow', () => { + let bidRequests = [ + { + bidder: 'nativo', + params: { + placementId: '10433394', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '27b02036ccfa6e', + bidderRequestId: '1372cd8bd8d6a8', + auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + transactionId: '3b36e7e0-0c3e-4006-a279-a741239154ff', + }, + ] + + let response + + beforeEach(() => { + response = { + id: '126456', + seatbid: [ + { + seat: 'seat_0', + bid: [ + { + id: 'f70362ac-f3cf-4225-82a5-948b690927a6', + impid: '1', + price: 3.569, + adm: '', + h: 300, + w: 250, + cat: [], + adomain: ['test.com'], + crid: '1060_72_6760217', + }, + ], + }, + ], + cur: 'USD', + } + }) + + let bidderRequest = { + id: 123456, + bids: [ + { + params: { + placementId: 1, + }, + }, + ], + } + + // mock + spec.getAdUnitData = () => { + return { + bidId: 123456, + size: [300, 250], + } + } + + it('Appends NO filter based on previous response', () => { + // Getting the mock response + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + + // Winning the bid + spec.onBidWon(result[0]) + + // Making another request + const request = spec.buildRequests(bidRequests, { + bidderRequestId: 123456, + refererInfo: { + referer: 'https://www.test.com', + }, + }) + expect(request.url).to.not.include('ntv_aft') + expect(request.url).to.not.include('ntv_avtf') + expect(request.url).to.not.include('ntv_ctf') + }) + + it('Appends Ads filter based on previous response', () => { + response.seatbid[0].bid[0].ext = { adsToFilter: ['12345'] } + + // Getting the mock response + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + + // Winning the bid + spec.onBidWon(result[0]) + + // Making another request + const request = spec.buildRequests(bidRequests, { + bidderRequestId: 123456, + refererInfo: { + referer: 'https://www.test.com', + }, + }) + expect(request.url).to.include(`ntv_atf=12345`) + expect(request.url).to.not.include('ntv_avtf') + expect(request.url).to.not.include('ntv_ctf') + }) + + it('Appends Advertiser filter based on previous response', () => { + response.seatbid[0].bid[0].ext = { advertisersToFilter: ['1'] } + + // Getting the mock response + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + + // Winning the bid + spec.onBidWon(result[0]) + + // Making another request + const request = spec.buildRequests(bidRequests, { + bidderRequestId: 123456, + refererInfo: { + referer: 'https://www.test.com', + }, + }) + expect(request.url).to.include(`ntv_atf=12345`) + expect(request.url).to.include('ntv_avtf=1') + expect(request.url).to.not.include('ntv_ctf') + }) + + it('Appends Campaign filter based on previous response', () => { + response.seatbid[0].bid[0].ext = { campaignsToFilter: ['234'] } + + // Getting the mock response + let result = spec.interpretResponse({ body: response }, { bidderRequest }) + + // Winning the bid + spec.onBidWon(result[0]) + + // Making another request + const request = spec.buildRequests(bidRequests, { + bidderRequestId: 123456, + refererInfo: { + referer: 'https://www.test.com', + }, + }) + expect(request.url).to.include(`ntv_atf=12345`) + expect(request.url).to.include('ntv_avtf=1') + expect(request.url).to.include('ntv_ctf=234') }) }) diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 1a24c6d0575..a8aa62f24d1 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -4,6 +4,7 @@ import { spec } from 'modules/nextMillenniumBidAdapter.js'; describe('nextMillenniumBidAdapterTests', function() { const bidRequestData = [ { + adUnitCode: 'test-div', bidId: 'bid1234', auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidder: 'nextMillennium', @@ -17,19 +18,94 @@ describe('nextMillenniumBidAdapterTests', function() { } ]; - it('Request params check with GDPR Consent', function () { + const bidRequestDataGI = [ + { + adUnitCode: 'test-banner-gi', + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + + sizes: [[300, 250]], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + } + }, + + { + adUnitCode: 'test-video-gi', + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + video: { + playerSize: [640, 480], + } + }, + + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + } + }, + ]; + + it('Request params check with GDPR and USP Consent', function () { const request = spec.buildRequests(bidRequestData, bidRequestData[0]); expect(JSON.parse(request[0].data).user.ext.consent).to.equal('kjfdniwjnifwenrif3'); expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('1---'); expect(JSON.parse(request[0].data).regs.ext.gdpr).to.equal(1); }); + it('Request params check without GDPR Consent', function () { + delete bidRequestData[0].gdprConsent + const request = spec.buildRequests(bidRequestData, bidRequestData[0]); + expect(JSON.parse(request[0].data).regs.ext.gdpr).to.be.undefined; + expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('1---'); + }); + it('validate_generated_params', function() { const request = spec.buildRequests(bidRequestData); expect(request[0].bidId).to.equal('bid1234'); expect(JSON.parse(request[0].data).id).to.equal('b06c5141-fe8f-4cdf-9d7d-54415490a917'); }); + it('use parameters group_id', function() { + for (let test of bidRequestDataGI) { + const request = spec.buildRequests([test]); + const requestData = JSON.parse(request[0].data); + const storeRequestId = requestData.ext.prebid.storedrequest.id; + const templateRE = /^g\d+;\d+x\d+;/; + expect(templateRE.test(storeRequestId)).to.be.true; + }; + }); + + it('Check if refresh_count param is incremented', function() { + const request = spec.buildRequests(bidRequestData); + expect(JSON.parse(request[0].data).ext.nextMillennium.refresh_count).to.equal(3); + }); + + 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: { diff --git a/test/spec/modules/nextrollBidAdapter_spec.js b/test/spec/modules/nextrollBidAdapter_spec.js index 4699fbc6e08..d4779120248 100644 --- a/test/spec/modules/nextrollBidAdapter_spec.js +++ b/test/spec/modules/nextrollBidAdapter_spec.js @@ -244,8 +244,8 @@ describe('nextrollBidAdapter', function() { let expectedResponse = { clickUrl: clickUrl, impressionTrackers: [impUrl], - privacyLink: 'https://info.evidon.com/pub_info/573', - privacyIcon: 'https://c.betrad.com/pub/icon1.png', + privacyLink: 'https://app.adroll.com/optout/personalized', + privacyIcon: 'https://s.adroll.com/j/ad-choices-small.png', title: titleText, image: {url: imgUrl, width: imgW, height: imgH}, sponsoredBy: brandText, @@ -274,8 +274,8 @@ describe('nextrollBidAdapter', function() { impressionTrackers: [impUrl], jstracker: [], clickTrackers: [], - privacyLink: 'https://info.evidon.com/pub_info/573', - privacyIcon: 'https://c.betrad.com/pub/icon1.png', + privacyLink: 'https://app.adroll.com/optout/personalized', + privacyIcon: 'https://s.adroll.com/j/ad-choices-small.png', title: titleText, image: {url: imgUrl, width: imgW, height: imgH}, icon: {url: iconUrl, width: iconW, height: iconH}, diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js new file mode 100644 index 00000000000..7501391cbfc --- /dev/null +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -0,0 +1,140 @@ +import {expect} from 'chai'; +import {spec} from 'modules/nexx360BidAdapter.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'; + +describe('Nexx360 bid adapter tests', function () { + var DEFAULT_PARAMS = [{ + adUnitCode: 'banner-div', + bidId: 'aaaa1234', + auctionId: 'bbbb1234', + transactionId: 'cccc1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bidder: 'nexx360', + params: { + account: '1067', + tagId: 'luvxjvgn' + }, + }]; + + var BID_RESPONSE = {'body': { + 'responses': [ + { + 'bidId': '49a705a42610a', + 'cpm': 0.437245, + 'width': 300, + 'height': 250, + 'creativeId': '98493581', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + 'uuid': 'ce6d1ee3-2a05-4d7c-b97a-9e62097798ec', + 'bidder': 'appnexus', + 'consent': 1, + 'tagId': 'luvxjvgn' + } + ], + }}; + + const DEFAULT_OPTIONS = { + gdprConsent: { + gdprApplies: true, + consentString: 'BOzZdA0OzZdA0AGABBENDJ-AAAAvh7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Mw_v-_v-b7JCPN_Y3v-8Kg', + vendorData: {} + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + }, + uspConsent: '111222333', + userId: { 'id5id': { uid: '1111' } }, + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + }] + }, + }; + it('Verify build request', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, 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('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('aaaa1234'); + expect(requestContent.adUnits[0]).to.have.property('auctionId').and.to.equal('bbbb1234'); + expect(requestContent.adUnits[0]).to.have.property('mediatypes').exist; + }); + + it('Verify parse response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + const response = spec.interpretResponse(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('49a705a42610a'); + expect(bid.nexx360).to.exist; + expect(bid.nexx360.ssp).to.equal('appnexus'); + }); + it('Verifies bidder code', function () { + expect(spec.code).to.equal('nexx360'); + }); + + 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(DEFAULT_PARAMS[0])).to.equal(true); + }); + it('Verifies bid won', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); + const response = spec.interpretResponse(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({}, [BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); + it('Verifies user sync with cookies in bid response', function () { + BID_RESPONSE.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; + var syncs = spec.getUserSyncs({}, [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); + }); +}); diff --git a/test/spec/modules/novatiqIdSystem_spec.js b/test/spec/modules/novatiqIdSystem_spec.js index 60c82626450..b92fb0d219a 100644 --- a/test/spec/modules/novatiqIdSystem_spec.js +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -3,41 +3,41 @@ import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; describe('novatiqIdSystem', function () { + let urlParams = { + novatiqId: 'snowflake', + useStandardUuid: false, + useSspId: true, + useSspHost: true + } + describe('getSrcId', function() { it('getSrcId should set srcId value to 000 due to undefined parameter in config section', function() { const config = { params: { } }; const configParams = config.params || {}; - const response = novatiqIdSubmodule.getSrcId(configParams); + const response = novatiqIdSubmodule.getSrcId(configParams, urlParams); expect(response).to.eq('000'); }); it('getSrcId should set srcId value to 000 due to missing value in config section', function() { const config = { params: { sourceid: '' } }; const configParams = config.params || {}; - const response = novatiqIdSubmodule.getSrcId(configParams); + const response = novatiqIdSubmodule.getSrcId(configParams, urlParams); expect(response).to.eq('000'); }); it('getSrcId should set value to 000 due to null value in config section', function() { const config = { params: { sourceid: null } }; const configParams = config.params || {}; - const response = novatiqIdSubmodule.getSrcId(configParams); + const response = novatiqIdSubmodule.getSrcId(configParams, urlParams); expect(response).to.eq('000'); }); it('getSrcId should set value to 001 due to wrong length in config section max 3 chars', function() { const config = { params: { sourceid: '1234' } }; const configParams = config.params || {}; - const response = novatiqIdSubmodule.getSrcId(configParams); + const response = novatiqIdSubmodule.getSrcId(configParams, urlParams); expect(response).to.eq('001'); }); - - it('getSrcId should set value to 002 due to wrong format in config section', function() { - const config = { params: { sourceid: '1xc' } }; - const configParams = config.params || {}; - const response = novatiqIdSubmodule.getSrcId(configParams); - expect(response).to.eq('002'); - }); }); describe('getId', function() { @@ -52,6 +52,89 @@ describe('novatiqIdSystem', function () { const response = novatiqIdSubmodule.getId(config); expect(response.id).should.be.not.empty; }); + + it('should set sharedStatus if sharedID is configured but returned null', function() { + const config = { params: { sourceid: '123', useSharedId: true } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.sharedStatus).to.equal('Not Found'); + }); + + it('should set sharedStatus if sharedID is configured and is valid', function() { + const config = { params: { sourceid: '123', useSharedId: true } }; + + let stub = sinon.stub(novatiqIdSubmodule, 'getSharedId').returns('fakeId'); + + const response = novatiqIdSubmodule.getId(config); + + stub.restore(); + + expect(response.sharedStatus).to.equal('Found'); + }); + + it('should set sharedStatus if sharedID is configured and is valid when making an async call', function() { + const config = { params: { sourceid: '123', useSharedId: true, useCallbacks: true } }; + + let stub = sinon.stub(novatiqIdSubmodule, 'getSharedId').returns('fakeId'); + + const response = novatiqIdSubmodule.getId(config); + + stub.restore(); + + expect(response.sharedStatus).to.equal('Found'); + }); + }); + + describe('getUrlParams', function() { + it('should return default url parameters when none set', function() { + const defaultUrlParams = { + novatiqId: 'snowflake', + useStandardUuid: false, + useSspId: true, + useSspHost: true + } + + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getUrlParams(config); + + expect(response).to.deep.equal(defaultUrlParams); + }); + + it('should return custom url parameters when set', function() { + let customUrlParams = { + novatiqId: 'hyperid', + useStandardUuid: true, + useSspId: false, + useSspHost: false + } + + const config = { + sourceid: '123', + urlParams: { + novatiqId: 'hyperid', + useStandardUuid: true, + useSspId: false, + useSspHost: false + } + }; + const response = novatiqIdSubmodule.getUrlParams(config); + + expect(response).to.deep.equal(customUrlParams); + }); + }); + + describe('sendAsyncSyncRequest', function() { + it('should return an async function when called asynchronously', function() { + const defaultUrlParams = { + novatiqId: 'snowflake', + useStandardUuid: false, + useSspId: true, + useSspHost: true + } + + const url = novatiqIdSubmodule.getSyncUrl(false, '', defaultUrlParams); + const response = novatiqIdSubmodule.sendAsyncSyncRequest('testuuid', url); + expect(response.callback).should.not.be.empty; + }); }); describe('decode', function() { diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index 883119d2707..acf62bf5a7b 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -119,7 +119,7 @@ describe('OguryBidAdapter', function () { }; }); - it('should return sync array with two elements of type image', () => { + it('should return syncs array with two elements of type image', () => { const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); expect(userSyncs).to.have.lengthOf(2); @@ -129,7 +129,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.contain('https://ms-cookie-sync.presage.io/ttd/init-sync'); }); - it('should set the same source as query param', () => { + 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'); @@ -146,7 +146,7 @@ describe('OguryBidAdapter', function () { expect(spec.getUserSyncs(syncOptions, [], gdprConsent)).to.have.lengthOf(0); }); - it('should return sync array with two elements of type image when consentString is undefined', () => { + it('should return syncs array with two elements of type image when consentString is undefined', () => { gdprConsent = { gdprApplies: true, consentString: undefined @@ -160,7 +160,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when consentString is null', () => { + it('should return syncs array with two elements of type image when consentString is null', () => { gdprConsent = { gdprApplies: true, consentString: null @@ -174,7 +174,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when gdprConsent is undefined', () => { + it('should return syncs array with two elements of type image when gdprConsent is undefined', () => { gdprConsent = undefined; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); @@ -185,7 +185,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when gdprConsent is null', () => { + it('should return syncs array with two elements of type image when gdprConsent is null', () => { gdprConsent = null; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); @@ -196,7 +196,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when gdprConsent is null and gdprApplies is false', () => { + it('should return syncs array with two elements of type image when gdprConsent is null and gdprApplies is false', () => { gdprConsent = { gdprApplies: false, consentString: null @@ -210,7 +210,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when gdprConsent is empty string and gdprApplies is false', () => { + it('should return syncs array with two elements of type image when gdprConsent is empty string and gdprApplies is false', () => { gdprConsent = { gdprApplies: false, consentString: '' @@ -229,7 +229,7 @@ describe('OguryBidAdapter', function () { const defaultTimeout = 1000; const expectedRequestObject = { id: bidRequests[0].auctionId, - at: 2, + at: 1, tmax: defaultTimeout, imp: [{ id: bidRequests[0].bidId, @@ -240,7 +240,8 @@ describe('OguryBidAdapter', function () { w: 300, h: 250 }] - } + }, + ext: bidRequests[0].params }, { id: bidRequests[1].bidId, tagid: bidRequests[1].params.adUnitId, @@ -250,7 +251,8 @@ describe('OguryBidAdapter', function () { w: 600, h: 500 }] - } + }, + ext: bidRequests[1].params }], regs: { ext: { @@ -266,6 +268,10 @@ describe('OguryBidAdapter', function () { ext: { consent: bidderRequest.gdprConsent.consentString }, + }, + ext: { + prebidversion: '$prebid.version$', + adapterversion: '1.2.10' } }; @@ -294,7 +300,59 @@ describe('OguryBidAdapter', function () { ...expectedRequestObject, regs: { ext: { - gdpr: 1 + gdpr: 0 + }, + }, + user: { + ext: { + consent: '' + }, + } + }; + + const validBidRequests = bidRequests + + const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); + expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); + expect(request.data.regs.ext.gdpr).to.be.a('number'); + }); + + it('should not add gdpr infos if gdprConsent is undefined', () => { + const bidderRequestWithoutGdpr = { + ...bidderRequest, + gdprConsent: undefined, + } + const expectedRequestObjectWithoutGdpr = { + ...expectedRequestObject, + regs: { + ext: { + gdpr: 0 + }, + }, + user: { + ext: { + consent: '' + }, + } + }; + + const validBidRequests = bidRequests + + const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); + expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); + expect(request.data.regs.ext.gdpr).to.be.a('number'); + }); + + it('should not add tcString and turn off gdpr-applies if consentString and gdprApplies are undefined', () => { + const bidderRequestWithoutGdpr = { + ...bidderRequest, + gdprConsent: { consentString: undefined, gdprApplies: undefined }, + } + const expectedRequestObjectWithoutGdpr = { + ...expectedRequestObject, + regs: { + ext: { + gdpr: 0 }, }, user: { @@ -423,7 +481,9 @@ describe('OguryBidAdapter', function () { meta: { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, - nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl + nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, + adapterVersion: '1.2.10', + prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, cpm: openRtbBidResponse.body.seatbid[0].bid[1].price, @@ -438,7 +498,9 @@ describe('OguryBidAdapter', function () { meta: { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, - nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl + nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, + adapterVersion: '1.2.10', + prebidVersion: '$prebid.version$' }] let request = spec.buildRequests(bidRequests, bidderRequest); @@ -484,6 +546,11 @@ describe('OguryBidAdapter', function () { expect(requests.length).to.equal(0); }) + it('Should not create nurl request if bid contains undefined nurl', function() { + spec.onBidWon({ nurl: undefined }) + expect(requests.length).to.equal(0); + }) + it('Should create nurl request if bid nurl', function() { spec.onBidWon({ nurl }) expect(requests.length).to.equal(1); @@ -549,7 +616,7 @@ describe('OguryBidAdapter', function () { xhr.restore() }) - it('should send notification on bid timeout', function() { + it('should send on bid timeout notification', function() { const bid = { ad: 'cookies', cpm: 3 @@ -559,6 +626,7 @@ describe('OguryBidAdapter', function () { expect(requests.length).to.equal(1); expect(requests[0].url).to.equal(TIMEOUT_URL); expect(requests[0].method).to.equal('POST'); + expect(JSON.parse(requests[0].requestBody).location).to.equal(window.location.href); }) }); }); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index e873597ca15..f335f2ec62a 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -1,6 +1,6 @@ import { spec, isValid, hasTypeVideo } from 'modules/onetagBidAdapter.js'; import { expect } from 'chai'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; import {INSTREAM, OUTSTREAM} from 'src/video.js'; diff --git a/test/spec/modules/ooloAnalyticsAdapter_spec.js b/test/spec/modules/ooloAnalyticsAdapter_spec.js index f82f7856fb2..a6030e972ce 100644 --- a/test/spec/modules/ooloAnalyticsAdapter_spec.js +++ b/test/spec/modules/ooloAnalyticsAdapter_spec.js @@ -2,7 +2,7 @@ import ooloAnalytics, { PAGEVIEW_ID } from 'modules/ooloAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; import constants from 'src/constants.json' -import events from 'src/events' +import * as events from 'src/events' import { config } from 'src/config'; import { buildAuctionData, generatePageViewId } from 'modules/ooloAnalyticsAdapter'; diff --git a/test/spec/modules/open8BidAdapter_spec.js b/test/spec/modules/open8BidAdapter_spec.js new file mode 100644 index 00000000000..27e460bad9d --- /dev/null +++ b/test/spec/modules/open8BidAdapter_spec.js @@ -0,0 +1,258 @@ +import { spec } from 'modules/open8BidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = 'https://as.vt.open8.com/v1/control/prebid'; + +describe('Open8Adapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'open8', + 'params': { + 'slotKey': 'slotkey1234' + }, + 'adUnitCode': 'adunit', + 'sizes': [[300, 250]], + 'bidId': 'bidid1234', + 'bidderRequestId': 'requestid1234', + 'auctionId': 'auctionid1234', + }; + + 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() { + bid.params = { + ' slotKey': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + { + 'bidder': 'open8', + 'params': { + 'slotKey': 'slotkey1234' + }, + 'adUnitCode': 'adunit', + 'sizes': [[300, 250]], + 'bidId': 'bidid1234', + 'bidderRequestId': 'requestid1234', + 'auctionId': 'auctionid1234', + } + ]; + + it('sends bid request to ENDPOINT via GET', function() { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].method).to.equal('GET'); + }); + }); + describe('interpretResponse', function() { + const adomin = ['example.com'] + const bannerResponse = { + slotKey: 'slotkey1234', + userId: 'userid1234', + impId: 'impid1234', + media: 'TEST_MEDIA', + nurl: '//example/win', + isAdReturn: true, + syncPixels: ['//example/sync/pixel.gif'], + syncIFs: [], + ad: { + bidId: 'TEST_BID_ID', + price: 1234.56, + creativeId: 'creativeid1234', + dealId: 'TEST_DEAL_ID', + currency: 'JPY', + ds: 876, + spd: 1234, + fa: 5678, + pr: 'pr1234', + mr: 'mr1234', + nurl: '//example/win', + adType: 2, + banner: { + w: 300, + h: 250, + adm: '
', + imps: ['//example.com/imp'] + }, + adomain: adomin + } + }; + const videoResponse = { + slotKey: 'slotkey1234', + userId: 'userid1234', + impId: 'impid1234', + media: 'TEST_MEDIA', + isAdReturn: true, + syncPixels: ['//example/sync/pixel.gif'], + syncIFs: [], + ad: { + bidId: 'TEST_BID_ID', + price: 1234.56, + creativeId: 'creativeid1234', + dealId: 'TEST_DEAL_ID', + currency: 'JPY', + ds: 876, + spd: 1234, + fa: 5678, + pr: 'pr1234', + mr: 'mr1234', + nurl: '//example/win', + adType: 1, + video: { + purl: '//playerexample.js', + vastXml: '', + w: 320, + h: 180 + }, + adomain: adomin + } + }; + + it('should get correct banner bid response', function() { + let expectedResponse = [{ + 'slotKey': 'slotkey1234', + 'userId': 'userid1234', + 'impId': 'impid1234', + 'media': 'TEST_MEDIA', + 'ds': 876, + 'spd': 1234, + 'fa': 5678, + 'pr': 'pr1234', + 'mr': 'mr1234', + 'nurl': '//example/win', + 'requestId': 'requestid1234', + 'cpm': 1234.56, + 'creativeId': 'creativeid1234', + 'dealId': 'TEST_DEAL_ID', + 'width': 300, + 'height': 250, + 'ad': "
", + 'mediaType': 'banner', + 'currency': 'JPY', + 'ttl': 360, + 'netRevenue': true, + 'meta': { } + }]; + + let bidderRequest; + let result = spec.interpretResponse({ body: bannerResponse }, { bidderRequest }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0]).to.nested.contain.property('meta.advertiserDomains', adomin); + }); + + it('handles video responses', function() { + let expectedResponse = [{ + 'slotKey': 'slotkey1234', + 'userId': 'userid1234', + 'impId': 'impid1234', + 'media': 'TEST_MEDIA', + 'ds': 876, + 'spd': 1234, + 'fa': 5678, + 'pr': 'pr1234', + 'mr': 'mr1234', + 'nurl': '//example/win', + 'requestId': 'requestid1234', + 'cpm': 1234.56, + 'creativeId': 'creativeid1234', + 'dealId': 'TEST_DEAL_ID', + 'width': 320, + 'height': 180, + 'vastXml': '', + 'mediaType': 'video', + 'renderer': {}, + 'adResponse': {}, + 'currency': 'JPY', + 'ttl': 360, + 'netRevenue': true, + 'meta': { } + }]; + + let bidderRequest; + let result = spec.interpretResponse({ body: videoResponse }, { bidderRequest }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0]).to.nested.contain.property('meta.advertiserDomains', adomin); + }); + + it('handles nobid responses', function() { + let response = { + isAdReturn: false, + 'ad': {} + }; + + let bidderRequest; + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function() { + const imgResponse1 = { + body: { + 'isAdReturn': true, + 'ad': { /* ad body */ }, + 'syncPixels': [ + 'https://example.test/1' + ] + } + }; + + const imgResponse2 = { + body: { + 'isAdReturn': true, + 'ad': { /* ad body */ }, + 'syncPixels': [ + 'https://example.test/2' + ] + } + }; + + const ifResponse = { + body: { + 'isAdReturn': true, + 'ad': { /* ad body */ }, + 'syncIFs': [ + 'https://example.test/3' + ] + } + }; + + it('should use a sync img url from first response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imgResponse1, imgResponse2, ifResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://example.test/1' + } + ]); + }); + + it('handle ifs response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [ifResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://example.test/3' + } + ]); + }); + + it('handle empty response (e.g. timeout)', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('returns empty syncs when not enabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imgResponse1]); + expect(syncs).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js index d7d2d31669c..47663a41f47 100644 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -1,10 +1,10 @@ import { expect } from 'chai'; import openxAdapter, {AUCTION_STATES} from 'modules/openxAnalyticsAdapter.js'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; const { EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON, AUCTION_END } diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 783449723ae..3bc53e30eb8 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1102,6 +1102,11 @@ describe('OpenxAdapter', function () { mwOpenLinkId: '1111-mwopenlinkid', dapId: '1111-dapId', amxId: '1111-amxid', + kpuid: '1111-kpuid', + publinkId: '1111-publinkid', + naveggId: '1111-naveggid', + imuid: '1111-imuid', + adtelligentId: '1111-adtelligentid' }; // generates the same set of tests for each id provider diff --git a/test/spec/modules/optimeraRtdProvider_spec.js b/test/spec/modules/optimeraRtdProvider_spec.js index 8b1866d044a..aec8b79045e 100644 --- a/test/spec/modules/optimeraRtdProvider_spec.js +++ b/test/spec/modules/optimeraRtdProvider_spec.js @@ -1,5 +1,6 @@ import * as optimeraRTD from '../../../modules/optimeraRtdProvider.js'; -let utils = require('src/utils.js'); + +const utils = require('src/utils.js'); describe('Optimera RTD sub module', () => { it('should init, return true, and set the params', () => { @@ -31,40 +32,86 @@ describe('Optimera RTD score file properly sets targeting values', () => { const scores = { 'div-0': ['A1', 'A2'], 'div-1': ['A3', 'A4'], - 'device': { - 'de': { + device: { + de: { 'div-0': ['A5', 'A6'], 'div-1': ['A7', 'A8'], + insights: { + ilv: ['div-0'], + miv: ['div-4'], + } }, - 'mo': { + mo: { 'div-0': ['A9', 'B0'], 'div-1': ['B1', 'B2'], + insights: { + ilv: ['div-1'], + miv: ['div-2'], + } } + }, + insights: { + ilv: ['div-5'], + miv: ['div-6'], } }; - it('Properly set the score file url', () => { + it('Properly set the score file url and scores', () => { optimeraRTD.setScores(JSON.stringify(scores)); expect(optimeraRTD.optimeraTargeting['div-0']).to.include.ordered.members(['A5', 'A6']); expect(optimeraRTD.optimeraTargeting['div-1']).to.include.ordered.members(['A7', 'A8']); }); }); +describe('Optimera RTD propery sets the window.optimera object', () => { + const scores = { + 'div-0': ['A1', 'A2'], + 'div-1': ['A3', 'A4'], + device: { + de: { + 'div-0': ['A5', 'A6'], + 'div-1': ['A7', 'A8'], + insights: { + ilv: ['div-0'], + miv: ['div-4'], + } + }, + mo: { + 'div-0': ['A9', 'B0'], + 'div-1': ['B1', 'B2'], + insights: { + ilv: ['div-1'], + miv: ['div-2'], + } + } + }, + insights: { + ilv: ['div-5'], + miv: ['div-6'], + } + }; + it('Properly set the score file url and scores', () => { + optimeraRTD.setScores(JSON.stringify(scores)); + expect(window.optimera.data['div-1']).to.include.ordered.members(['A7', 'A8']); + expect(window.optimera.insights.ilv).to.include.ordered.members(['div-0']); + }); +}); + describe('Optimera RTD targeting object is properly formed', () => { const adDivs = ['div-0', 'div-1']; it('applyTargeting properly created the targeting object', () => { const targeting = optimeraRTD.returnTargetingData(adDivs); - expect(targeting).to.deep.include({'div-0': {'optimera': [['A5', 'A6']]}}); - expect(targeting).to.deep.include({'div-1': {'optimera': [['A7', 'A8']]}}); + expect(targeting).to.deep.include({ 'div-0': { optimera: [['A5', 'A6']] } }); + expect(targeting).to.deep.include({ 'div-1': { optimera: [['A7', 'A8']] } }); }); }); describe('Optimera RTD error logging', () => { let utilsLogErrorStub; - beforeEach(function () { + beforeEach(() => { utilsLogErrorStub = sinon.stub(utils, 'logError'); }); - afterEach(function () { + afterEach(() => { utilsLogErrorStub.restore(); }); diff --git a/test/spec/modules/optimonAnalyticsAdapter_spec.js b/test/spec/modules/optimonAnalyticsAdapter_spec.js index b5b76ce3fde..c50bfcb170f 100644 --- a/test/spec/modules/optimonAnalyticsAdapter_spec.js +++ b/test/spec/modules/optimonAnalyticsAdapter_spec.js @@ -2,7 +2,7 @@ import * as utils from 'src/utils.js'; import { expect } from 'chai'; import optimonAnalyticsAdapter from '../../../modules/optimonAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager'; -import events from 'src/events'; +import * as events from 'src/events'; import constants from 'src/constants.json' const AD_UNIT_CODE = 'demo-adunit-1'; diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js index 0a18799ad4b..750524cf47f 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -283,6 +283,27 @@ describe('orbidderBidAdapter', () => { }); }); + describe('buildRequests with price floor module', () => { + const bidRequest = deepClone(defaultBidRequestBanner); + bidRequest.params.bidfloor = 1; + bidRequest.getFloor = (floorObj) => { + return { + floor: bidRequest.floors.values['banner|640x480'], + currency: floorObj.currency, + mediaType: floorObj.mediaType + } + }; + + bidRequest.floors = { + currency: 'EUR', + values: { + 'banner|640x480': 15.07 + } + }; + const request = buildRequest(bidRequest); + expect(request.data.params.bidfloor).to.equal(15.07); + }); + describe('interpretResponse', () => { it('banner: should get correct bid response', () => { const serverResponse = [ diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index 4bc163aefe6..5dbdd049d82 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -100,6 +100,38 @@ describe('Outbrain Adapter', function () { } expect(spec.isBidRequestValid(bid)).to.equal(false) }) + it('should fail if tag id is not string', function () { + const bid = { + bidder: 'outbrain', + params: { + tagid: 123 + }, + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should fail if badv does not include strings', function () { + const bid = { + bidder: 'outbrain', + params: { + tagid: 123, + badv: ['a', 2, 'c'] + }, + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should fail if bcat does not include strings', function () { + const bid = { + bidder: 'outbrain', + params: { + tagid: 123, + bcat: ['a', 2, 'c'] + }, + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) it('should succeed with outbrain config', function () { const bid = { bidder: 'outbrain', @@ -362,6 +394,63 @@ describe('Outbrain Adapter', function () { {source: 'liveramp.com', uids: [{id: 'id-value', atype: 3}]} ]); }); + + it('should pass bidfloor', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + bidRequest.getFloor = function() { + return { + currency: 'USD', + floor: 1.23, + } + } + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.imp[0].bidfloor).to.equal(1.23) + }); + + it('should transform string sizes to numbers', function () { + let bidRequest = { + bidId: 'bidId', + params: {}, + ...commonBidRequest, + ...nativeBidRequestParams, + }; + bidRequest.nativeParams.image.sizes = ['120', '100'] + + const expectedNativeAssets = { + assets: [ + { + required: 1, + id: 3, + img: { + type: 3, + w: 120, + h: 100 + } + }, + { + required: 1, + id: 0, + title: {} + }, + { + required: 0, + id: 5, + data: { + type: 1 + } + } + ] + } + + let res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data) + expect(resData.imp[0].native.request).to.equal(JSON.stringify(expectedNativeAssets)); + }); }) describe('interpretResponse', function () { @@ -382,7 +471,7 @@ describe('Outbrain Adapter', function () { impid: '1', price: 1.1, nurl: 'http://example.com/win/${AUCTION_PRICE}', - adm: '{"ver":"1.2","assets":[{"id":3,"required":1,"img":{"url":"http://example.com/img/url","w":120,"h":100}},{"id":0,"required":1,"title":{"text":"Test title"}},{"id":5,"data":{"value":"Test sponsor"}}],"link":{"url":"http://example.com/click/url"},"eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + adm: '{"ver":"1.2","assets":[{"id":3,"required":1,"img":{"url":"http://example.com/img/url","w":120,"h":100}},{"id":0,"required":1,"title":{"text":"Test title"}},{"id":5,"data":{"value":"Test sponsor"}}],"privacy":"http://example.com/privacy","link":{"url":"http://example.com/click/url"},"eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', adomain: [ 'example.com' ], @@ -435,7 +524,8 @@ describe('Outbrain Adapter', function () { sponsoredBy: 'Test sponsor', impressionTrackers: [ 'http://example.com/impression', - ] + ], + privacyLink: 'http://example.com/privacy' } } ] diff --git a/test/spec/modules/outbrainBidAdapter_spec.js~HEAD b/test/spec/modules/outbrainBidAdapter_spec.js~HEAD deleted file mode 100644 index a5f23240a7c..00000000000 --- a/test/spec/modules/outbrainBidAdapter_spec.js~HEAD +++ /dev/null @@ -1,562 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/outbrainBidAdapter.js'; -import {config} from 'src/config.js'; -import {server} from 'test/mocks/xhr'; - -describe('Outbrain Adapter', function () { - describe('Bid request and response', function () { - const commonBidRequest = { - bidder: 'outbrain', - params: { - publisher: { - id: 'publisher-id' - }, - }, - bidId: '2d6815a92ba1ba', - auctionId: '12043683-3254-4f74-8934-f941b085579e', - } - const nativeBidRequestParams = { - nativeParams: { - image: { - required: true, - sizes: [ - 120, - 100 - ], - sendId: true - }, - title: { - required: true, - sendId: true - }, - sponsoredBy: { - required: false - } - }, - } - - const displayBidRequestParams = { - sizes: [ - [ - 300, - 250 - ] - ] - } - - describe('isBidRequestValid', function () { - before(() => { - config.setConfig({ - outbrain: { - bidderUrl: 'https://bidder-url.com', - } - } - ) - }) - after(() => { - config.resetConfig() - }) - - it('should fail when bid is invalid', function () { - const bid = { - bidder: 'outbrain', - params: { - publisher: { - id: 'publisher-id', - } - }, - } - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) - it('should succeed when bid contains native params', function () { - const bid = { - bidder: 'outbrain', - params: { - publisher: { - id: 'publisher-id', - } - }, - ...nativeBidRequestParams, - } - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) - it('should succeed when bid contains sizes', function () { - const bid = { - bidder: 'outbrain', - params: { - publisher: { - id: 'publisher-id', - } - }, - ...displayBidRequestParams, - } - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) - it('should fail if publisher id is not set', function () { - const bid = { - bidder: 'outbrain', - ...nativeBidRequestParams, - } - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) - it('should succeed with outbrain config', function () { - const bid = { - bidder: 'outbrain', - params: { - publisher: { - id: 'publisher-id', - } - }, - ...nativeBidRequestParams, - } - config.resetConfig() - config.setConfig({ - outbrain: { - bidderUrl: 'https://bidder-url.com', - } - }) - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) - it('should fail if bidder url is not set', function () { - const bid = { - bidder: 'outbrain', - params: { - publisher: { - id: 'publisher-id', - } - }, - ...nativeBidRequestParams, - } - config.resetConfig() - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) - }) - - describe('buildRequests', function () { - before(() => { - config.setConfig({ - outbrain: { - bidderUrl: 'https://bidder-url.com', - } - } - ) - }) - after(() => { - config.resetConfig() - }) - - const commonBidderRequest = { - refererInfo: { - referer: 'https://example.com/' - } - } - - it('should build native request', function () { - const bidRequest = { - ...commonBidRequest, - ...nativeBidRequestParams, - } - const expectedNativeAssets = { - assets: [ - { - required: 1, - id: 3, - img: { - type: 3, - w: 120, - h: 100 - } - }, - { - required: 1, - id: 0, - title: {} - }, - { - required: 0, - id: 5, - data: { - type: 1 - } - } - ] - } - const expectedData = { - site: { - page: 'https://example.com/', - publisher: { - id: 'publisher-id' - } - }, - device: { - ua: navigator.userAgent - }, - source: { - fd: 1 - }, - cur: [ - 'USD' - ], - imp: [ - { - id: '1', - native: { - request: JSON.stringify(expectedNativeAssets) - } - } - ], - 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 build display request', function () { - const bidRequest = { - ...commonBidRequest, - ...displayBidRequestParams, - } - const expectedData = { - site: { - page: 'https://example.com/', - publisher: { - id: 'publisher-id' - } - }, - device: { - ua: navigator.userAgent - }, - source: { - fd: 1 - }, - cur: [ - 'USD' - ], - imp: [ - { - id: '1', - banner: { - format: [ - { - w: 300, - h: 250 - } - ] - } - } - ], - 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, - ...nativeBidRequestParams, - } - bidRequest.params.tagid = 'test-tag' - bidRequest.params.publisher.name = 'test-publisher' - bidRequest.params.publisher.domain = 'test-publisher.com' - bidRequest.params.bcat = ['bad-category'] - bidRequest.params.badv = ['bad-advertiser'] - - const res = spec.buildRequests([bidRequest], commonBidderRequest) - const resData = JSON.parse(res.data) - expect(resData.imp[0].tagid).to.equal('test-tag') - expect(resData.site.publisher.name).to.equal('test-publisher') - expect(resData.site.publisher.domain).to.equal('test-publisher.com') - expect(resData.bcat).to.deep.equal(['bad-category']) - expect(resData.badv).to.deep.equal(['bad-advertiser']) - }); - - it('should pass bidder timeout', function () { - const bidRequest = { - ...commonBidRequest, - ...nativeBidRequestParams, - } - const bidderRequest = { - ...commonBidderRequest, - timeout: 500 - } - const res = spec.buildRequests([bidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.tmax).to.equal(500) - }); - - it('should pass GDPR consent', function () { - const bidRequest = { - ...commonBidRequest, - ...nativeBidRequestParams, - } - const bidderRequest = { - ...commonBidderRequest, - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - } - } - const res = spec.buildRequests([bidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.user.ext.consent).to.equal('consentString') - expect(resData.regs.ext.gdpr).to.equal(1) - }); - - it('should pass us privacy consent', function () { - const bidRequest = { - ...commonBidRequest, - ...nativeBidRequestParams, - } - const bidderRequest = { - ...commonBidderRequest, - uspConsent: 'consentString' - } - const res = spec.buildRequests([bidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.regs.ext.us_privacy).to.equal('consentString') - }); - - it('should pass coppa consent', function () { - const bidRequest = { - ...commonBidRequest, - ...nativeBidRequestParams, - } - config.setConfig({coppa: true}) - - const res = spec.buildRequests([bidRequest], commonBidderRequest) - const resData = JSON.parse(res.data) - expect(resData.regs.coppa).to.equal(1) - - config.resetConfig() - }); - }) - - describe('interpretResponse', function () { - it('should return empty array if no valid bids', function () { - const res = spec.interpretResponse({}, []) - expect(res).to.be.an('array').that.is.empty - }); - - it('should interpret native response', function () { - const serverResponse = { - body: { - id: '0a73e68c-9967-4391-b01b-dda2d9fc54e4', - seatbid: [ - { - bid: [ - { - id: '82822cf5-259c-11eb-8a52-f29e5275aa57', - impid: '1', - price: 1.1, - nurl: 'http://example.com/win/${AUCTION_PRICE}', - adm: '{"ver":"1.2","assets":[{"id":3,"required":1,"img":{"url":"http://example.com/img/url","w":120,"h":100}},{"id":0,"required":1,"title":{"text":"Test title"}},{"id":5,"data":{"value":"Test sponsor"}}],"link":{"url":"http://example.com/click/url"},"eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', - adomain: [ - 'example.com' - ], - cid: '3487171', - crid: '28023739', - cat: [ - 'IAB10-2' - ] - } - ], - seat: 'acc-5537' - } - ], - bidid: '82822cf5-259c-11eb-8a52-b48e7518c657', - cur: 'USD' - }, - } - const request = { - bids: [ - { - ...commonBidRequest, - ...nativeBidRequestParams, - } - ] - } - const expectedRes = [ - { - requestId: request.bids[0].bidId, - cpm: 1.1, - creativeId: '28023739', - ttl: 360, - netRevenue: false, - currency: 'USD', - mediaType: 'native', - nurl: 'http://example.com/win/${AUCTION_PRICE}', - meta: { - 'advertiserDomains': [ - 'example.com' - ] - }, - native: { - clickTrackers: undefined, - clickUrl: 'http://example.com/click/url', - image: { - url: 'http://example.com/img/url', - width: 120, - height: 100 - }, - title: 'Test title', - sponsoredBy: 'Test sponsor', - impressionTrackers: [ - 'http://example.com/impression', - ] - } - } - ] - - const res = spec.interpretResponse(serverResponse, request) - expect(res).to.deep.equal(expectedRes) - }); - - it('should interpret display response', function () { - const serverResponse = { - body: { - id: '6b2eedc8-8ff5-46ef-adcf-e701b508943e', - seatbid: [ - { - bid: [ - { - id: 'd90fe7fa-28d7-11eb-8ce4-462a842a7cf9', - impid: '1', - price: 1.1, - nurl: 'http://example.com/win/${AUCTION_PRICE}', - adm: '
ad
', - adomain: [ - 'example.com' - ], - cid: '3865084', - crid: '29998660', - cat: [ - 'IAB10-2' - ], - w: 300, - h: 250 - } - ], - seat: 'acc-6536' - } - ], - bidid: 'd90fe7fa-28d7-11eb-8ce4-13d94bfa26f9', - cur: 'USD' - } - } - const request = { - bids: [ - { - ...commonBidRequest, - ...displayBidRequestParams - } - ] - } - const expectedRes = [ - { - requestId: request.bids[0].bidId, - cpm: 1.1, - creativeId: '29998660', - ttl: 360, - netRevenue: false, - currency: 'USD', - mediaType: 'banner', - nurl: 'http://example.com/win/${AUCTION_PRICE}', - ad: '
ad
', - width: 300, - height: 250, - meta: { - 'advertiserDomains': [ - 'example.com' - ] - }, - } - ] - - const res = spec.interpretResponse(serverResponse, request) - expect(res).to.deep.equal(expectedRes) - }); - }) - }) - - describe('getUserSyncs', function () { - const usersyncUrl = 'https://usersync-url.com'; - beforeEach(() => { - config.setConfig({ - outbrain: { - usersyncUrl: usersyncUrl, - } - } - ) - }) - after(() => { - config.resetConfig() - }) - - 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}]) - }) - - it('should not return user sync if pixel disabled', function () { - 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}) - 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([{ - type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` - }]); - 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([{ - type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` - }]); - }); - - 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([{ - type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` - }]); - }); - }) - - describe('onBidWon', function () { - it('should make an ajax call with the original cpm', function () { - const bid = { - nurl: 'http://example.com/win/${AUCTION_PRICE}', - cpm: 2.1, - originalCpm: 1.1, - } - spec.onBidWon(bid) - expect(server.requests[0].url).to.equals('http://example.com/win/1.1') - }); - }) -}) diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index f9941b41189..658af310ea5 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -69,8 +69,6 @@ var validBidRequestsMulti = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; -// use 'pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'criteoId' -// see http://prebid.org/dev-docs/modules/userId.html var validBidRequestsWithUserIdData = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -291,7 +289,6 @@ var validBidRequests1OutstreamVideo2020 = [ } ]; -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var validBidderRequest1OutstreamVideo2020 = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -394,7 +391,6 @@ var validBidderRequest1OutstreamVideo2020 = { timeout: 3000 } }; -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var validBidderRequest = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -419,11 +415,6 @@ var validBidderRequest = { } }; -// bidder request with GDPR - change the values for testing: -// gdprConsent.gdprApplies (true/false) -// gdprConsent.vendorData.purposeConsents (make empty, make null, remove it) -// gdprConsent.vendorData.vendorConsents (remove 524, remove all, make the element null, remove it) -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var bidderRequestWithFullGdpr = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -512,7 +503,6 @@ var gdpr1 = { 'gdprApplies': true }; -// simulating the Mirror var bidderRequestWithPartialGdpr = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -558,7 +548,6 @@ var bidderRequestWithPartialGdpr = { } }; -// make sure the impid matches the request bidId var validResponse = { 'body': { 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', @@ -1113,7 +1102,6 @@ var multiRequest1 = [ } ]; -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var multiBidderRequest1 = { bidderRequest: { 'bidderCode': 'ozone', @@ -1507,7 +1495,6 @@ var multiResponse1 = { describe('ozone Adapter', function () { describe('isBidRequestValid', function () { - // A test ad unit that will consistently return test creatives let validBidReq = { bidder: BIDDER_CODE, params: { @@ -1941,7 +1928,6 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); expect(request).to.be.a('array'); expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - // need to reset the singleRequest config flag: config.setConfig({'ozone': {'singleRequest': true}}); }); @@ -1965,7 +1951,6 @@ describe('ozone Adapter', function () { expect(payload.user.ext.consent).to.equal(consentString); }); - // mirror it('should add gdpr consent information to the request when vendorData is missing vendorConsents (Mirror)', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; let bidderRequest = validBidderRequest.bidderRequest; @@ -2018,7 +2003,6 @@ describe('ozone Adapter', function () { }; let bidRequests = validBidRequests; - // values from http://prebid.org/dev-docs/modules/userId.html#pubcommon-id bidRequests[0]['userId'] = { 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, @@ -2038,13 +2022,11 @@ describe('ozone Adapter', function () { it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { let bidRequests = validBidRequests; - // values from http://prebid.org/dev-docs/modules/userId.html#pubcommon-id bidRequests[0]['userId'] = { 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, 'idl_env': '3333', 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - // 'pubcid': '5555', // remove pubcid from here to emulate the OLD module & cause the failover code to kick in 'tdid': '6666', 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} }; @@ -2170,7 +2152,6 @@ describe('ozone Adapter', function () { }); it('should use oztestmode GET value if set', function() { var specMock = utils.deepClone(spec); - // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; @@ -2181,7 +2162,6 @@ describe('ozone Adapter', function () { }); it('should pass through GET params if present: ozf, ozpf, ozrp, ozip', function() { var specMock = utils.deepClone(spec); - // mock the getGetParametersAsObject function to simulate GET parameters : specMock.getGetParametersAsObject = function() { return {ozf: '1', ozpf: '0', ozrp: '2', ozip: '123'}; }; @@ -2194,7 +2174,6 @@ describe('ozone Adapter', function () { }); it('should pass through GET params if present: ozf, ozpf, ozrp, ozip with alternative values', function() { var specMock = utils.deepClone(spec); - // mock the getGetParametersAsObject function to simulate GET parameters : specMock.getGetParametersAsObject = function() { return {ozf: 'false', ozpf: 'true', ozrp: 'xyz', ozip: 'hello'}; }; @@ -2207,7 +2186,6 @@ describe('ozone Adapter', function () { }); it('should use oztestmode GET value if set, even if there is no customdata in config', function() { var specMock = utils.deepClone(spec); - // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; @@ -2217,7 +2195,6 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); }); it('should use GET values auction=dev & cookiesync=dev if set', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {}; @@ -2228,8 +2205,6 @@ describe('ozone Adapter', function () { let cookieUrl = specMock.getCookieSyncUrl(); expect(cookieUrl).to.equal('https://elb.the-ozone-project.com/static/load-cookie.html'); - // now mock the response from getGetParametersAsObject & do the request again - specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'auction': 'dev', 'cookiesync': 'dev'}; @@ -2241,7 +2216,6 @@ describe('ozone Adapter', function () { expect(cookieUrl).to.equal('https://test.ozpr.net/static/load-cookie.html'); }); it('should use a valid ozstoredrequest GET value if set to override the placementId values, and set oz_rw if we find it', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'ozstoredrequest': '1122334455'}; // 10 digits are valid @@ -2252,7 +2226,6 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1122334455'); }); it('should NOT use an invalid ozstoredrequest GET value if set to override the placementId values, and set oz_rw to 0', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'ozstoredrequest': 'BADVAL'}; // 10 digits are valid @@ -2424,6 +2397,26 @@ describe('ozone Adapter', function () { expect(utils.deepAccess(payload, 'imp.0.floor.banner.floor')).to.equal(0.8); config.resetConfig(); }); + + it('handles schain object in each bidrequest (will be the same in each br)', function () { + let br = JSON.parse(JSON.stringify(validBidRequests)); + let schainConfigObject = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'bidderA.com', + 'sid': '00001', + 'hp': 1 + } + ] + }; + br[0]['schain'] = schainConfigObject; + const request = spec.buildRequests(br, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.source.ext).to.haveOwnProperty('schain'); + expect(data.source.ext.schain).to.deep.equal(schainConfigObject); // .deep.equal() : Target object deeply (but not strictly) equals `{a: 1}` + }); }); describe('interpretResponse', function () { @@ -2624,7 +2617,6 @@ describe('ozone Adapter', function () { expect(result[1]['impid']).to.equal('3025f169863b7f8'); expect(result[1]['id']).to.equal('18552976939844999'); expect(result[1]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('3025f169863b7f8-0-oz-2'); - // change the bid values so a different second bid for an impid by the same bidder gets dropped validres = JSON.parse(JSON.stringify(multiResponse1)); validres.body.seatbid[0].bid[1].price = 1.1; validres.body.seatbid[0].bid[1].cpm = 1.1; @@ -2647,7 +2639,6 @@ describe('ozone Adapter', function () { expect(result).to.be.empty; }); it('should append the various values if they exist', function() { - // get the cookie bag populated spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1); expect(result).to.be.an('array'); @@ -2657,14 +2648,12 @@ describe('ozone Adapter', function () { expect(result[0].url).to.include('gdpr_consent=BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); }); it('should append ccpa (usp data)', function() { - // get the cookie bag populated spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1, '1YYN'); expect(result).to.be.an('array'); expect(result[0].url).to.include('usp_consent=1YYN'); }); it('should use "" if no usp is sent to cookieSync', function() { - // get the cookie bag populated spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1); expect(result).to.be.an('array'); @@ -2887,4 +2876,27 @@ describe('ozone Adapter', function () { expect(response[1].bid.length).to.equal(2); }); }); + /** + * spec.getWhitelabelConfigItem test - get a config value for a whitelabelled bidder, + * from a standard ozone.oz_xxxx_yyy string + */ + describe('getWhitelabelConfigItem', function() { + it('should fetch the whitelabelled equivalent config value correctly', function () { + var specMock = utils.deepClone(spec); + config.setConfig({'ozone': {'oz_omp_floor': 'ozone-floor-value'}}); + config.setConfig({'markbidder': {'mb_omp_floor': 'markbidder-floor-value'}}); + specMock.propertyBag.whitelabel = {bidder: 'ozone', keyPrefix: 'oz'}; + let testKey = 'ozone.oz_omp_floor'; + let ozone_value = specMock.getWhitelabelConfigItem(testKey); + expect(ozone_value).to.equal('ozone-floor-value'); + specMock.propertyBag.whitelabel = {bidder: 'markbidder', keyPrefix: 'mb'}; + let markbidder_config = specMock.getWhitelabelConfigItem(testKey); + expect(markbidder_config).to.equal('markbidder-floor-value'); + config.setConfig({'markbidder': {'singleRequest': 'markbidder-singlerequest-value'}}); + let testKey2 = 'ozone.singleRequest'; + let markbidder_config2 = specMock.getWhitelabelConfigItem(testKey2); + expect(markbidder_config2).to.equal('markbidder-singlerequest-value'); + config.resetConfig(); + }); + }); }); diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 44118fb50de..176ba70fbcb 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; import { newStorageManager } from 'src/storageManager.js'; diff --git a/test/spec/modules/pilotxBidAdapter_spec.js b/test/spec/modules/pilotxBidAdapter_spec.js new file mode 100644 index 00000000000..2ef31c0a8f5 --- /dev/null +++ b/test/spec/modules/pilotxBidAdapter_spec.js @@ -0,0 +1,244 @@ +// import or require modules necessary for the test, e.g.: +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { spec } from '../../../modules/pilotxBidAdapter.js'; + +describe('pilotxAdapter', function () { + describe('isBidRequestValid', function () { + let banner; + beforeEach(function () { + banner = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + sizes: [[300, 250], [468, 60]], + bidId: '2de8c82e30665a', + params: { + placementId: '1' + } + }; + }); + + it('should return false if sizes is empty', function () { + banner.sizes = [] + expect(spec.isBidRequestValid(banner)).to.equal(false); + }); + it('should return true if all is valid/ is not empty', function () { + expect(spec.isBidRequestValid(banner)).to.equal(true); + }); + it('should return false if there is no placement id found', function () { + banner.params = {} + expect(spec.isBidRequestValid(banner)).to.equal(false); + }); + it('should return false if sizes is empty', function () { + banner.sizes = [] + expect(spec.isBidRequestValid(banner)).to.equal(false); + }); + it('should return false for no size and empty params', function() { + const emptySizes = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + bidId: '2de8c82e30665a', + params: { + placementId: '1', + sizes: [] + } + }; + expect(spec.isBidRequestValid(emptySizes)).to.equal(false); + }) + it('should return true for no size and valid size params', function() { + const emptySizes = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + bidId: '2de8c82e30665a', + params: { + placementId: '1', + sizes: [[300, 250], [468, 60]] + } + }; + expect(spec.isBidRequestValid(emptySizes)).to.equal(true); + }) + it('should return false for no size items', function() { + const emptySizes = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + bidId: '2de8c82e30665a', + params: { + placementId: '1' + } + }; + expect(spec.isBidRequestValid(emptySizes)).to.equal(false); + }) + }); + + describe('buildRequests', function () { + const mockRequest = { refererInfo: {} }; + const mockRequestGDPR = { + refererInfo: {}, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gdprApplies: true + } + + } + const mockVideo1 = [{ + adUnitCode: 'video1', + auctionId: '01618029-7ae9-4e98-a73a-1ed0c817f414', + bidId: '2a59588c0114fa', + bidRequestsCount: 1, + bidder: 'pilotx', + bidderRequestId: '1f6b4ba2039726', + bidderRequestsCount: 1, + bidderWinsCount: 0, + crumbs: { pubcid: 'de5240ef-ff80-4b55-8837-26a11cfbf64c' }, + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4'], + playbackmethod: [2], + playerSize: [[640, 480]], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1 + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'video1' + } + } + }, + params: { placementId: '379' }, + sizes: [[640, 480]], + src: 'client', + transactionId: 'fec9f2ff-da13-4921-8437-8d679c2be7fe', + }]; + const mockVideo2 = [{ + adUnitCode: 'video1', + auctionId: '01618029-7ae9-4e98-a73a-1ed0c817f414', + bidId: '2a59588c0114fa', + bidRequestsCount: 1, + bidder: 'pilotx', + bidderRequestId: '1f6b4ba2039726', + bidderRequestsCount: 1, + bidderWinsCount: 0, + crumbs: { pubcid: 'de5240ef-ff80-4b55-8837-26a11cfbf64c' }, + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4'], + playbackmethod: [2], + playerSize: [[640, 480]], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1 + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'video1' + } + } + }, + params: { placementId: '379' }, + sizes: [640, 480], + src: 'client', + transactionId: 'fec9f2ff-da13-4921-8437-8d679c2be7fe', + }]; + it('should return correct response', function () { + const builtRequest = spec.buildRequests(mockVideo1, mockRequest) + let builtRequestData = builtRequest.data + let data = JSON.parse(builtRequestData) + expect(data['379'].bidId).to.equal(mockVideo1[0].bidId) + }); + it('should return correct response for only array of size', function () { + const builtRequest = spec.buildRequests(mockVideo2, mockRequest) + let builtRequestData = builtRequest.data + let data = JSON.parse(builtRequestData) + expect(data['379'].sizes[0][0]).to.equal(mockVideo2[0].sizes[0]) + expect(data['379'].sizes[0][1]).to.equal(mockVideo2[0].sizes[1]) + }); + it('should be valid and pass gdpr items correctly', function () { + const builtRequest = spec.buildRequests(mockVideo2, mockRequestGDPR) + let builtRequestData = builtRequest.data + let data = JSON.parse(builtRequestData) + expect(data['379'].gdprConsentString).to.equal(mockRequestGDPR.gdprConsent.consentString) + expect(data['379'].gdprConsentRequired).to.equal(mockRequestGDPR.gdprConsent.gdprApplies) + }); + }); + describe('interpretResponse', function () { + const bidRequest = {} + const serverResponse = { + cpm: 2.5, + creativeId: 'V9060', + currency: 'US', + height: 480, + mediaType: 'video', + netRevenue: false, + requestId: '273b39c74069cb', + ttl: 3000, + vastUrl: 'http://testadserver.com/ads?&k=60cd901ad8ab70c9cedf373cb17b93b8&pid=379&tid=91342717', + width: 640 + } + const serverResponseVideo = { + body: serverResponse + } + const serverResponse2 = { + cpm: 2.5, + creativeId: 'V9060', + currency: 'US', + height: 480, + mediaType: 'banner', + netRevenue: false, + requestId: '273b39c74069cb', + ttl: 3000, + vastUrl: 'http://testadserver.com/ads?&k=60cd901ad8ab70c9cedf373cb17b93b8&pid=379&tid=91342717', + width: 640 + } + const serverResponseBanner = { + body: serverResponse2 + } + it('should be valid from bidRequest for video', function () { + const bidResponses = spec.interpretResponse(serverResponseVideo, bidRequest) + expect(bidResponses[0].requestId).to.equal(serverResponse.requestId) + expect(bidResponses[0].cpm).to.equal(serverResponse.cpm) + expect(bidResponses[0].width).to.equal(serverResponse.width) + expect(bidResponses[0].height).to.equal(serverResponse.height) + expect(bidResponses[0].creativeId).to.equal(serverResponse.creativeId) + expect(bidResponses[0].currency).to.equal(serverResponse.currency) + expect(bidResponses[0].netRevenue).to.equal(serverResponse.netRevenue) + expect(bidResponses[0].ttl).to.equal(serverResponse.ttl) + expect(bidResponses[0].vastUrl).to.equal(serverResponse.vastUrl) + expect(bidResponses[0].mediaType).to.equal(serverResponse.mediaType) + expect(bidResponses[0].meta.mediaType).to.equal(serverResponse.mediaType) + }); + it('should be valid from bidRequest for banner', function () { + const bidResponses = spec.interpretResponse(serverResponseBanner, bidRequest) + expect(bidResponses[0].requestId).to.equal(serverResponse2.requestId) + expect(bidResponses[0].cpm).to.equal(serverResponse2.cpm) + expect(bidResponses[0].width).to.equal(serverResponse2.width) + expect(bidResponses[0].height).to.equal(serverResponse2.height) + expect(bidResponses[0].creativeId).to.equal(serverResponse2.creativeId) + expect(bidResponses[0].currency).to.equal(serverResponse2.currency) + expect(bidResponses[0].netRevenue).to.equal(serverResponse2.netRevenue) + expect(bidResponses[0].ttl).to.equal(serverResponse2.ttl) + expect(bidResponses[0].ad).to.equal(serverResponse2.ad) + expect(bidResponses[0].mediaType).to.equal(serverResponse2.mediaType) + expect(bidResponses[0].meta.mediaType).to.equal(serverResponse2.mediaType) + }); + }); + describe('setPlacementID', function () { + const multiplePlacementIds = ['380', '381'] + it('should be valid with an array of placement ids passed', function () { + const placementID = spec.setPlacementID(multiplePlacementIds) + expect(placementID).to.equal('380#381') + }); + it('should be valid with single placement ID passed', function () { + const placementID = spec.setPlacementID('381') + expect(placementID).to.equal('381') + }); + }); + // Add other `describe` or `it` blocks as necessary +}); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index a06d4dbcd68..a0c7903091c 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -4,10 +4,20 @@ 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 events from 'src/events.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'; let CONFIG = { accountId: '1', @@ -274,7 +284,7 @@ const RESPONSE_OPENRTB = { 'win': 'http://wurl.org?id=333' }, 'meta': { - 'dchain': { 'ver': '1.0', 'complete': 0, 'nodes': [ { 'asi': 'magnite.com', 'bsid': '123456789', } ] } + 'dchain': { 'ver': '1.0', 'complete': 0, 'nodes': [{ 'asi': 'magnite.com', 'bsid': '123456789', }] } } }, 'bidder': { @@ -447,6 +457,18 @@ describe('S2S Adapter', function () { addBidResponse = sinon.spy(), done = sinon.spy(); + function prepRequest(req) { + req.ad_units.forEach((adUnit) => { + delete adUnit.nativeParams + }); + decorateAdUnitsWithNativeParams(req.ad_units); + } + + before(() => { + hook.ready(); + prepRequest(REQUEST); + }); + beforeEach(function () { config.resetConfig(); adapter = new Adapter(); @@ -507,7 +529,18 @@ describe('S2S Adapter', function () { resetSyncedStatus(); }); - it('should block request if config did not define p1Consent URL in endpoint object config', function() { + it('should set id to auction ID and source.tid to tid', function () { + config.setConfig({ s2sConfig: CONFIG }); + + adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.id).to.equal('173afb6d132ba3'); + expect(requestBid.source).to.be.an('object'); + expect(requestBid.source.tid).to.equal('437fbbf5-33f5-487a-8e16-a7112903cfe5'); + }); + + 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' }; config.setConfig({ s2sConfig: badConfig }); @@ -520,7 +553,7 @@ describe('S2S Adapter', function () { expect(server.requests.length).to.equal(0); }); - it('should block request if config did not define noP1Consent URL in endpoint object config', function() { + it('should block request if config did not define noP1Consent URL in endpoint object config', function () { let badConfig = utils.deepClone(CONFIG); badConfig.endpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }; config.setConfig({ s2sConfig: badConfig }); @@ -548,7 +581,7 @@ describe('S2S Adapter', function () { expect(server.requests.length).to.equal(0); }); - it('should block request if config did not define any URLs in endpoint object config', function() { + it('should block request if config did not define any URLs in endpoint object config', function () { let badConfig = utils.deepClone(CONFIG); badConfig.endpoint = {}; config.setConfig({ s2sConfig: badConfig }); @@ -950,11 +983,11 @@ describe('S2S Adapter', function () { ).to.be.true; // if getFloor does not return number - getFloorResponse = {currency: 'EUR', floor: 'not a number'}; + getFloorResponse = { currency: 'EUR', floor: 'not a number' }; runTest(undefined, undefined); // if getFloor does not return currency - getFloorResponse = {floor: 1.1}; + getFloorResponse = { floor: 1.1 }; runTest(undefined, undefined); }); @@ -969,7 +1002,7 @@ describe('S2S Adapter', function () { sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); // returns USD and string floor - getFloorResponse = {currency: 'USD', floor: '1.23'}; + getFloorResponse = { currency: 'USD', floor: '1.23' }; runTest(1.23, 'USD'); // make sure getFloor was called expect( @@ -979,7 +1012,7 @@ describe('S2S Adapter', function () { ).to.be.true; // returns non USD and number floor - getFloorResponse = {currency: 'EUR', floor: 0.85}; + getFloorResponse = { currency: 'EUR', floor: 0.85 }; runTest(0.85, 'EUR'); }); @@ -996,7 +1029,7 @@ describe('S2S Adapter', function () { sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); // returns USD and string floor - getFloorResponse = {currency: 'JPY', floor: 97.2}; + getFloorResponse = { currency: 'JPY', floor: 97.2 }; runTest(97.2, 'JPY'); // make sure getFloor was called with JPY expect( @@ -1081,6 +1114,19 @@ describe('S2S Adapter', function () { }); }); + 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 }; + prepRequest(req); + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const nativeReq = JSON.parse(JSON.parse(server.requests[0].requestBody).imp[0].native.request); + const icons = nativeReq.assets.map((a) => a.img).filter((img) => img && img.type === 1); + expect(icons).to.have.length(1); + expect(icons[0].hmin).to.equal(2); + expect(icons[0].wmin).to.equal(1); + expect(deepAccess(icons[0], 'ext.aspectratios')).to.be.undefined; + }) + it('adds site if app is not present', function () { const _config = { s2sConfig: CONFIG, @@ -1174,16 +1220,22 @@ describe('S2S Adapter', function () { }); }); - it('skips pbs alias when skipPbsAliasing is enabled in adapter', function() { + it('skips pbs alias when skipPbsAliasing is enabled in adapter', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: { p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' } }); config.setConfig({ s2sConfig: s2sConfig }); - + registerBidder({ + code: 'bidderCodeForTestSkipBPSAlias', + aliases: [{ + code: 'bidderCodeForTestSkipBPSAlias_Alias', + skipPbsAliasing: true + }] + }) const aliasBidder = { - bidder: 'mediafuse', + bidder: 'bidderCodeForTestSkipBPSAlias_Alias', params: { aid: 123 } }; @@ -1220,7 +1272,7 @@ describe('S2S Adapter', function () { const alias = 'foobar_1'; const aliasBidder = { bidder: alias, - params: { aid: 123456 } + params: { aid: 1234567 } }; const request = utils.deepClone(REQUEST); @@ -1688,7 +1740,7 @@ describe('S2S Adapter', function () { maxbids: 2 }]; - config.setConfig({multibid: multibid}); + config.setConfig({ multibid: multibid }); adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); @@ -1702,19 +1754,25 @@ describe('S2S Adapter', function () { adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); - expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.version$'}); + expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({ name: 'pbjs', version: 'v$prebid.version$' }); }); - it('does not set pbjs version in request if channel does exist in s2sConfig', () => { + it('extPrebid is now mergedDeep -> should include default channel as well', () => { const s2sBidRequest = utils.deepClone(REQUEST); const bidRequests = utils.deepClone(BID_REQUESTS); - utils.deepSetValue(s2sBidRequest, 's2sConfig.extPrebid.channel', {test: 1}); + utils.deepSetValue(s2sBidRequest, 's2sConfig.extPrebid.channel', { test: 1 }); adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); - expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({test: 1}); + + // extPrebid is now deep merged with + expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({ + name: 'pbjs', + test: 1, + version: 'v$prebid.version$' + }); }); it('passes first party data in request', () => { @@ -1747,10 +1805,10 @@ describe('S2S Adapter', function () { }; const bcat = ['IAB25', 'IAB7-39']; const badv = ['blockedAdv-1.com', 'blockedAdv-2.com']; - const allowedBidders = [ 'rubicon', 'appnexus' ]; + const allowedBidders = ['rubicon', 'appnexus']; const expected = allowedBidders.map(bidder => ({ - bidders: [ bidder ], + bidders: [bidder], config: { ortb2: { site: { @@ -1777,7 +1835,10 @@ describe('S2S Adapter', function () { } } })); - const commonContextExpected = utils.mergeDeep({'page': 'http://mytestpage.com', 'publisher': {'id': '1'}}, commonContext); + const commonContextExpected = utils.mergeDeep({ + 'page': 'http://mytestpage.com', + 'publisher': { 'id': '1' } + }, commonContext); config.setConfig({ fpd: { context: commonContext, user: commonUser, badv, bcat } }); config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user, bcat, badv } } }); @@ -2032,7 +2093,7 @@ describe('S2S Adapter', function () { }); it('should pass through default adserverTargeting if present in bidObject for video request', function () { - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB); const targetingTestData = { hb_cache_path: '/cache', @@ -2054,7 +2115,7 @@ describe('S2S Adapter', function () { }); }); - it('should set the bidResponse currency to whats in the PBS response', function() { + it('should set the bidResponse currency to whats in the PBS response', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); sinon.assert.calledOnce(addBidResponse); @@ -2062,7 +2123,7 @@ describe('S2S Adapter', function () { expect(pbjsResponse).to.have.property('currency', 'EUR'); }); - it('should set the default bidResponse currency when not specified in OpenRTB', function() { + it('should set the default bidResponse currency when not specified in OpenRTB', function () { let modifiedResponse = utils.deepClone(RESPONSE_OPENRTB); modifiedResponse.cur = ''; adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -2374,11 +2435,8 @@ describe('S2S Adapter', function () { }); it('handles OpenRTB native responses', function () { - sinon.stub(utils, 'getBidRequest').returns({ - adUnitCode: 'div-gpt-ad-1460505748561-0', - bidder: 'appnexus', - bidId: '123' - }); + const stub = sinon.stub(auctionManager, 'index'); + stub.get(() => stubAuctionIndex({ adUnits: REQUEST.ad_units })); const s2sConfig = Object.assign({}, CONFIG, { endpoint: { p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' @@ -2401,7 +2459,81 @@ describe('S2S Adapter', function () { expect(response).to.have.property('requestId', '123'); expect(response).to.have.property('cpm', 10); - utils.getBidRequest.restore(); + stub.restore(); + }); + + it('does not (by default) allow bids that were not requested', function () { + config.setConfig({ s2sConfig: CONFIG }); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const response = deepClone(RESPONSE_OPENRTB); + response.seatbid[0].seat = 'unknown'; + server.requests[0].respond(200, {}, JSON.stringify(response)); + + expect(addBidResponse.called).to.be.false; + }); + + it('allows unrequested bids if config.allowUnknownBidderCodes', function () { + const cfg = { ...CONFIG, allowUnknownBidderCodes: true }; + config.setConfig({ s2sConfig: cfg }); + adapter.callBids({ ...REQUEST, s2sConfig: cfg }, BID_REQUESTS, addBidResponse, done, ajax); + const response = deepClone(RESPONSE_OPENRTB); + response.seatbid[0].seat = 'unknown'; + server.requests[0].respond(200, {}, JSON.stringify(response)); + + 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'})) + }); + + it('copies ortb2Imp to response when there is only a null bid', () => { + const cfg = {...CONFIG}; + config.setConfig({s2sConfig: cfg}); + const ortb2Imp = {ext: {prebid: {storedrequest: 'value'}}}; + const req = {...REQUEST, s2sConfig: cfg, ad_units: [{...REQUEST.ad_units[0], bids: [{bidder: null, bid_id: 'testId'}], ortb2Imp}]}; + 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 actual = JSON.parse(server.requests[0].requestBody); + sinon.assert.match(actual.imp[0], sinon.match(ortb2Imp)); + }); + + describe('on sync requested with no cookie', () => { + let cfg, req, csRes; + + beforeEach(() => { + cfg = utils.deepClone(CONFIG); + req = utils.deepClone(REQUEST); + cfg.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; + req.s2sConfig = cfg; + config.setConfig({ s2sConfig: cfg }); + csRes = utils.deepClone(RESPONSE_NO_COOKIE); + }); + + afterEach(() => { + resetSyncedStatus(); + }) + + Object.entries({ + iframe: () => utils.insertUserSyncIframe, + image: () => utils.triggerPixel, + }).forEach(([type, syncer]) => { + it(`passes timeout to ${type} syncs`, () => { + cfg.syncTimeout = 123; + csRes.bidder_status[0].usersync.type = type; + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(csRes)); + expect(syncer().args[0]).to.include.members([123]); + }); + }); }); }); @@ -2526,21 +2658,6 @@ describe('S2S Adapter', function () { sinon.assert.calledOnce(logErrorSpy); }); - it('should log an error when bidders is missing', function () { - const options = { - accountId: '1', - enabled: true, - timeout: 1000, - adapter: 's2s', - endpoint: { - p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - } - }; - - config.setConfig({ s2sConfig: options }); - sinon.assert.calledOnce(logErrorSpy); - }); - it('should log an error when endpoint is missing', function () { const options = { accountId: '1', @@ -2581,8 +2698,14 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['appnexus']); expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig.endpoint).to.deep.equal({p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/openrtb2/auction'}); - expect(vendorConfig.syncEndpoint).to.deep.equal({p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync'}); + expect(vendorConfig.endpoint).to.deep.equal({ + p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', + noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/openrtb2/auction' + }); + expect(vendorConfig.syncEndpoint).to.deep.equal({ + p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', + noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' + }); expect(vendorConfig).to.have.property('timeout', 750); }); @@ -2602,7 +2725,10 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['appnexus']); expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig.endpoint).to.deep.equal({p1Consent: 'https://ib.adnxs.com/openrtb2/prebid', noP1Consent: 'https://ib.adnxs-simple.com/openrtb2/prebid'}); + expect(vendorConfig.endpoint).to.deep.equal({ + p1Consent: 'https://ib.adnxs.com/openrtb2/prebid', + noP1Consent: 'https://ib.adnxs-simple.com/openrtb2/prebid' + }); expect(vendorConfig.syncEndpoint).to.be.undefined; expect(vendorConfig).to.have.property('timeout', 750); }); @@ -2623,8 +2749,14 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['rubicon']); expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig.endpoint).to.deep.equal({p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction'}); - expect(vendorConfig.syncEndpoint).to.deep.equal({p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync'}); + expect(vendorConfig.endpoint).to.deep.equal({ + p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction' + }); + expect(vendorConfig.syncEndpoint).to.deep.equal({ + p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', + noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync' + }); expect(vendorConfig).to.have.property('timeout', 750); }); @@ -2643,8 +2775,14 @@ describe('S2S Adapter', function () { 'bidders': ['rubicon'], 'defaultVendor': 'rubicon', 'enabled': true, - 'endpoint': {p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction'}, - 'syncEndpoint': {p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync'}, + 'endpoint': { + p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction' + }, + 'syncEndpoint': { + p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', + noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync' + }, 'timeout': 750 }) }); @@ -2665,8 +2803,14 @@ describe('S2S Adapter', function () { accountId: 'abc', bidders: ['rubicon'], defaultVendor: 'rubicon', - endpoint: {p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction'}, - syncEndpoint: {p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync'}, + endpoint: { + p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction' + }, + syncEndpoint: { + p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', + noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync' + }, }) }); @@ -2702,7 +2846,8 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig: { syncUrlModifier: { - appnexus: () => { } + appnexus: () => { + } } } }); @@ -2714,7 +2859,7 @@ describe('S2S Adapter', function () { // Add syncEndpoint so that the request goes to the User Sync endpoint // Modify the bidders property to include an alias for Rubicon adapter - s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; + s2sConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; s2sConfig.bidders = ['appnexus', 'rubicon-alias']; const s2sBidRequest = utils.deepClone(REQUEST); @@ -2785,7 +2930,7 @@ describe('S2S Adapter', function () { it('should add cooperative sync flag to cookie_sync request if property is present', function () { let s2sConfig = utils.deepClone(CONFIG); s2sConfig.coopSync = false; - s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; + s2sConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; const s2sBidRequest = utils.deepClone(REQUEST); s2sBidRequest.s2sConfig = s2sConfig; @@ -2800,7 +2945,7 @@ describe('S2S Adapter', function () { it('should not add cooperative sync flag to cookie_sync request if property is not present', function () { let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; + s2sConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; const s2sBidRequest = utils.deepClone(REQUEST); s2sBidRequest.s2sConfig = s2sConfig; @@ -2813,8 +2958,26 @@ describe('S2S Adapter', function () { expect(requestBid.coopSync).to.be.undefined; }); + it('should set imp banner if ortb2Imp.banner is present', function () { + const consentConfig = { s2sConfig: CONFIG }; + config.setConfig(consentConfig); + const bidRequest = utils.deepClone(REQUEST); + bidRequest.ad_units[0].ortb2Imp = { + banner: { + api: 7 + }, + instl: 1 + }; + + adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + + expect(parsedRequestBody.imp[0].banner.api).to.equal(7); + expect(parsedRequestBody.imp[0].instl).to.equal(1); + }); + it('adds debug flag', function () { - config.setConfig({debug: true}); + config.setConfig({ debug: true }); let bidRequest = utils.deepClone(BID_REQUESTS); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index b3105dafc39..6ea58e8c47a 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -14,8 +14,12 @@ import { fieldMatchingFunctions, allowedFields } from 'modules/priceFloors.js'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; import * as mockGpt from '../integration/faker/googletag.js'; +import 'src/prebid.js'; +import {createBid} from '../../../src/bidfactory.js'; +import {auctionManager} from '../../../src/auctionManager.js'; +import {stubAuctionIndex} from '../../helpers/indexStub.js'; describe('the price floors module', function () { let logErrorSpy; @@ -109,6 +113,7 @@ describe('the price floors module', function () { bidder: 'rubicon', adUnitCode: 'test_div_1', auctionId: '1234-56-789', + transactionId: 'tr_test_div_1' }; function getAdUnitMock(code = 'adUnit-code') { @@ -225,6 +230,44 @@ describe('the price floors module', function () { }); describe('getFirstMatchingFloor', function () { + it('uses a 0 floor as overrite', function () { + let inputFloorData = { + currency: 'USD', + schema: { + delimiter: '|', + fields: ['adUnitCode'] + }, + values: { + 'test_div_1': 0, + 'test_div_2': 2 + }, + default: 0.5 + }; + + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 0, + matchingFloor: 0, + matchingData: 'test_div_1', + matchingRule: 'test_div_1' + }); + + expect(getFirstMatchingFloor(inputFloorData, {...basicBidRequest, adUnitCode: 'test_div_2'}, {mediaType: 'banner', size: '*'})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 2, + matchingFloor: 2, + matchingData: 'test_div_2', + matchingRule: 'test_div_2' + }); + + expect(getFirstMatchingFloor(inputFloorData, {...basicBidRequest, adUnitCode: 'test_div_3'}, {mediaType: 'banner', size: '*'})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 0.5, + matchingFloor: 0.5, + matchingData: 'test_div_3', + matchingRule: undefined + }); + }); it('selects the right floor for different mediaTypes', function () { // banner with * size (not in rule file so does not do anything) expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ @@ -401,6 +444,7 @@ describe('the price floors module', function () { }); describe('with gpt enabled', function () { let gptFloorData; + let indexStub, adUnits; beforeEach(function () { gptFloorData = { currency: 'USD', @@ -426,10 +470,13 @@ describe('the price floors module', function () { code: '/12345/sports/basketball', divId: 'test_div_2' }); + indexStub = sinon.stub(auctionManager, 'index'); + indexStub.get(() => stubAuctionIndex({adUnits})) }); afterEach(function () { // reset it so no lingering stuff from other test specs mockGpt.reset(); + indexStub.restore(); }); it('picks the right rule when looking for gptSlot', function () { expect(getFirstMatchingFloor(gptFloorData, basicBidRequest)).to.deep.equal({ @@ -449,9 +496,10 @@ describe('the price floors module', function () { matchingRule: '/12345/sports/basketball' }); }); - it('picks the gptSlot from the bidRequest and does not call the slotMatching', function () { - const newBidRequest1 = { ...basicBidRequest }; - utils.deepSetValue(newBidRequest1, 'ortb2Imp.ext.data.adserver', { + it('picks the gptSlot from the adUnit and does not call the slotMatching', function () { + const newBidRequest1 = { ...basicBidRequest, transactionId: 'au1' }; + adUnits = [{code: newBidRequest1.code, transactionId: 'au1'}]; + utils.deepSetValue(adUnits[0], 'ortb2Imp.ext.data.adserver', { name: 'gam', adslot: '/12345/news/politics' }) @@ -463,8 +511,9 @@ describe('the price floors module', function () { matchingRule: '/12345/news/politics' }); - const newBidRequest2 = { ...basicBidRequest, adUnitCode: 'test_div_2' }; - utils.deepSetValue(newBidRequest2, 'ortb2Imp.ext.data.adserver', { + const newBidRequest2 = { ...basicBidRequest, adUnitCode: 'test_div_2', transactionId: 'au2' }; + adUnits = [{code: newBidRequest2.adUnitCode, transactionId: newBidRequest2.transactionId}]; + utils.deepSetValue(adUnits[0], 'ortb2Imp.ext.data.adserver', { name: 'gam', adslot: '/12345/news/weather' }) @@ -1565,17 +1614,12 @@ describe('the price floors module', function () { }); }); describe('bidResponseHook tests', function () { - let returnedBidResponse; - let bidderRequest = { - bidderCode: 'appnexus', - auctionId: '123456', - bids: [{ - bidder: 'appnexus', - adUnitCode: 'test_div_1', - auctionId: '123456', - bidId: '1111' - }] - }; + const AUCTION_ID = '123456'; + let returnedBidResponse, indexStub; + let adUnit = { + transactionId: 'au', + code: 'test_div_1' + } let basicBidResponse = { bidderCode: 'appnexus', width: 300, @@ -1583,38 +1627,46 @@ describe('the price floors module', function () { cpm: 0.5, mediaType: 'banner', requestId: '1111', + transactionId: 'au', }; beforeEach(function () { returnedBidResponse = {}; + indexStub = sinon.stub(auctionManager, 'index'); + indexStub.get(() => stubAuctionIndex({adUnits: [adUnit]})); + }); + + afterEach(() => { + indexStub.restore(); }); + function runBidResponse(bidResp = basicBidResponse) { let next = (adUnitCode, bid) => { returnedBidResponse = bid; }; - addBidResponseHook.bind({ bidderRequest })(next, bidResp.adUnitCode, bidResp); + addBidResponseHook(next, bidResp.adUnitCode, Object.assign(createBid(CONSTANTS.STATUS.GOOD, {auctionId: AUCTION_ID}), bidResp)); }; it('continues with the auction if not floors data is present without any flooring', function () { runBidResponse(); expect(returnedBidResponse).to.not.haveOwnProperty('floorData'); }); it('if no matching rule it should not floor and should call log warn', function () { - _floorDataForAuction[bidderRequest.auctionId] = utils.deepClone(basicFloorConfig); - _floorDataForAuction[bidderRequest.auctionId].data.values = { 'video': 1.0 }; + _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[AUCTION_ID].data.values = { 'video': 1.0 }; runBidResponse(); expect(returnedBidResponse).to.not.haveOwnProperty('floorData'); expect(logWarnSpy.calledOnce).to.equal(true); }); it('if it finds a rule and floors should update the bid accordingly', function () { - _floorDataForAuction[bidderRequest.auctionId] = utils.deepClone(basicFloorConfig); - _floorDataForAuction[bidderRequest.auctionId].data.values = { 'banner': 1.0 }; + _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); }); it('if it finds a rule and does not floor should update the bid accordingly', function () { - _floorDataForAuction[bidderRequest.auctionId] = utils.deepClone(basicFloorConfig); - _floorDataForAuction[bidderRequest.auctionId].data.values = { 'banner': 0.3 }; + _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 0.3 }; runBidResponse(); expect(returnedBidResponse).to.haveOwnProperty('floorData'); expect(returnedBidResponse.floorData).to.deep.equal({ @@ -1636,7 +1688,7 @@ describe('the price floors module', function () { expect(returnedBidResponse.cpm).to.equal(0.5); }); it('if should work with more complex rules and update accordingly', function () { - _floorDataForAuction[bidderRequest.auctionId] = { + _floorDataForAuction[AUCTION_ID] = { ...basicFloorConfig, data: { currency: 'USD', @@ -1722,4 +1774,49 @@ describe('the price floors module', function () { expect(_floorDataForAuction[AUCTION_END_EVENT.auctionId]).to.be.undefined; }); }); + + describe('fieldMatchingFunctions', () => { + let sandbox; + + const req = { + ...basicBidRequest, + } + + const resp = { + transactionId: req.transactionId, + size: [100, 100], + mediaType: 'banner', + } + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(auctionManager, 'index').get(() => stubAuctionIndex({ + adUnits: [ + { + code: req.adUnitCode, + transactionId: req.transactionId, + ortb2Imp: {ext: {data: {adserver: {name: 'gam', adslot: 'slot'}}}} + } + ] + })); + }); + + afterEach(() => { + sandbox.restore(); + }) + + Object.entries({ + size: '100x100', + mediaType: resp.mediaType, + gptSlot: 'slot', + domain: 'localhost', + adUnitCode: req.adUnitCode, + }).forEach(([test, expected]) => { + describe(`${test}`, () => { + it('should work with only bidResponse', () => { + expect(fieldMatchingFunctions[test](undefined, resp)).to.eql(expected) + }) + }); + }) + }); }); diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 6568f7aa782..4599eb2a6fa 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -173,7 +173,7 @@ describe('pubGENIUS adapter', () => { expectedRequest = { method: 'POST', - url: 'https://ortb.adpearl.io/prebid/auction', + url: 'https://auction.adpearl.io/prebid/auction', data: { id: 'fake-auction-id', imp: [ @@ -493,7 +493,7 @@ describe('pubGENIUS adapter', () => { }; expectedSync = { type: 'iframe', - url: 'https://ortb.adpearl.io/usersync/pixels.html?', + url: 'https://auction.adpearl.io/usersync/pixels.html?', }; }); @@ -551,7 +551,7 @@ describe('pubGENIUS adapter', () => { onTimeout(timeoutData); expect(server.requests[0].method).to.equal('POST'); - expect(server.requests[0].url).to.equal('https://ortb.adpearl.io/prebid/events?type=timeout'); + expect(server.requests[0].url).to.equal('https://auction.adpearl.io/prebid/events?type=timeout'); expect(JSON.parse(server.requests[0].requestBody)).to.deep.equal(timeoutData); }); }); diff --git a/test/spec/modules/publinkIdSystem_spec.js b/test/spec/modules/publinkIdSystem_spec.js index cfb5f8ed135..4656afe1585 100644 --- a/test/spec/modules/publinkIdSystem_spec.js +++ b/test/spec/modules/publinkIdSystem_spec.js @@ -5,7 +5,7 @@ import sinon from 'sinon'; import {uspDataHandler} from '../../../src/adapterManager'; import {parseUrl} from '../../../src/utils'; -export const storage = getStorageManager(24); +export const storage = getStorageManager({gvlid: 24}); const TEST_COOKIE_VALUE = 'cookievalue'; describe('PublinkIdSystem', () => { describe('decode', () => { diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index c6496ee7fe1..c60b08ae972 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -7,7 +7,6 @@ import { addBidResponseHook, } from 'modules/currency.js'; -// using es6 "import * as events from 'src/events'" causes the events.getEvents stub not to work... let events = require('src/events'); let ajax = require('src/ajax'); let utils = require('src/utils'); @@ -95,6 +94,9 @@ const BID2 = Object.assign({}, BID, { 'hb_pb': '1.500', 'hb_size': '728x90', 'hb_source': 'server' + }, + meta: { + advertiserDomains: ['example.com'] } }); @@ -382,6 +384,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -651,6 +654,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -708,6 +712,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -754,6 +759,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -771,9 +777,10 @@ describe('pubmatic analytics adapter', function () { expect(data.kgpv).to.equal('*'); }); - it('Logger: regexPattern in bid.bidResponse', function() { + it('Logger: regexPattern in bid.bidResponse and url in adomain', function() { const BID2_COPY = utils.deepClone(BID2); BID2_COPY.regexPattern = '*'; + BID2_COPY.meta.advertiserDomains = ['https://www.example.com/abc/223'] events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -808,6 +815,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -859,6 +867,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -912,6 +921,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -1011,6 +1021,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 5e59fdd2e59..64e95460321 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -3457,7 +3457,7 @@ describe('PubMatic adapter', function () { 'h': 0, 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', 'ext': { - 'BidType': 1 + 'bidtype': 1 } }], 'ext': { @@ -3901,4 +3901,40 @@ describe('PubMatic adapter', function () { expect(data.imp[0]['video']['battr']).to.equal(undefined); }); }); + + describe('GroupM params', function() { + let sandbox, utilsMock, newBidRequests, newBidResponses; + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logInfo'); + newBidRequests = utils.deepClone(bidRequests) + newBidRequests[0].bidder = 'groupm'; + newBidResponses = utils.deepClone(bidResponses); + newBidResponses.body.seatbid[0].bid[0].ext.marketplace = 'groupm' + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }) + + it('Should log info when bidder is groupm and return', function () { + let request = spec.buildRequests(newBidRequests, {bidderCode: 'groupm', + auctionId: 'new-auction-id' + }); + sinon.assert.calledOnce(utils.logInfo); + expect(request).to.equal(undefined); + }); + + it('Should add bidder code & bidder as groupm for marketplace groupm response', function () { + let request = spec.buildRequests(newBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(newBidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].bidderCode).to.equal('groupm'); + expect(response[0].bidder).to.equal('groupm'); + }); + }); }); diff --git a/test/spec/modules/pubstackAnalyticsAdapter_spec.js b/test/spec/modules/pubstackAnalyticsAdapter_spec.js index e3db334c888..4df25f1665b 100644 --- a/test/spec/modules/pubstackAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubstackAnalyticsAdapter_spec.js @@ -1,7 +1,7 @@ import * as utils from 'src/utils.js'; import pubstackAnalytics from '../../../modules/pubstackAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager'; -import events from 'src/events'; +import * as events from 'src/events'; import constants from 'src/constants.json' describe('Pubstack Analytics Adapter', () => { diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 3d9be082be3..63364b867be 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -28,6 +28,7 @@ describe('pubxai analytics adapter', function() { }; let location = utils.getWindowLocation(); + let storage = window.top['sessionStorage']; let prebidEvent = { 'auctionInit': { @@ -514,6 +515,11 @@ describe('pubxai analytics adapter', function() { 'path': location.pathname, 'search': location.search }, + 'pmcDetail': { + 'bidDensity': storage.getItem('pbx:dpbid'), + 'maxBid': storage.getItem('pbx:mxbid'), + 'auctionId': storage.getItem('pbx:aucid') + } }; let expectedAfterBid = { @@ -577,6 +583,11 @@ describe('pubxai analytics adapter', function() { 'deviceOS': getOS(), 'browser': getBrowser() }, + 'pmcDetail': { + 'bidDensity': storage.getItem('pbx:dpbid'), + 'maxBid': storage.getItem('pbx:mxbid'), + 'auctionId': storage.getItem('pbx:aucid') + }, 'initOptions': initOptions }; @@ -625,6 +636,11 @@ describe('pubxai analytics adapter', function() { 'statusMessage': 'Bid available', 'timeToRespond': 267 }, + 'pageDetail': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search + }, 'deviceDetail': { 'platform': navigator.platform, 'deviceType': getDeviceType(), diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js index eefd7792a7c..04358fad52b 100644 --- a/test/spec/modules/readpeakBidAdapter_spec.js +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -4,9 +4,13 @@ import { config } from 'src/config.js'; import { parseUrl } from 'src/utils.js'; describe('ReadPeakAdapter', function() { - let bidRequest; - let serverResponse; - let serverRequest; + let baseBidRequest; + let bannerBidRequest; + let nativeBidRequest; + let nativeServerResponse; + let nativeServerRequest; + let bannerServerResponse; + let bannerServerRequest; let bidderRequest; beforeEach(function() { @@ -16,15 +20,8 @@ describe('ReadPeakAdapter', function() { } }; - bidRequest = { + baseBidRequest = { bidder: 'readpeak', - nativeParams: { - title: { required: true, len: 200 }, - image: { wmin: 100 }, - sponsoredBy: {}, - body: { required: false }, - cta: { required: false } - }, params: { bidfloor: 5.0, publisherId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', @@ -34,17 +31,46 @@ describe('ReadPeakAdapter', function() { bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', - transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + }; + + nativeBidRequest = { + ...baseBidRequest, + nativeParams: { + title: { required: true, len: 200 }, + image: { wmin: 100 }, + sponsoredBy: {}, + body: { required: false }, + cta: { required: false } + }, + mediaTypes: { + native: { + title: { required: true, len: 200 }, + image: { wmin: 100 }, + sponsoredBy: {}, + body: { required: false }, + cta: { required: false } + }, + } }; - serverResponse = { - id: bidRequest.bidderRequestId, + bannerBidRequest = { + ...baseBidRequest, + mediaTypes: { + banner: { + sizes: [[640, 320], [300, 600]], + } + }, + sizes: [[640, 320], [300, 600]], + } + nativeServerResponse = { + id: baseBidRequest.bidderRequestId, cur: 'USD', seatbid: [ { bid: [ { - id: 'bidRequest.bidId', - impid: bidRequest.bidId, + id: 'baseBidRequest.bidId', + impid: baseBidRequest.bidId, price: 0.12, cid: '12', crid: '123', @@ -91,7 +117,30 @@ describe('ReadPeakAdapter', function() { } ] }; - serverRequest = { + bannerServerResponse = { + id: baseBidRequest.bidderRequestId, + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: 'baseBidRequest.bidId', + impid: baseBidRequest.bidId, + price: 0.12, + cid: '12', + crid: '123', + adomain: ['readpeak.com'], + adm: '', + burl: 'https://localhost:8081/url/b?d=0O95O4326I528Ie4d39f94-533d-4577-a579-585fd4c02b0aI0I352e303232363639333139393939393939&c=USD&p=${AUCTION_PRICE}&bad=0-0-95O0O0OdO640360&gc=0', + nurl: 'https://localhost:8081/url/n?d=0O95O4326I528Ie4d39f94-533d-4577-a579-585fd4c02b0aI0I352e303232363639333139393939393939&gc=0', + w: 640, + h: 360, + } + ] + } + ] + }; + nativeServerRequest = { method: 'POST', url: 'http://localhost:60080/header/prebid', data: JSON.stringify({ @@ -101,7 +150,7 @@ describe('ReadPeakAdapter', function() { id: '2ffb201a808da7', native: { request: - '{"assets":[{"id":1,"required":1,"title":{"len":200}},{"id":2,"required":0,"data":{"type":1,"len":50}},{"id":3,"required":0,"img":{"type":3,"wmin":100,"hmin":150}}]}', + '{\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":70}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":150,\"hmin\":150}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}', ver: '1.1' }, bidfloor: 5, @@ -127,175 +176,362 @@ describe('ReadPeakAdapter', function() { isPrebid: true }) }; + bannerServerRequest = { + method: 'POST', + url: 'http://localhost:60080/header/prebid', + data: JSON.stringify({ + id: '178e34bad3658f', + imp: [ + { + id: '2ffb201a808da7', + bidfloor: 5, + bidfloorcur: 'USD', + tagId: 'test-tag-1', + banner: { + w: 640, + h: 360, + format: [ + { w: 640, h: 360 }, + { w: 320, h: 320 }, + ] + } + } + ], + site: { + publisher: { + id: '11bc5dd5-7421-4dd8-c926-40fa653bec76' + }, + id: '11bc5dd5-7421-4dd8-c926-40fa653bec77', + ref: '', + page: 'http://localhost', + domain: 'localhost' + }, + app: null, + device: { + ua: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/61.0.3163.100 Safari/537.36', + language: 'en-US' + }, + isPrebid: true + }) + }; }); - describe('spec.isBidRequestValid', function() { - it('should return true when the required params are passed', function() { - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); + describe('Native', function() { + describe('spec.isBidRequestValid', function() { + it('should return true when the required params are passed', function() { + expect(spec.isBidRequestValid(nativeBidRequest)).to.equal(true); + }); - it('should return false when the native params are missing', function() { - bidRequest.nativeParams = undefined; - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); + it('should return false when the "publisherId" param is missing', function() { + nativeBidRequest.params = { + bidfloor: 5.0 + }; + expect(spec.isBidRequestValid(nativeBidRequest)).to.equal(false); + }); + + it('should return false when no bid params are passed', function() { + nativeBidRequest.params = {}; + expect(spec.isBidRequestValid(nativeBidRequest)).to.equal(false); + }); - it('should return false when the "publisherId" param is missing', function() { - bidRequest.params = { - bidfloor: 5.0 - }; - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + it('should return false when a bid request is not passed', function() { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); + }); }); - it('should return false when no bid params are passed', function() { - bidRequest.params = {}; - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + describe('spec.buildRequests', function() { + it('should create a POST request for every bid', function() { + const request = spec.buildRequests([nativeBidRequest], bidderRequest); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT); + }); + + it('should attach request data', function() { + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }); + + const request = spec.buildRequests([nativeBidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal('$prebid.version$'); + expect(data.id).to.equal(nativeBidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(nativeBidRequest.params.bidfloor); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + expect(data.imp[0].tagId).to.equal('test-tag-1'); + expect(data.site.publisher.id).to.equal(nativeBidRequest.params.publisherId); + expect(data.site.id).to.equal(nativeBidRequest.params.siteId); + expect(data.site.page).to.equal(bidderRequest.refererInfo.referer); + expect(data.site.domain).to.equal(parseUrl(bidderRequest.refererInfo.referer).hostname); + expect(data.device).to.deep.contain({ + ua: navigator.userAgent, + language: navigator.language + }); + expect(data.cur).to.deep.equal(['EUR']); + expect(data.user).to.be.undefined; + expect(data.regs).to.be.undefined; + }); + + it('should get bid floor from module', function() { + const floorModuleData = { + currency: 'USD', + floor: 3.2, + } + nativeBidRequest.getFloor = function () { + return floorModuleData + } + const request = spec.buildRequests([nativeBidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal('$prebid.version$'); + expect(data.id).to.equal(nativeBidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(floorModuleData.floor); + expect(data.imp[0].bidfloorcur).to.equal(floorModuleData.currency); + }); + + it('should send gdpr data when gdpr does not apply', function() { + const gdprData = { + gdprConsent: { + gdprApplies: false, + consentString: undefined, + } + } + const request = spec.buildRequests([nativeBidRequest], {...bidderRequest, ...gdprData}); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: '' + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: false + } + }); + }); + + it('should send gdpr data when gdpr applies', function() { + const tcString = 'sometcstring'; + const gdprData = { + gdprConsent: { + gdprApplies: true, + consentString: tcString + } + } + const request = spec.buildRequests([nativeBidRequest], {...bidderRequest, ...gdprData}); + + const data = JSON.parse(request.data); + + expect(data.user).to.deep.equal({ + ext: { + consent: tcString + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: true + } + }); + }); }); - it('should return false when a bid request is not passed', function() { - expect(spec.isBidRequestValid()).to.equal(false); - expect(spec.isBidRequestValid({})).to.equal(false); + describe('spec.interpretResponse', function() { + it('should return no bids if the response is not valid', function() { + const bidResponse = spec.interpretResponse({ body: null }, nativeServerRequest); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid bid response', function() { + const bidResponse = spec.interpretResponse( + { body: nativeServerResponse }, + nativeServerRequest + )[0]; + expect(bidResponse).to.contain({ + requestId: nativeBidRequest.bidId, + cpm: nativeServerResponse.seatbid[0].bid[0].price, + creativeId: nativeServerResponse.seatbid[0].bid[0].crid, + ttl: 300, + netRevenue: true, + mediaType: 'native', + currency: nativeServerResponse.cur + }); + + expect(bidResponse.meta).to.deep.equal({ + advertiserDomains: ['readpeak.com'], + }) + expect(bidResponse.native.title).to.equal('Title'); + expect(bidResponse.native.body).to.equal('Description'); + expect(bidResponse.native.image).to.deep.equal({ + url: 'http://url.to/image', + width: 750, + height: 500 + }); + expect(bidResponse.native.clickUrl).to.equal( + 'http%3A%2F%2Furl.to%2Ftarget' + ); + expect(bidResponse.native.impressionTrackers).to.contain( + 'http://url.to/pixeltracker' + ); + }); }); }); - describe('spec.buildRequests', function() { - it('should create a POST request for every bid', function() { - const request = spec.buildRequests([bidRequest], bidderRequest); - expect(request.method).to.equal('POST'); - expect(request.url).to.equal(ENDPOINT); - }); + describe('Banner', function() { + describe('spec.isBidRequestValid', function() { + it('should return true when the required params are passed', function() { + expect(spec.isBidRequestValid(bannerBidRequest)).to.equal(true); + }); - it('should attach request data', function() { - config.setConfig({ - currency: { - adServerCurrency: 'EUR' - } + it('should return false when the "publisherId" param is missing', function() { + bannerBidRequest.params = { + bidfloor: 5.0 + }; + expect(spec.isBidRequestValid(bannerBidRequest)).to.equal(false); }); - const request = spec.buildRequests([bidRequest], bidderRequest); - - const data = JSON.parse(request.data); - - expect(data.source.ext.prebid).to.equal('$prebid.version$'); - expect(data.id).to.equal(bidRequest.bidderRequestId); - expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); - expect(data.imp[0].bidfloorcur).to.equal('USD'); - expect(data.imp[0].tagId).to.equal('test-tag-1'); - expect(data.site.publisher.id).to.equal(bidRequest.params.publisherId); - expect(data.site.id).to.equal(bidRequest.params.siteId); - expect(data.site.page).to.equal(bidderRequest.refererInfo.referer); - expect(data.site.domain).to.equal(parseUrl(bidderRequest.refererInfo.referer).hostname); - expect(data.device).to.deep.contain({ - ua: navigator.userAgent, - language: navigator.language + it('should return false when no bid params are passed', function() { + bannerBidRequest.params = {}; + expect(spec.isBidRequestValid(bannerBidRequest)).to.equal(false); }); - expect(data.cur).to.deep.equal(['EUR']); - expect(data.user).to.be.undefined; - expect(data.regs).to.be.undefined; }); - it('should get bid floor from module', function() { - const floorModuleData = { - currency: 'USD', - floor: 3.2, - } - bidRequest.getFloor = function () { - return floorModuleData - } - const request = spec.buildRequests([bidRequest], bidderRequest); + describe('spec.buildRequests', function() { + it('should create a POST request for every bid', function() { + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT); + }); - const data = JSON.parse(request.data); + it('should attach request data', function() { + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }); - expect(data.source.ext.prebid).to.equal('$prebid.version$'); - expect(data.id).to.equal(bidRequest.bidderRequestId); - expect(data.imp[0].bidfloor).to.equal(floorModuleData.floor); - expect(data.imp[0].bidfloorcur).to.equal(floorModuleData.currency); - }); + const request = spec.buildRequests([bannerBidRequest], bidderRequest); - it('should send gdpr data when gdpr does not apply', function() { - const gdprData = { - gdprConsent: { - gdprApplies: false, - consentString: undefined, - } - } - const request = spec.buildRequests([bidRequest], {...bidderRequest, ...gdprData}); + const data = JSON.parse(request.data); - const data = JSON.parse(request.data); + expect(data.source.ext.prebid).to.equal('$prebid.version$'); + expect(data.id).to.equal(bannerBidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(bannerBidRequest.params.bidfloor); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + expect(data.imp[0].tagId).to.equal('test-tag-1'); + expect(data.site.publisher.id).to.equal(bannerBidRequest.params.publisherId); + expect(data.site.id).to.equal(bannerBidRequest.params.siteId); + expect(data.site.page).to.equal(bidderRequest.refererInfo.referer); + expect(data.site.domain).to.equal(parseUrl(bidderRequest.refererInfo.referer).hostname); + expect(data.device).to.deep.contain({ + ua: navigator.userAgent, + language: navigator.language + }); + expect(data.cur).to.deep.equal(['EUR']); + expect(data.user).to.be.undefined; + expect(data.regs).to.be.undefined; + }); - expect(data.user).to.deep.equal({ - ext: { - consent: '' + it('should get bid floor from module', function() { + const floorModuleData = { + currency: 'USD', + floor: 3.2, } - }); - expect(data.regs).to.deep.equal({ - ext: { - gdpr: false + bannerBidRequest.getFloor = function () { + return floorModuleData } + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + + const data = JSON.parse(request.data); + + expect(data.source.ext.prebid).to.equal('$prebid.version$'); + expect(data.id).to.equal(bannerBidRequest.bidderRequestId); + expect(data.imp[0].bidfloor).to.equal(floorModuleData.floor); + expect(data.imp[0].bidfloorcur).to.equal(floorModuleData.currency); }); - }); - it('should send gdpr data when gdpr applies', function() { - const tcString = 'sometcstring'; - const gdprData = { - gdprConsent: { - gdprApplies: true, - consentString: tcString + it('should send gdpr data when gdpr does not apply', function() { + const gdprData = { + gdprConsent: { + gdprApplies: false, + consentString: undefined, + } } - } - const request = spec.buildRequests([bidRequest], {...bidderRequest, ...gdprData}); + const request = spec.buildRequests([bannerBidRequest], {...bidderRequest, ...gdprData}); - const data = JSON.parse(request.data); + const data = JSON.parse(request.data); - expect(data.user).to.deep.equal({ - ext: { - consent: tcString - } + expect(data.user).to.deep.equal({ + ext: { + consent: '' + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: false + } + }); }); - expect(data.regs).to.deep.equal({ - ext: { - gdpr: true + + it('should send gdpr data when gdpr applies', function() { + const tcString = 'sometcstring'; + const gdprData = { + gdprConsent: { + gdprApplies: true, + consentString: tcString + } } - }); - }); - }); + const request = spec.buildRequests([bannerBidRequest], {...bidderRequest, ...gdprData}); + + const data = JSON.parse(request.data); - describe('spec.interpretResponse', function() { - it('should return no bids if the response is not valid', function() { - const bidResponse = spec.interpretResponse({ body: null }, serverRequest); - expect(bidResponse.length).to.equal(0); + expect(data.user).to.deep.equal({ + ext: { + consent: tcString + } + }); + expect(data.regs).to.deep.equal({ + ext: { + gdpr: true + } + }); + }); }); - it('should return a valid bid response', function() { - const bidResponse = spec.interpretResponse( - { body: serverResponse }, - serverRequest - )[0]; - expect(bidResponse).to.contain({ - requestId: bidRequest.bidId, - cpm: serverResponse.seatbid[0].bid[0].price, - creativeId: serverResponse.seatbid[0].bid[0].crid, - ttl: 300, - netRevenue: true, - mediaType: 'native', - currency: serverResponse.cur + describe('spec.interpretResponse', function() { + it('should return no bids if the response is not valid', function() { + const bidResponse = spec.interpretResponse({ body: null }, bannerServerRequest); + expect(bidResponse.length).to.equal(0); }); - expect(bidResponse.meta).to.deep.equal({ - advertiserDomains: ['readpeak.com'], - }) - expect(bidResponse.native.title).to.equal('Title'); - expect(bidResponse.native.body).to.equal('Description'); - expect(bidResponse.native.image).to.deep.equal({ - url: 'http://url.to/image', - width: 750, - height: 500 + it('should return a valid bid response', function() { + const bidResponse = spec.interpretResponse( + { body: bannerServerResponse }, + bannerServerRequest + )[0]; + expect(bidResponse).to.contain({ + requestId: bannerBidRequest.bidId, + cpm: bannerServerResponse.seatbid[0].bid[0].price, + creativeId: bannerServerResponse.seatbid[0].bid[0].crid, + ttl: 300, + netRevenue: true, + mediaType: 'banner', + currency: bannerServerResponse.cur, + ad: bannerServerResponse.seatbid[0].bid[0].adm, + width: bannerServerResponse.seatbid[0].bid[0].w, + height: bannerServerResponse.seatbid[0].bid[0].h, + }); + expect(bidResponse.meta).to.deep.equal({ + advertiserDomains: ['readpeak.com'], + }); }); - expect(bidResponse.native.clickUrl).to.equal( - 'http%3A%2F%2Furl.to%2Ftarget' - ); - expect(bidResponse.native.impressionTrackers).to.contain( - 'http://url.to/pixeltracker' - ); }); }); }); diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index b84aef15feb..daeeb9bc47c 100644 --- a/test/spec/modules/realTimeDataModule_spec.js +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -1,6 +1,9 @@ import * as rtdModule from 'modules/rtdModule/index.js'; -import { config } from 'src/config.js'; +import {config} from 'src/config.js'; import * as sinon from 'sinon'; +import {default as CONSTANTS} from '../../../src/constants.json'; +import * as events from '../../../src/events.js'; +import 'src/prebid.js'; const getBidRequestDataSpy = sinon.spy(); @@ -58,33 +61,136 @@ const conf = { }; describe('Real time module', function () { - before(function () { - rtdModule.attachRealTimeDataProvider(validSM); - rtdModule.attachRealTimeDataProvider(invalidSM); - rtdModule.attachRealTimeDataProvider(failureSM); - rtdModule.attachRealTimeDataProvider(nonConfSM); - rtdModule.attachRealTimeDataProvider(validSMWait); - }); + let eventHandlers; + let sandbox; - after(function () { - config.resetConfig(); - }); + function mockEmitEvent(event, ...args) { + (eventHandlers[event] || []).forEach((h) => h(...args)); + } - beforeEach(function () { - config.setConfig(conf); + before(() => { + eventHandlers = {}; + sandbox = sinon.sandbox.create(); + sandbox.stub(events, 'on').callsFake((event, handler) => { + if (!eventHandlers.hasOwnProperty(event)) { + eventHandlers[event] = []; + } + eventHandlers[event].push(handler); + }); }); - it('should use only valid modules', function () { - rtdModule.init(config); - expect(rtdModule.subModules).to.eql([validSMWait, validSM]); + after(() => { + sandbox.restore(); }); - it('should be able to modify bid request', function (done) { - rtdModule.setBidRequestsData(() => { - assert(getBidRequestDataSpy.calledTwice); - assert(getBidRequestDataSpy.calledWith({bidRequest: {}})); + describe('', () => { + const PROVIDERS = [validSM, invalidSM, failureSM, nonConfSM, validSMWait]; + let _detachers; + + beforeEach(function () { + _detachers = PROVIDERS.map(rtdModule.attachRealTimeDataProvider); + rtdModule.init(config); + config.setConfig(conf); + }); + + afterEach(function () { + _detachers.forEach((f) => f()); + config.resetConfig(); + }); + + it('should use only valid modules', function () { + expect(rtdModule.subModules).to.eql([validSMWait, validSM]); + }); + + it('should be able to modify bid request', function (done) { + rtdModule.setBidRequestsData(() => { + assert(getBidRequestDataSpy.calledTwice); + assert(getBidRequestDataSpy.calledWith({bidRequest: {}})); + done(); + }, {bidRequest: {}}) + }); + + it('sould place targeting on adUnits', function (done) { + const auction = { + adUnitCodes: ['ad1', 'ad2'], + adUnits: [ + { + code: 'ad1' + }, + { + code: 'ad2', + adserverTargeting: {preKey: 'preValue'} + } + ] + }; + + const expectedAdUnits = [ + { + code: 'ad1', + adserverTargeting: {key: 'validSMWait'} + }, + { + code: 'ad2', + adserverTargeting: { + preKey: 'preValue', + key: 'validSM' + } + } + ]; + + const adUnits = rtdModule.getAdUnitTargeting(auction); + assert.deepEqual(expectedAdUnits, adUnits) done(); - }, {bidRequest: {}}) + }); + + describe('setBidRequestData', () => { + let withWait, withoutWait; + + function runSetBidRequestData() { + return new Promise((resolve) => { + rtdModule.setBidRequestsData(resolve, {bidRequest: {}}); + }); + } + + beforeEach(() => { + withWait = { + submod: validSMWait, + cbTime: 0, + cbRan: false + }; + withoutWait = { + submod: validSM, + cbTime: 0, + cbRan: false + }; + + [withWait, withoutWait].forEach((c) => { + c.submod.getBidRequestData = sinon.stub().callsFake((_, cb) => { + setTimeout(() => { + c.cbRan = true; + cb(); + }, c.cbTime); + }); + }); + }); + + it('should allow non-priority submodules to run synchronously', () => { + withWait.cbTime = withoutWait.cbTime = 0; + return runSetBidRequestData().then(() => { + expect(withWait.cbRan).to.be.true; + expect(withoutWait.cbRan).to.be.true; + }) + }); + + it('should not wait for non-priority submodules if priority ones complete first', () => { + withWait.cbTime = 10; + withoutWait.cbTime = 100; + return runSetBidRequestData().then(() => { + expect(withWait.cbRan).to.be.true; + expect(withoutWait.cbRan).to.be.false; + }); + }); + }); }); it('deep merge object', function () { @@ -125,36 +231,78 @@ describe('Real time module', function () { assert.deepEqual(expected, merged); }); - it('sould place targeting on adUnits', function (done) { - const auction = { - adUnitCodes: ['ad1', 'ad2'], - adUnits: [ - { - code: 'ad1' - }, - { - code: 'ad2', - adserverTargeting: {preKey: 'preValue'} - } - ] + describe('event', () => { + const EVENTS = { + [CONSTANTS.EVENTS.AUCTION_INIT]: 'onAuctionInitEvent', + [CONSTANTS.EVENTS.AUCTION_END]: 'onAuctionEndEvent', + [CONSTANTS.EVENTS.BID_RESPONSE]: 'onBidResponseEvent', + [CONSTANTS.EVENTS.BID_REQUESTED]: 'onBidRequestEvent' + } + const conf = { + 'realTimeData': { + dataProviders: [ + { + 'name': 'tp1', + }, + { + 'name': 'tp2', + } + ] + } }; + let providers; + let _detachers; - const expectedAdUnits = [ - { - code: 'ad1', - adserverTargeting: {key: 'validSMWait'} - }, - { - code: 'ad2', - adserverTargeting: { - preKey: 'preValue', - key: 'validSM' - } + function eventHandlingProvider(name) { + const provider = { + name: name, + init: () => true, } - ]; + Object.values(EVENTS).forEach((ev) => provider[ev] = sinon.spy()); + return provider; + } + + beforeEach(() => { + providers = [eventHandlingProvider('tp1'), eventHandlingProvider('tp2')]; + _detachers = providers.map(rtdModule.attachRealTimeDataProvider); + rtdModule.init(config); + config.setConfig(conf); + }); - const adUnits = rtdModule.getAdUnitTargeting(auction); - assert.deepEqual(expectedAdUnits, adUnits) - done(); - }) + afterEach(() => { + _detachers.forEach((d) => d()) + config.resetConfig(); + }); + + it('should set targeting for auctionEnd', () => { + providers.forEach(p => p.getTargetingData = sinon.spy()); + const auction = { + adUnitCodes: ['a1'], + adUnits: [{code: 'a1'}] + }; + mockEmitEvent(CONSTANTS.EVENTS.AUCTION_END, auction); + providers.forEach(p => { + expect(p.getTargetingData.calledWith(auction.adUnitCodes)).to.be.true; + }); + }); + + Object.entries(EVENTS).forEach(([event, hook]) => { + it(`'${event}' should be propagated to providers through '${hook}'`, () => { + const eventArg = {}; + mockEmitEvent(event, eventArg); + providers.forEach((provider) => { + const providerConf = conf.realTimeData.dataProviders.find((cfg) => cfg.name === provider.name); + expect(provider[hook].called).to.be.true; + expect(provider[hook].args).to.have.length(1); + expect(provider[hook].args[0]).to.include.members([eventArg, providerConf]) + }) + }); + + it(`${event} should not fail to propagate elsewhere if a provider throws in its event handler`, () => { + providers[0][hook] = function () { throw new Error() }; + mockEmitEvent(event); + expect(providers[1][hook].called).to.be.true; + }); + }); + }); }); diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 868b1856c34..f0d381ee3ed 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -57,23 +57,34 @@ describe('RelaidoAdapter', function () { serverResponse = { body: { status: 'ok', - price: 500, - model: 'vcpm', - currency: 'JPY', - creativeId: 1000, - uuid: relaido_uuid, - vast: '', + ads: [{ + placementId: 100000, + width: 640, + height: 360, + bidId: '2ed93003f7bb99', + price: 500, + model: 'vcpm', + currency: 'JPY', + creativeId: 1000, + vast: '', + syncUrl: 'https://relaido/sync.html', + adomain: ['relaido.co.jp', 'www.cmertv.co.jp'], + mediaType: 'video' + }], playerUrl: 'https://relaido/player.js', - syncUrl: 'https://relaido/sync.html', - adomain: ['relaido.co.jp', 'www.cmertv.co.jp'] + syncUrl: 'https://api-dev.ulizaex.com/tr/v1/prebid/sync.html', + uuid: relaido_uuid, } }; serverRequest = { - method: 'GET', - bidId: bidRequest.bidId, - width: bidRequest.mediaTypes.video.playerSize[0][0], - height: bidRequest.mediaTypes.video.playerSize[0][1], - mediaType: 'video', + method: 'POST', + data: { + bids: [{ + bidId: bidRequest.bidId, + width: bidRequest.mediaTypes.video.playerSize[0][0], + height: bidRequest.mediaTypes.video.playerSize[0][1], + mediaType: 'video'}] + } }; }); @@ -195,28 +206,23 @@ describe('RelaidoAdapter', function () { describe('spec.buildRequests', function () { it('should build bid requests by video', function () { const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - expect(request.method).to.equal('GET'); - expect(request.url).to.equal('https://api.relaido.jp/bid/v1/prebid/100000'); - expect(request.bidId).to.equal(bidRequest.bidId); - expect(request.width).to.equal(bidRequest.mediaTypes.video.playerSize[0][0]); - expect(request.height).to.equal(bidRequest.mediaTypes.video.playerSize[0][1]); - expect(request.mediaType).to.equal('video'); - expect(request.data.ref).to.equal(bidderRequest.refererInfo.referer); - expect(request.data.timeout_ms).to.equal(bidderRequest.timeout); - expect(request.data.ad_unit_code).to.equal(bidRequest.adUnitCode); - expect(request.data.auction_id).to.equal(bidRequest.auctionId); - expect(request.data.bidder).to.equal(bidRequest.bidder); - expect(request.data.bidder_request_id).to.equal(bidRequest.bidderRequestId); - expect(request.data.bid_requests_count).to.equal(bidRequest.bidRequestsCount); - expect(request.data.bid_id).to.equal(bidRequest.bidId); - expect(request.data.transaction_id).to.equal(bidRequest.transactionId); - expect(request.data.media_type).to.equal('video'); - expect(request.data.uuid).to.equal(relaido_uuid); - expect(request.data.width).to.equal(bidRequest.mediaTypes.video.playerSize[0][0]); - expect(request.data.height).to.equal(bidRequest.mediaTypes.video.playerSize[0][1]); - expect(request.data.pv).to.equal('$prebid.version$'); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const request = data.bids[0]; + expect(bidRequests.method).to.equal('POST'); + expect(bidRequests.url).to.equal('https://api.relaido.jp/bid/v1/sprebid'); + expect(data.ref).to.equal(bidderRequest.refererInfo.referer); + expect(data.timeout_ms).to.equal(bidderRequest.timeout); + expect(request.ad_unit_code).to.equal(bidRequest.adUnitCode); + expect(request.auction_id).to.equal(bidRequest.auctionId); + expect(data.bidder).to.equal(bidRequest.bidder); + expect(request.bidder_request_id).to.equal(bidRequest.bidderRequestId); + expect(data.bid_requests_count).to.equal(bidRequest.bidRequestsCount); + expect(request.bid_id).to.equal(bidRequest.bidId); + expect(request.transaction_id).to.equal(bidRequest.transactionId); + expect(request.media_type).to.equal('video'); + expect(data.uuid).to.equal(relaido_uuid); + expect(data.pv).to.equal('$prebid.version$'); }); it('should build bid requests by banner', function () { @@ -236,9 +242,10 @@ describe('RelaidoAdapter', function () { } }; const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - expect(request.mediaType).to.equal('banner'); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const request = data.bids[0]; + expect(request.media_type).to.equal('banner'); }); it('should take 1x1 size', function () { @@ -258,17 +265,18 @@ describe('RelaidoAdapter', function () { } }; const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const request = data.bids[0]; expect(request.width).to.equal(1); }); it('The referrer should be the last', function () { const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - const keys = Object.keys(request.data); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const keys = Object.keys(data); expect(keys[0]).to.equal('version'); expect(keys[keys.length - 1]).to.equal('ref'); }); @@ -277,9 +285,9 @@ describe('RelaidoAdapter', function () { bidRequest.userId = {} bidRequest.userId.imuid = 'i.tjHcK_7fTcqnbrS_YA2vaw'; const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - expect(request.data.imuid).to.equal('i.tjHcK_7fTcqnbrS_YA2vaw'); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + expect(data.imuid).to.equal('i.tjHcK_7fTcqnbrS_YA2vaw'); }); }); @@ -288,35 +296,54 @@ describe('RelaidoAdapter', function () { const bidResponses = spec.interpretResponse(serverResponse, serverRequest); expect(bidResponses).to.have.lengthOf(1); const response = bidResponses[0]; - expect(response.requestId).to.equal(serverRequest.bidId); - expect(response.width).to.equal(serverRequest.width); - expect(response.height).to.equal(serverRequest.height); - expect(response.cpm).to.equal(serverResponse.body.price); - expect(response.currency).to.equal(serverResponse.body.currency); - expect(response.creativeId).to.equal(serverResponse.body.creativeId); - expect(response.vastXml).to.equal(serverResponse.body.vast); - expect(response.meta.advertiserDomains).to.equal(serverResponse.body.adomain); + expect(response.requestId).to.equal(serverRequest.data.bids[0].bidId); + expect(response.width).to.equal(serverRequest.data.bids[0].width); + expect(response.height).to.equal(serverRequest.data.bids[0].height); + expect(response.cpm).to.equal(serverResponse.body.ads[0].price); + expect(response.currency).to.equal(serverResponse.body.ads[0].currency); + expect(response.creativeId).to.equal(serverResponse.body.ads[0].creativeId); + expect(response.vastXml).to.equal(serverResponse.body.ads[0].vast); + expect(response.playerUrl).to.equal(serverResponse.body.playerUrl); + expect(response.meta.advertiserDomains).to.equal(serverResponse.body.ads[0].adomain); expect(response.meta.mediaType).to.equal(VIDEO); expect(response.ad).to.be.undefined; }); it('should build bid response by banner', function () { - serverRequest.mediaType = 'banner'; + serverResponse.body.ads[0].mediaType = 'banner'; const bidResponses = spec.interpretResponse(serverResponse, serverRequest); expect(bidResponses).to.have.lengthOf(1); const response = bidResponses[0]; - expect(response.requestId).to.equal(serverRequest.bidId); - expect(response.width).to.equal(serverRequest.width); - expect(response.height).to.equal(serverRequest.height); - expect(response.cpm).to.equal(serverResponse.body.price); - expect(response.currency).to.equal(serverResponse.body.currency); - expect(response.creativeId).to.equal(serverResponse.body.creativeId); + expect(response.requestId).to.equal(serverRequest.data.bids[0].bidId); + expect(response.width).to.equal(serverRequest.data.bids[0].width); + expect(response.height).to.equal(serverRequest.data.bids[0].height); + expect(response.cpm).to.equal(serverResponse.body.ads[0].price); + expect(response.currency).to.equal(serverResponse.body.ads[0].currency); + expect(response.creativeId).to.equal(serverResponse.body.ads[0].creativeId); expect(response.vastXml).to.be.undefined; + expect(response.playerUrl).to.equal(serverResponse.body.playerUrl); expect(response.ad).to.include(`
`); expect(response.ad).to.include(``); expect(response.ad).to.include(`window.RelaidoPlayer.renderAd`); }); + it('should build bid response by video and playerUrl in ads', function () { + serverResponse.body.ads[0].playerUrl = 'https://relaido/player-customized.js'; + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + expect(bidResponses).to.have.lengthOf(1); + const response = bidResponses[0]; + expect(response.playerUrl).to.equal(serverResponse.body.ads[0].playerUrl); + }); + + it('should build bid response by banner and playerUrl in ads', function () { + serverResponse.body.ads[0].playerUrl = 'https://relaido/player-customized.js'; + serverResponse.body.ads[0].mediaType = 'banner'; + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + expect(bidResponses).to.have.lengthOf(1); + const response = bidResponses[0]; + expect(response.playerUrl).to.equal(serverResponse.body.ads[0].playerUrl); + }); + it('should not build bid response', function () { serverResponse = {}; const bidResponses = spec.interpretResponse(serverResponse, serverRequest); @@ -370,8 +397,8 @@ describe('RelaidoAdapter', function () { it('Should create nurl pixel if bid nurl', function () { let bid = { bidder: bidRequest.bidder, - creativeId: serverResponse.body.creativeId, - cpm: serverResponse.body.price, + creativeId: serverResponse.body.ads[0].creativeId, + cpm: serverResponse.body.ads[0].price, params: [bidRequest.params], auctionId: bidRequest.auctionId, requestId: bidRequest.bidId, diff --git a/test/spec/modules/relevantAnalyticsAdapter_spec.js b/test/spec/modules/relevantAnalyticsAdapter_spec.js index 3e31db2d7dc..5c818fe01d4 100644 --- a/test/spec/modules/relevantAnalyticsAdapter_spec.js +++ b/test/spec/modules/relevantAnalyticsAdapter_spec.js @@ -1,6 +1,6 @@ import relevantAnalytics from '../../../modules/relevantAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager'; -import events from 'src/events'; +import * as events from 'src/events'; import constants from 'src/constants.json' import { expect } from 'chai'; diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 00ae55823b0..9e9366072be 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -791,6 +791,46 @@ describe('Richaudience adapter tests', function () { })).to.equal(true); }); + it('should pass schain', function() { + let schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + DEFAULT_PARAMS_NEW_SIZES[0].schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('schain').to.deep.equal(schain); + }) + describe('userSync', function () { it('Verifies user syncs iframe include', function () { config.setConfig({ @@ -905,7 +945,7 @@ describe('Richaudience adapter tests', function () { }, [], { consentString: null, referer: 'http://domain.com', - gdprApplies: true + gdprApplies: false }) expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('image'); @@ -942,7 +982,7 @@ describe('Richaudience adapter tests', function () { }, [], { consentString: null, referer: 'http://domain.com', - gdprApplies: true + gdprApplies: false }) expect(syncs).to.have.lengthOf(0); }); diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 0966718a18d..596c4d0f58c 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -2,12 +2,13 @@ import { expect } from 'chai'; import { spec } from 'modules/riseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { VIDEO } from '../../../src/mediaTypes.js'; -import { deepClone } from 'src/utils.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from 'src/utils.js'; -const ENDPOINT = 'https://hb.yellowblue.io/hb'; -const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-test'; +const ENDPOINT = 'https://hb.yellowblue.io/hb-multi'; +const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-multi-test'; const TTL = 360; +/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ describe('riseAdapter', function () { const adapter = newBidder(spec); @@ -54,6 +55,29 @@ describe('riseAdapter', function () { 'bidId': '299ffc8cca0b87', 'bidderRequestId': '1144f487e563f9', 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream' + } + }, + 'vastXml': '"..."' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + } + }, + 'ad': '""' } ]; @@ -77,62 +101,41 @@ describe('riseAdapter', function () { } const placementId = '12345678'; - it('sends the placementId as a query param', function () { + it('sends the placementId to ENDPOINT via POST', function () { bidRequests[0].params.placementId = placementId; - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data.placement_id).to.equal(placementId); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].placementId).to.equal(placementId); }); - const customSessionId = '12345678'; - - it('sends bid request to ENDPOINT via GET', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('GET'); - } - }); - - it('sends the is_wrapper query param', function () { - bidRequests[0].params.isWrapper = true; - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data.is_wrapper).to.equal(true); - } + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); }); - it('sends the custom session id as a query param', function () { - bidRequests[0].params.sessionId = customSessionId; - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data.session_id).to.equal(customSessionId); - } + it('sends bid request to TEST ENDPOINT via POST', function () { + const request = spec.buildRequests(testModeBidRequests, bidderRequest); + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('POST'); }); - it('sends bid request to test ENDPOINT via GET', function () { - const requests = spec.buildRequests(testModeBidRequests, bidderRequest); - for (const request of requests) { - expect(request.url).to.equal(TEST_ENDPOINT); - expect(request.method).to.equal('GET'); - } + it('should send the correct bid Id', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); }); - it('should send the correct bid Id', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data.bid_id).to.equal('299ffc8cca0b87'); - } + it('should send the correct sizes array', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sizes).to.be.an('array'); + expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) + expect(request.data.bids[1].sizes).to.be.an('array'); + expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) }); - it('should send the correct width and height', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('width', 640); - expect(request.data).to.have.property('height', 480); - } + it('should send the correct media type', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].mediaType).to.equal(VIDEO) + expect(request.data.bids[1].mediaType).to.equal(BANNER) }); it('should respect syncEnabled option', function() { @@ -147,11 +150,9 @@ describe('riseAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('cs_method'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); }); it('should respect "iframe" filter settings', function () { @@ -166,11 +167,9 @@ describe('riseAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('cs_method', 'iframe'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); }); it('should respect "all" filter settings', function () { @@ -185,24 +184,21 @@ describe('riseAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('cs_method', 'iframe'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); }); it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.resetConfig(); config.setConfig({ userSync: { - syncEnabled: true + syncEnabled: true, } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('cs_method', 'pixel'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'pixel'); }); it('should respect total exclusion', function() { @@ -221,48 +217,38 @@ describe('riseAdapter', function () { } } }); - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('cs_method'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); }); it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('us_privacy', '1YNN'); - } + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('us_privacy', '1YNN'); }); it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('us_privacy'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('us_privacy'); }); it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.not.have.property('gdpr'); - expect(request.data).to.not.have.property('gdpr_consent'); - } + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gdpr'); + expect(request.data.params).to.not.have.property('gdpr_consent'); }); it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); - const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('gdpr', true); - expect(request.data).to.have.property('gdpr_consent', 'test-consent-string'); - } + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gdpr', true); + expect(request.data.params).to.have.property('gdpr_consent', 'test-consent-string'); }); it('should have schain param if it is available in the bidRequest', () => { @@ -272,15 +258,13 @@ describe('riseAdapter', function () { nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], }; bidRequests[0].schain = schain; - const requests = spec.buildRequests(bidRequests, bidderRequest); - for (const request of requests) { - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,,,,'); - } + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); }); - it('should set floor_price to getFloor.floor value if it is greater than params.floorPrice', function() { - const bid = deepClone(bidRequests[0]); + it('should set flooPrice to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = utils.deepClone(bidRequests[0]); bid.getFloor = () => { return { currency: 'USD', @@ -288,13 +272,13 @@ describe('riseAdapter', function () { } } bid.params.floorPrice = 0.64; - const request = spec.buildRequests([bid], bidderRequest)[0]; - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('floor_price', 3.32); + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 3.32); }); - it('should set floor_price to params.floorPrice value if it is greater than getFloor.floor', function() { - const bid = deepClone(bidRequests[0]); + it('should set floorPrice to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = utils.deepClone(bidRequests[0]); bid.getFloor = () => { return { currency: 'USD', @@ -302,61 +286,109 @@ describe('riseAdapter', function () { } } bid.params.floorPrice = 1.5; - const request = spec.buildRequests([bid], bidderRequest)[0]; - expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('floor_price', 1.5); + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); }); describe('interpretResponse', function () { const response = { + params: { + currency: 'USD', + netRevenue: true, + }, + bids: [{ + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: VIDEO + }, + { + cpm: 12.5, + ad: '""', + width: 300, + height: 250, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: BANNER + }] + }; + + const expectedVideoResponse = { + requestId: '21e12606d47ba7', cpm: 12.5, - vastXml: '', + currency: 'USD', width: 640, height: 480, - requestId: '21e12606d47ba7', + ttl: TTL, + creativeId: '21e12606d47ba7', netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: VIDEO, + meta: { + mediaType: VIDEO, + advertiserDomains: ['abc.com'] + }, + vastXml: '', + }; + + const expectedBannerResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, currency: 'USD', - adomain: ['abc.com'] + width: 640, + height: 480, + ttl: TTL, + creativeId: '21e12606d47ba7', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: BANNER, + meta: { + mediaType: BANNER, + advertiserDomains: ['abc.com'] + }, + ad: '""' }; it('should get correct bid response', function () { - let expectedResponse = [ - { - requestId: '21e12606d47ba7', - cpm: 12.5, - width: 640, - height: 480, - creativeId: '21e12606d47ba7', - currency: 'USD', - netRevenue: true, - ttl: TTL, - vastXml: '', - mediaType: VIDEO, - meta: { - advertiserDomains: ['abc.com'] - } - } - ]; const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + }); + + it('video type should have vastXml key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[0].vastXml).to.equal(expectedVideoResponse.vastXml) + }); + + it('banner type should have ad key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); }) describe('getUserSyncs', function() { const imageSyncResponse = { body: { - userSyncPixels: [ - 'https://image-sync-url.test/1', - 'https://image-sync-url.test/2', - 'https://image-sync-url.test/3' - ] + params: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } } }; const iframeSyncResponse = { body: { - userSyncURL: 'https://iframe-sync-url.test' + params: { + userSyncURL: 'https://iframe-sync-url.test' + } } }; @@ -420,4 +452,28 @@ describe('riseAdapter', function () { expect(syncs).to.deep.equal([]); }); }) + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should trigger pixel if bid nurl', function() { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'nurl': 'http://example.com/win/1234', + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + spec.onBidWon(bid); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) }); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index d6bee26d73b..f4bcb48474a 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -68,6 +68,7 @@ describe('RTBHouseAdapter', () => { 'params': { 'publisherId': 'PREBID_TEST', 'region': 'prebid-eu', + 'channel': 'Partner_Site - news', 'test': 1 }, 'adUnitCode': 'adunit-code', @@ -101,6 +102,25 @@ describe('RTBHouseAdapter', () => { expect(JSON.parse(builtTestRequest).test).to.equal(1); }); + it('should build channel param into request.site', () => { + let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(JSON.parse(builtTestRequest).site.channel).to.equal('Partner_Site - news'); + }) + + it('should not build channel param into request.site if no value is passed', () => { + let bidRequest = Object.assign([], bidRequests); + bidRequest[0].params.channel = undefined; + let builtTestRequest = spec.buildRequests(bidRequest, bidderRequest).data; + expect(JSON.parse(builtTestRequest).site.channel).to.be.undefined + }) + + it('should cap the request.site.channel length to 50', () => { + let bidRequest = Object.assign([], bidRequests); + bidRequest[0].params.channel = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent scelerisque ipsum eu purus lobortis iaculis.'; + let builtTestRequest = spec.buildRequests(bidRequest, bidderRequest).data; + expect(JSON.parse(builtTestRequest).site.channel.length).to.equal(50) + }) + it('should build valid OpenRTB banner object', () => { const request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); const imp = request.imp[0]; diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 798e98bb97d..f365d416876 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -13,6 +13,7 @@ import { setConfig, addBidResponseHook, } from 'modules/currency.js'; +import truncate from 'lodash.truncate'; let Ajv = require('ajv'); let schema = require('./rubiconAnalyticsSchema.json'); let ajv = new Ajv({ @@ -26,7 +27,6 @@ function validate(message) { expect(validator.errors).to.deep.equal(null); } -// using es6 "import * as events from 'src/events.js'" causes the events.getEvents stub not to work... let events = require('src/events.js'); let utils = require('src/utils.js'); @@ -39,7 +39,8 @@ const { BIDDER_DONE, BID_WON, BID_TIMEOUT, - SET_TARGETING + SET_TARGETING, + BILLABLE_EVENT } } = CONSTANTS; @@ -246,7 +247,7 @@ const MOCK = { } }, BID_REQUESTED: { - 'bidder': 'rubicon', + 'bidderCode': 'rubicon', 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', 'bidderRequestId': '1be65d7958826a', 'bids': [ @@ -384,6 +385,10 @@ const ANALYTICS_MESSAGE = { 'referrerHostname': 'www.test.com', 'auctions': [ { + + 'auctionEnd': 1519767013781, + 'auctionStart': 1519767010567, + 'bidderOrder': ['rubicon'], 'requestId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', 'clientTimeoutMillis': 3000, 'serverTimeoutMillis': 1000, @@ -573,21 +578,21 @@ const ANALYTICS_MESSAGE = { } }; -function performStandardAuction(gptEvents) { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); +function performStandardAuction(gptEvents, auctionId = MOCK.AUCTION_INIT.auctionId) { + events.emit(AUCTION_INIT, { ...MOCK.AUCTION_INIT, auctionId }); + events.emit(BID_REQUESTED, { ...MOCK.BID_REQUESTED, auctionId }); + events.emit(BID_RESPONSE, { ...MOCK.BID_RESPONSE[0], auctionId }); + events.emit(BID_RESPONSE, { ...MOCK.BID_RESPONSE[1], 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)); } - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); + events.emit(SET_TARGETING, { ...MOCK.SET_TARGETING, auctionId }); + events.emit(BID_WON, { ...MOCK.BID_WON[0], auctionId }); + events.emit(BID_WON, { ...MOCK.BID_WON[1], auctionId }); } describe('rubicon analytics adapter', function () { @@ -673,6 +678,11 @@ describe('rubicon analytics adapter', function () { }); expect(rubiConf).to.deep.equal({ analyticsEventDelay: 0, + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + }, pvid: '12345678', wrapperName: '1001_general', int_type: 'dmpbjs', @@ -692,6 +702,11 @@ describe('rubicon analytics adapter', function () { }); expect(rubiConf).to.deep.equal({ analyticsEventDelay: 0, + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + }, pvid: '12345678', wrapperName: '1001_general', int_type: 'dmpbjs', @@ -713,6 +728,11 @@ describe('rubicon analytics adapter', function () { }); expect(rubiConf).to.deep.equal({ analyticsEventDelay: 0, + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + }, pvid: '12345678', wrapperName: '1001_general', int_type: 'dmpbjs', @@ -853,6 +873,32 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(ANALYTICS_MESSAGE); }); + it('should pass along bidderOrder correctly', function () { + const appnexusBid = utils.deepClone(MOCK.BID_REQUESTED); + appnexusBid.bidderCode = 'appnexus'; + const pubmaticBid = utils.deepClone(MOCK.BID_REQUESTED); + pubmaticBid.bidderCode = 'pubmatic'; + const indexBid = utils.deepClone(MOCK.BID_REQUESTED); + indexBid.bidderCode = 'ix'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, pubmaticBid); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_REQUESTED, indexBid); + events.emit(BID_REQUESTED, appnexusBid); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + clock.tick(SEND_TIMEOUT + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].bidderOrder).to.deep.equal([ + 'pubmatic', + 'rubicon', + 'ix', + 'appnexus' + ]); + }); + it('should pass along user ids', function () { let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); auctionInit.bidderRequests[0].bids[0].userId = { @@ -987,6 +1033,36 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; }); + it('should NOT pass along adomians with other edge cases', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // should filter out non string values and pass valid ones + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + bidResponse1.meta = { + advertiserDomains: [123, 'prebid.org', false, true, [], 'magnite.com', {}] + } + + // array of arrays (as seen when passed by kargo bid adapter) + let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse2.meta = { + advertiserDomains: [['prebid.org']] + } + + events.emit(BID_RESPONSE, bidResponse1); + events.emit(BID_RESPONSE, bidResponse2); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com']); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; + }); + it('should not pass empty adServerTargeting values', function () { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -1703,6 +1779,50 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); + it('should only mark the first gam data not all matches', function () { + config.setConfig({ + rubicon: { + waitForGamSlots: true + } + }); + performStandardAuction(); + performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1], '32d332de-123a-32dg-2345-cefef3423324'); + + // tick the clock and both should fire + clock.tick(3000); + + expect(server.requests.length).to.equal(2); + + // first one should have GAM data + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + // trigger should be gam since all adunits had associated gam render + expect(message.trigger).to.be.equal('gam'); + expect(message.auctions[0].adUnits[0].gam).to.deep.equal({ + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }); + expect(message.auctions[0].adUnits[1].gam).to.deep.equal({ + advertiserId: 4444, + creativeId: 5555, + lineItemId: 6666, + adSlot: '/19968336/header-bid-tag1' + }); + + // second one should NOT have gam data + request = server.requests[1]; + message = JSON.parse(request.requestBody); + validate(message); + + // trigger should be auctionEnd + expect(message.trigger).to.be.equal('auctionEnd'); + expect(message.auctions[0].adUnits[0].gam).to.be.undefined; + expect(message.auctions[0].adUnits[1].gam).to.be.undefined; + }); + it('should send request when waitForGamSlots is present but no bidWons are sent', function () { config.setConfig({ rubicon: { @@ -2022,6 +2142,35 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].adUnits[1].pattern).to.equal('1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile'); }); + it('should pass gpid if defined', function () { + let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); + bidRequest.bids[0].ortb2Imp = { + ext: { + gpid: '1234/mycoolsite/lowerbox' + } + }; + bidRequest.bids[1].ortb2Imp = { + ext: { + gpid: '1234/mycoolsite/leaderboard' + } + }; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, bidRequest); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + + clock.tick(SEND_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].gpid).to.equal('1234/mycoolsite/lowerbox'); + expect(message.auctions[0].adUnits[1].gpid).to.equal('1234/mycoolsite/leaderboard'); + }); + it('should pass bidderDetail for multibid auctions', function () { let bidResponse = utils.deepClone(MOCK.BID_RESPONSE[1]); bidResponse.targetingBidder = 'rubi2'; @@ -2103,6 +2252,246 @@ describe('rubicon analytics adapter', function () { }); }); + describe('billing events integration', () => { + beforeEach(function () { + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + // default dmBilling + config.setConfig({ + rubicon: { + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + } + } + }) + }); + afterEach(function () { + rubiconAnalyticsAdapter.disableAnalytics(); + }); + const basicBillingAuction = (billingEvents = []) => { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // emit billing events + billingEvents.forEach(ev => events.emit(BILLABLE_EVENT, ev)); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + } + 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', () => { + // 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(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: 'general', // mapping all events to endpoint as 'general' for now + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' + }]); + }); + 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' + }, + { + vendor: 'vendorName', + type: 'auction', + billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f' + }, + { + 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.haveOwnProperty('auctions'); + expect(message.billableEvents).to.deep.equal([ + { + accountId: 1001, + vendor: 'vendorName', + type: 'general', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' + }, + { + accountId: 1001, + vendor: 'vendorName', + type: 'general', + billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f' + } + ]); + }); + it('should pass along event right away if no pending auction', () => { + // off by default + config.setConfig({ + rubicon: { + 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: 'general', + 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: 'general', + 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('wrapper details passed in', () => { it('should correctly pass in the wrapper details if provided', () => { config.setConfig({ diff --git a/test/spec/modules/rubiconAnalyticsSchema.json b/test/spec/modules/rubiconAnalyticsSchema.json index 39a33867edd..36257a76cc8 100644 --- a/test/spec/modules/rubiconAnalyticsSchema.json +++ b/test/spec/modules/rubiconAnalyticsSchema.json @@ -17,6 +17,11 @@ "required": [ "bidsWon" ] + }, + { + "required": [ + "billableEvents" + ] } ], "properties": { @@ -237,9 +242,44 @@ ] } }, - "wrapperName": { - "type": "string" - } + "billableEvents":{ + "type":"array", + "minItems":1, + "items":{ + "type":"object", + "required":[ + "accountId", + "vendor", + "type", + "billingId" + ], + "properties":{ + "vendor":{ + "type":"string", + "description":"The name of the vendor who emitted the billable event" + }, + "type":{ + "type":"string", + "description":"The type of billable event", + "enum":[ + "impression", + "pageLoad", + "auction", + "request", + "general" + ] + }, + "billingId":{ + "type":"string", + "description":"A UUID which is responsible more mapping this event to" + }, + "accountId": { + "type": "number", + "description": "The account id for the rubicon publisher" + } + } + } + } }, "definitions": { "gam": { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 6210640f79f..c281c195dd2 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -10,7 +10,7 @@ import { import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; import {createEidsArray} from 'modules/userId/eids.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid @@ -597,7 +597,7 @@ describe('the rubicon adapter', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; + const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'l_pb_bid_id', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; request.data.split('&').forEach((item, i) => { expect(item.split('=')[0]).to.equal(referenceOrdering[i]); @@ -1408,7 +1408,7 @@ describe('the rubicon adapter', function () { expect(data['tg_i.pbadslot']).to.equal('abc'); }); - it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string, but all leading slash characters should be removed', function () { + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { bidderRequest.bids[0].ortb2Imp = { ext: { data: { @@ -1422,7 +1422,45 @@ describe('the rubicon adapter', function () { expect(data).to.be.an('Object'); expect(data).to.have.property('tg_i.pbadslot'); - expect(data['tg_i.pbadslot']).to.equal('a/b/c'); + expect(data['tg_i.pbadslot']).to.equal('/a/b/c'); + }); + + it('should send gpid as p_gpid if valid', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/1233/sports&div1' + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('p_gpid'); + expect(data['p_gpid']).to.equal('/1233/sports&div1'); + }); + + it('should send gpid and pbadslot since it is prefered over dfp code', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/1233/sports&div1', + data: { + pbadslot: 'pb_slot', + adserver: { + adslot: '/1234/sports', + name: 'gam' + } + } + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data['p_gpid']).to.equal('/1233/sports&div1'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(data['tg_i.pbadslot']).to.equal('pb_slot'); }); }); @@ -1470,12 +1508,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string', function () { + it('should send NOT \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string but not gam', function () { bidderRequest.bids[0].ortb2Imp = { ext: { data: { adserver: { - adslot: 'abc' + adslot: '/a/b/c', + name: 'not gam' } } } @@ -1485,16 +1524,16 @@ describe('the rubicon adapter', function () { const data = parseQuery(request.data); expect(data).to.be.an('Object'); - expect(data).to.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string, but all leading slash characters should be removed', function () { + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string and name is gam', function () { bidderRequest.bids[0].ortb2Imp = { ext: { data: { adserver: { - adslot: 'a/b/c' + name: 'gam', + adslot: '/a/b/c' } } } @@ -1505,7 +1544,7 @@ describe('the rubicon adapter', function () { expect(data).to.be.an('Object'); expect(data).to.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.dfp_ad_unit_code']).to.equal('a/b/c'); + expect(data['tg_i.dfp_ad_unit_code']).to.equal('/a/b/c'); }); }); }); @@ -3223,7 +3262,7 @@ describe('the rubicon adapter', function () { label: undefined, placement: { align: 'left', - attachTo: '#outstream_video1_placement', + attachTo: adUnit, position: 'append', }, vastUrl: 'https://test.com/vast.xml', @@ -3343,6 +3382,23 @@ describe('the rubicon adapter', function () { type: 'iframe', url: `${emilyUrl}?gdpr_consent=foo&us_privacy=1NYN` }); }); + + it('should pass gdprApplies', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { + gdprApplies: true + }, '1NYN')).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr=1&us_privacy=1NYN` + }); + }); + + it('should pass all correctly', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { + gdprApplies: true, + consentString: 'foo' + }, '1NYN')).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` + }); + }); }); describe('get price granularity', function () { diff --git a/test/spec/modules/scaleableAnalyticsAdapter_spec.js b/test/spec/modules/scaleableAnalyticsAdapter_spec.js index 70b94a2b807..c65740252d2 100644 --- a/test/spec/modules/scaleableAnalyticsAdapter_spec.js +++ b/test/spec/modules/scaleableAnalyticsAdapter_spec.js @@ -1,6 +1,6 @@ import scaleableAnalytics from 'modules/scaleableAnalyticsAdapter.js'; import { expect } from 'chai'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import { server } from 'test/mocks/xhr.js'; diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 3aa378379dd..1e0dca68d00 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -1,14 +1,17 @@ -import { expect } from 'chai' -import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js' -import * as utils from 'src/utils.js' +import { expect } from 'chai'; +import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js'; +import * as utils from 'src/utils.js'; -const PUBLISHER_ID = '0000-0000-01' -const ADUNIT_ID = '000000' +const PUBLISHER_ID = '0000-0000-01'; +const ADUNIT_ID = '000000'; function getSlotConfigs(mediaTypes, params) { return { params: params, - sizes: [[300, 250], [300, 600]], + sizes: [ + [300, 250], + [300, 600], + ], bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', @@ -17,325 +20,336 @@ function getSlotConfigs(mediaTypes, params) { mediaTypes: mediaTypes, src: 'client', transactionId: 'd704d006-0d6e-4a09-ad6c-179e7e758096', - adUnitCode: 'adunit-code' - } + adUnitCode: 'adunit-code', + }; } function createVideoSlotConfig(mediaType) { return getSlotConfigs(mediaType, { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'video' - }) + placement: 'video', + }); } -describe('Seedtag Adapter', function() { - describe('isBidRequestValid method', function() { - describe('returns true', function() { +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 => { + describe('and placement has the correct value', function () { + const createBannerSlotConfig = (placement) => { return getSlotConfigs( { banner: {} }, { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement + placement, } - ) - } - const placements = ['banner', 'video', 'inImage', 'inScreen', 'inArticle'] - placements.forEach(placement => { - it('should be ' + placement, function() { + ); + }; + const placements = [ + 'banner', + 'video', + 'inImage', + 'inScreen', + 'inArticle', + ]; + placements.forEach((placement) => { + it('should be ' + placement, function () { const isBidRequestValid = spec.isBidRequestValid( createBannerSlotConfig(placement) - ) - expect(isBidRequestValid).to.equal(true) - }) - }) - }) - }) - describe('when video slot has all mandatory params', function() { + ); + expect(isBidRequestValid).to.equal(true); + }); + }); + }); + }); + 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]] - } + playerSize: [[600, 200]], + }, }, { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'video' + placement: 'video', } - ) - const isBidRequestValid = spec.isBidRequestValid(slotConfig) - expect(isBidRequestValid).to.equal(true) - }) + ); + const isBidRequestValid = spec.isBidRequestValid(slotConfig); + expect(isBidRequestValid).to.equal(true); + }); it('should return true, when video context is outstream', function () { const slotConfig = getSlotConfigs( { video: { context: 'outstream', - playerSize: [[600, 200]] - } + playerSize: [[600, 200]], + }, }, { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'video' + placement: 'video', } - ) - const isBidRequestValid = spec.isBidRequestValid(slotConfig) - expect(isBidRequestValid).to.equal(true) - }) - }) - }) - describe('returns false', function() { - describe('when params are not correct', function() { + ); + const isBidRequestValid = spec.isBidRequestValid(slotConfig); + expect(isBidRequestValid).to.equal(true); + }); + }); + }); + describe('returns false', function () { + describe('when params are not correct', function () { function createSlotConfig(params) { - return getSlotConfigs({ banner: {} }, params) + return getSlotConfigs({ banner: {} }, params); } - it('does not have the PublisherToken.', function() { + it('does not have the PublisherToken.', function () { const isBidRequestValid = spec.isBidRequestValid( createSlotConfig({ adUnitId: ADUNIT_ID, - placement: 'banner' + placement: 'banner', }) - ) - expect(isBidRequestValid).to.equal(false) - }) - it('does not have the AdUnitId.', function() { + ); + expect(isBidRequestValid).to.equal(false); + }); + it('does not have the AdUnitId.', function () { const isBidRequestValid = spec.isBidRequestValid( createSlotConfig({ publisherId: PUBLISHER_ID, - placement: 'banner' + placement: 'banner', }) - ) - expect(isBidRequestValid).to.equal(false) - }) - it('does not have the placement.', function() { + ); + expect(isBidRequestValid).to.equal(false); + }); + it('does not have the placement.', function () { const isBidRequestValid = spec.isBidRequestValid( createSlotConfig({ publisherId: PUBLISHER_ID, - adUnitId: ADUNIT_ID + adUnitId: ADUNIT_ID, }) - ) - expect(isBidRequestValid).to.equal(false) - }) - it('does not have a the correct placement.', function() { + ); + expect(isBidRequestValid).to.equal(false); + }); + it('does not have a the correct placement.', function () { const isBidRequestValid = spec.isBidRequestValid( createSlotConfig({ publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'another_thing' + placement: 'another_thing', }) - ) - expect(isBidRequestValid).to.equal(false) - }) - }) - describe('when video mediaType object is not correct', 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' - }) + placement: 'video', + }); } - it('is a void object', function() { + it('is a void object', function () { const isBidRequestValid = spec.isBidRequestValid( createVideoSlotConfig({ video: {} }) - ) - expect(isBidRequestValid).to.equal(false) - }) - it('does not have playerSize.', function() { + ); + expect(isBidRequestValid).to.equal(false); + }); + it('does not have playerSize.', function () { const isBidRequestValid = spec.isBidRequestValid( createVideoSlotConfig({ video: { context: 'instream' } }) - ) - expect(isBidRequestValid).to.equal(false) - }) + ); + expect(isBidRequestValid).to.equal(false); + }); it('is outstream ', function () { const isBidRequestValid = spec.isBidRequestValid( createVideoSlotConfig({ video: { context: 'outstream', - playerSize: [[600, 200]] - } + playerSize: [[600, 200]], + }, }) - ) - expect(isBidRequestValid).to.equal(true) - }) - describe('order does not matter', function() { - it('when video is not the first slot', function() { + ); + expect(isBidRequestValid).to.equal(true); + }); + describe('order does not matter', function () { + it('when video is not the first slot', function () { const isBidRequestValid = spec.isBidRequestValid( createVideoSlotConfig({ banner: {}, video: {} }) - ) - expect(isBidRequestValid).to.equal(false) - }) - it('when video is the first slot', function() { + ); + expect(isBidRequestValid).to.equal(false); + }); + it('when video is the first slot', function () { const isBidRequestValid = spec.isBidRequestValid( createVideoSlotConfig({ video: {}, banner: {} }) - ) - expect(isBidRequestValid).to.equal(false) - }) - }) - }) - }) - }) + ); + expect(isBidRequestValid).to.equal(false); + }); + }); + }); + }); + }); - describe('buildRequests method', function() { + describe('buildRequests method', function () { const bidderRequest = { refererInfo: { referer: 'referer' }, - timeout: 1000 - } + timeout: 1000, + }; const mandatoryParams = { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'banner' - } + placement: 'banner', + }; const inStreamParams = Object.assign({}, mandatoryParams, { video: { - mimes: 'mp4' - } - }) + mimes: 'mp4', + }, + }); const validBidRequests = [ getSlotConfigs({ banner: {} }, mandatoryParams), getSlotConfigs( { video: { context: 'instream', playerSize: [[300, 200]] } }, inStreamParams - ) - ] - it('Url params should be correct ', function() { - const request = spec.buildRequests(validBidRequests, bidderRequest) - expect(request.method).to.equal('POST') - expect(request.url).to.equal('https://s.seedtag.com/c/hb/bid') - }) + ), + ]; + it('Url params should be correct ', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://s.seedtag.com/c/hb/bid'); + }); - it('Common data request should be correct', function() { - const request = spec.buildRequests(validBidRequests, bidderRequest) - const data = JSON.parse(request.data) - expect(data.url).to.equal('referer') - expect(data.publisherToken).to.equal('0000-0000-01') - expect(typeof data.version).to.equal('string') - expect(['fixed', 'mobile', 'unknown'].indexOf(data.connectionType)).to.be.above(-1) - expect(data.bidRequests[0].adUnitCode).to.equal('adunit-code') - }) + it('Common data request should be correct', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.url).to.equal('referer'); + expect(data.publisherToken).to.equal('0000-0000-01'); + expect(typeof data.version).to.equal('string'); + expect( + ['fixed', 'mobile', 'unknown'].indexOf(data.connectionType) + ).to.be.above(-1); + expect(data.bidRequests[0].adUnitCode).to.equal('adunit-code'); + }); - describe('adPosition param', function() { - it('should sended when publisher set adPosition param', function() { + 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() { + 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) - }) - }) + 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() { - const request = spec.buildRequests(validBidRequests, bidderRequest) - const data = JSON.parse(request.data) - expect(data.cmp).to.equal(false) - }) - }) - describe('when there are consent management platform', function() { - it('cmps should be true and ga should not sended, when gdprApplies is undefined', function() { + describe('GDPR params', function () { + describe('when there arent consent management platform', function () { + it('cmp should be false', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.cmp).to.equal(false); + }); + }); + describe('when there are consent management platform', function () { + it('cmps should be true and ga should not sended, when gdprApplies is undefined', function () { bidderRequest['gdprConsent'] = { gdprApplies: undefined, - consentString: 'consentString' - } - const request = spec.buildRequests(validBidRequests, bidderRequest) - const data = JSON.parse(request.data) - expect(data.cmp).to.equal(true) - expect(Object.keys(data).indexOf('data')).to.equal(-1) - expect(data.cd).to.equal('consentString') - }) - it('cmps should be true and all gdpr parameters should be sended, when there are gdprApplies', function() { + consentString: 'consentString', + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.cmp).to.equal(true); + expect(Object.keys(data).indexOf('data')).to.equal(-1); + expect(data.cd).to.equal('consentString'); + }); + it('cmps should be true and all gdpr parameters should be sended, when there are gdprApplies', function () { bidderRequest['gdprConsent'] = { gdprApplies: true, - consentString: 'consentString' - } - const request = spec.buildRequests(validBidRequests, bidderRequest) - const data = JSON.parse(request.data) - expect(data.cmp).to.equal(true) - expect(data.ga).to.equal(true) - expect(data.cd).to.equal('consentString') - }) - }) - }) + consentString: 'consentString', + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.cmp).to.equal(true); + expect(data.ga).to.equal(true); + expect(data.cd).to.equal('consentString'); + }); + it('should expose gvlid', function () { + expect(spec.gvlid).to.equal(157); + }); + }); + }); - describe('BidRequests params', function() { - const request = spec.buildRequests(validBidRequests, bidderRequest) - const data = JSON.parse(request.data) - const bidRequests = data.bidRequests - it('should request a Banner', function() { - const bannerBid = bidRequests[0] - expect(bannerBid.id).to.equal('30b31c1838de1e') + describe('BidRequests params', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const bidRequests = data.bidRequests; + it('should request a Banner', function () { + const bannerBid = bidRequests[0]; + expect(bannerBid.id).to.equal('30b31c1838de1e'); expect(bannerBid.transactionId).to.equal( 'd704d006-0d6e-4a09-ad6c-179e7e758096' - ) - expect(bannerBid.supplyTypes[0]).to.equal('display') - expect(bannerBid.adUnitId).to.equal('000000') - expect(bannerBid.sizes[0][0]).to.equal(300) - expect(bannerBid.sizes[0][1]).to.equal(250) - expect(bannerBid.sizes[1][0]).to.equal(300) - expect(bannerBid.sizes[1][1]).to.equal(600) - expect(bannerBid.requestCount).to.equal(1) - }) - it('should request an InStream Video', function() { - const videoBid = bidRequests[1] - expect(videoBid.id).to.equal('30b31c1838de1e') + ); + expect(bannerBid.supplyTypes[0]).to.equal('display'); + expect(bannerBid.adUnitId).to.equal('000000'); + expect(bannerBid.sizes[0][0]).to.equal(300); + expect(bannerBid.sizes[0][1]).to.equal(250); + expect(bannerBid.sizes[1][0]).to.equal(300); + expect(bannerBid.sizes[1][1]).to.equal(600); + expect(bannerBid.requestCount).to.equal(1); + }); + it('should request an InStream Video', function () { + const videoBid = bidRequests[1]; + expect(videoBid.id).to.equal('30b31c1838de1e'); expect(videoBid.transactionId).to.equal( 'd704d006-0d6e-4a09-ad6c-179e7e758096' - ) - expect(videoBid.supplyTypes[0]).to.equal('video') - expect(videoBid.adUnitId).to.equal('000000') - expect(videoBid.videoParams.mimes).to.equal('mp4') - expect(videoBid.videoParams.w).to.equal(300) - expect(videoBid.videoParams.h).to.equal(200) - expect(videoBid.sizes[0][0]).to.equal(300) - expect(videoBid.sizes[0][1]).to.equal(250) - expect(videoBid.sizes[1][0]).to.equal(300) - expect(videoBid.sizes[1][1]).to.equal(600) - expect(videoBid.requestCount).to.equal(1) - }) - }) - }) + ); + expect(videoBid.supplyTypes[0]).to.equal('video'); + expect(videoBid.adUnitId).to.equal('000000'); + expect(videoBid.videoParams.mimes).to.equal('mp4'); + expect(videoBid.videoParams.w).to.equal(300); + expect(videoBid.videoParams.h).to.equal(200); + expect(videoBid.sizes[0][0]).to.equal(300); + expect(videoBid.sizes[0][1]).to.equal(250); + expect(videoBid.sizes[1][0]).to.equal(300); + expect(videoBid.sizes[1][1]).to.equal(600); + expect(videoBid.requestCount).to.equal(1); + }); + }); + }); - describe('interpret response method', function() { - it('should return a void array, when the server response are not correct.', function() { - const request = { data: JSON.stringify({}) } + describe('interpret response method', function () { + it('should return a void array, when the server response are not correct.', function () { + const request = { data: JSON.stringify({}) }; const serverResponse = { - body: {} - } - const bids = spec.interpretResponse(serverResponse, request) - expect(typeof bids).to.equal('object') - expect(bids.length).to.equal(0) - }) - it('should return a void array, when the server response have no bids.', function() { - const request = { data: JSON.stringify({}) } - const serverResponse = { body: { bids: [] } } - const bids = spec.interpretResponse(serverResponse, request) - expect(typeof bids).to.equal('object') - expect(bids.length).to.equal(0) - }) - describe('when the server response return a bid', function() { - describe('the bid is a banner', function() { - it('should return a banner bid', function() { - const request = { data: JSON.stringify({}) } + body: {}, + }; + const bids = spec.interpretResponse(serverResponse, request); + expect(typeof bids).to.equal('object'); + expect(bids.length).to.equal(0); + }); + it('should return a void array, when the server response have no bids.', function () { + const request = { data: JSON.stringify({}) }; + const serverResponse = { body: { bids: [] } }; + const bids = spec.interpretResponse(serverResponse, request); + expect(typeof bids).to.equal('object'); + expect(bids.length).to.equal(0); + }); + describe('when the server response return a bid', function () { + describe('the bid is a banner', function () { + it('should return a banner bid', function () { + const request = { data: JSON.stringify({}) }; const serverResponse = { body: { bids: [ @@ -349,28 +363,30 @@ describe('Seedtag Adapter', function() { mediaType: 'display', ttl: 360, nurl: 'testurl.com/nurl', - adomain: ['advertiserdomain.com'] - } + adomain: ['advertiserdomain.com'], + }, ], - cookieSync: { url: '' } - } - } - const bids = spec.interpretResponse(serverResponse, request) - expect(bids.length).to.equal(1) - expect(bids[0].requestId).to.equal('2159a54dc2566f') - expect(bids[0].cpm).to.equal(0.5) - expect(bids[0].width).to.equal(728) - expect(bids[0].height).to.equal(90) - expect(bids[0].currency).to.equal('USD') - expect(bids[0].netRevenue).to.equal(true) - expect(bids[0].ad).to.equal('content') - expect(bids[0].nurl).to.equal('testurl.com/nurl') - expect(bids[0].meta.advertiserDomains).to.deep.equal(['advertiserdomain.com']) - }) - }) - describe('the bid is a video', function() { - it('should return a instream bid', function() { - const request = { data: JSON.stringify({}) } + cookieSync: { url: '' }, + }, + }; + const bids = spec.interpretResponse(serverResponse, request); + expect(bids.length).to.equal(1); + expect(bids[0].requestId).to.equal('2159a54dc2566f'); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ad).to.equal('content'); + expect(bids[0].nurl).to.equal('testurl.com/nurl'); + expect(bids[0].meta.advertiserDomains).to.deep.equal([ + 'advertiserdomain.com', + ]); + }); + }); + describe('the bid is a video', function () { + it('should return a instream bid', function () { + const request = { data: JSON.stringify({}) }; const serverResponse = { body: { bids: [ @@ -383,114 +399,124 @@ describe('Seedtag Adapter', function() { height: 90, mediaType: 'video', ttl: 360, - nurl: undefined - } + nurl: undefined, + }, ], - cookieSync: { url: '' } - } - } - const bids = spec.interpretResponse(serverResponse, request) - expect(bids.length).to.equal(1) - expect(bids[0].requestId).to.equal('2159a54dc2566f') - expect(bids[0].cpm).to.equal(0.5) - expect(bids[0].width).to.equal(728) - expect(bids[0].height).to.equal(90) - expect(bids[0].currency).to.equal('USD') - expect(bids[0].netRevenue).to.equal(true) - expect(bids[0].vastXml).to.equal('content') - expect(bids[0].meta.advertiserDomains).to.deep.equal([]) - }) - }) - }) - }) + cookieSync: { url: '' }, + }, + }; + const bids = spec.interpretResponse(serverResponse, request); + expect(bids.length).to.equal(1); + expect(bids[0].requestId).to.equal('2159a54dc2566f'); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].vastXml).to.equal('content'); + expect(bids[0].meta.advertiserDomains).to.deep.equal([]); + }); + }); + }); + }); - describe('user syncs method', function() { - it('should return empty array, when iframe sync option are disabled.', function() { - const syncOption = { iframeEnabled: false } - const serverResponses = [{ body: { cookieSync: 'someUrl' } }] - const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses) - expect(cookieSyncArray.length).to.equal(0) - }) - it('should return empty array, when the server response are wrong.', function() { - const syncOption = { iframeEnabled: true } - const serverResponses = [{ body: {} }] - const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses) - expect(cookieSyncArray.length).to.equal(0) - }) - it('should return empty array, when the server response are void.', function() { - const syncOption = { iframeEnabled: true } - const serverResponses = [{ body: { cookieSync: '' } }] - const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses) - expect(cookieSyncArray.length).to.equal(0) - }) - it('should return a array with the cookie sync, when the server response with a cookie sync.', function() { - const syncOption = { iframeEnabled: true } - const serverResponses = [{ body: { cookieSync: 'someUrl' } }] - const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses) - expect(cookieSyncArray.length).to.equal(1) - expect(cookieSyncArray[0].type).to.equal('iframe') - expect(cookieSyncArray[0].url).to.equal('someUrl') - }) - }) + describe('user syncs method', function () { + it('should return empty array, when iframe sync option are disabled.', function () { + const syncOption = { iframeEnabled: false }; + const serverResponses = [{ body: { cookieSync: 'someUrl' } }]; + const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses); + expect(cookieSyncArray.length).to.equal(0); + }); + it('should return empty array, when the server response are wrong.', function () { + const syncOption = { iframeEnabled: true }; + const serverResponses = [{ body: {} }]; + const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses); + expect(cookieSyncArray.length).to.equal(0); + }); + it('should return empty array, when the server response are void.', function () { + const syncOption = { iframeEnabled: true }; + const serverResponses = [{ body: { cookieSync: '' } }]; + const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses); + expect(cookieSyncArray.length).to.equal(0); + }); + it('should return a array with the cookie sync, when the server response with a cookie sync.', function () { + const syncOption = { iframeEnabled: true }; + const serverResponses = [{ body: { cookieSync: 'someUrl' } }]; + const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses); + expect(cookieSyncArray.length).to.equal(1); + expect(cookieSyncArray[0].type).to.equal('iframe'); + expect(cookieSyncArray[0].url).to.equal('someUrl'); + }); + }); describe('onTimeout', function () { - beforeEach(function() { - sinon.stub(utils, 'triggerPixel') - }) + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); - afterEach(function() { - utils.triggerPixel.restore() - }) + afterEach(function () { + utils.triggerPixel.restore(); + }); it('should return the correct endpoint', function () { - const params = { publisherId: '0000', adUnitId: '11111' } - const timeoutData = [{ params: [ params ] }]; + const params = { publisherId: '0000', adUnitId: '11111' }; + const timeout = 3000; + const timeoutData = [{ params: [params], timeout }]; const timeoutUrl = getTimeoutUrl(timeoutData); expect(timeoutUrl).to.equal( 'https://s.seedtag.com/se/hb/timeout?publisherToken=' + - params.publisherId + - '&adUnitId=' + - params.adUnitId - ) - }) + params.publisherId + + '&adUnitId=' + + params.adUnitId + + '&timeout=' + + timeout + ); + }); - it('should set the timeout pixel', function() { - const params = { publisherId: '0000', adUnitId: '11111' } - const timeoutData = [{ params: [ params ] }]; - spec.onTimeout(timeoutData) - expect(utils.triggerPixel.calledWith('https://s.seedtag.com/se/hb/timeout?publisherToken=' + - params.publisherId + - '&adUnitId=' + - params.adUnitId)).to.equal(true); - }) - }) + it('should set the timeout pixel', function () { + const params = { publisherId: '0000', adUnitId: '11111' }; + const timeout = 3000; + const timeoutData = [{ params: [params], timeout }]; + spec.onTimeout(timeoutData); + expect( + utils.triggerPixel.calledWith( + 'https://s.seedtag.com/se/hb/timeout?publisherToken=' + + params.publisherId + + '&adUnitId=' + + params.adUnitId + + '&timeout=' + + timeout + ) + ).to.equal(true); + }); + }); describe('onBidWon', function () { - beforeEach(function() { - sinon.stub(utils, 'triggerPixel') - }) + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); - afterEach(function() { - utils.triggerPixel.restore() - }) + afterEach(function () { + utils.triggerPixel.restore(); + }); - describe('without nurl', function() { - const bid = {} + describe('without nurl', function () { + const bid = {}; - it('does not create pixel ', function() { - spec.onBidWon(bid) + it('does not create pixel ', function () { + spec.onBidWon(bid); expect(utils.triggerPixel.called).to.equal(false); - }) - }) + }); + }); describe('with nurl', function () { - const nurl = 'http://seedtag_domain/won' - const bid = { nurl } + const nurl = 'http://seedtag_domain/won'; + const bid = { nurl }; - it('creates nurl pixel if bid nurl', function() { - spec.onBidWon({ nurl }) + it('creates nurl pixel if bid nurl', function () { + spec.onBidWon({ nurl }); expect(utils.triggerPixel.calledWith(nurl)).to.equal(true); - }) - }) - }) -}) + }); + }); + }); +}); diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 534d0b3f381..8ef34a1599e 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -50,10 +50,10 @@ describe('SharedId System', function () { expect(callbackSpy.calledOnce).to.be.true; expect(callbackSpy.lastCall.lastArg).to.equal(UUID); }); - it('should log message if coppa is set', function () { + it('should abort if coppa is set', function () { coppaDataHandlerDataStub.returns('true'); - sharedIdSystemSubmodule.getId({}); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); + const result = sharedIdSystemSubmodule.getId({}); + expect(result).to.be.undefined; }); }); describe('SharedId System extendId()', function () { @@ -85,10 +85,56 @@ describe('SharedId System', function () { let pubcommId = sharedIdSystemSubmodule.extendId(config, undefined, 'TestId').id; expect(pubcommId).to.equal('TestId'); }); - it('should log message if coppa is set', function () { + it('should abort if coppa is set', function () { coppaDataHandlerDataStub.returns('true'); - sharedIdSystemSubmodule.extendId({}, undefined, 'TestId'); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); + const result = sharedIdSystemSubmodule.extendId({params: {extend: true}}, undefined, 'TestId'); + expect(result).to.be.undefined; + }); + }); + + describe('SharedID System domainOverride', () => { + let sandbox, domain, cookies, rejectCookiesFor; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(document, 'domain').get(() => domain); + cookies = {}; + sandbox.stub(storage, 'getCookie').callsFake((key) => cookies[key]); + rejectCookiesFor = null; + sandbox.stub(storage, 'setCookie').callsFake((key, value, expires, sameSite, domain) => { + if (domain !== rejectCookiesFor) { + if (expires != null) { + expires = new Date(expires); + } + if (expires == null || expires > Date.now()) { + cookies[key] = value; + } else { + delete cookies[key]; + } + } + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return TLD if cookies can be set there', () => { + domain = 'sub.domain.com'; + rejectCookiesFor = 'com'; + expect(sharedIdSystemSubmodule.domainOverride()).to.equal('domain.com'); + }); + + it('should return undefined when cookies cannot be set', () => { + domain = 'sub.domain.com'; + rejectCookiesFor = 'sub.domain.com'; + expect(sharedIdSystemSubmodule.domainOverride()).to.be.undefined; + }); + + it('should return half-way domain if parent domain rejects cookies', () => { + domain = 'inner.outer.domain.com'; + rejectCookiesFor = 'domain.com'; + expect(sharedIdSystemSubmodule.domainOverride()).to.equal('outer.domain.com'); }); }); }); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index db21af5f6b3..39238cc877f 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import * as sinon from 'sinon'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config'; @@ -157,7 +158,6 @@ describe('sharethrough adapter spec', function () { startdelay: 42, skipmin: 10, skipafter: 20, - placement: 1, delivery: 1, companiontype: 'companion type', companionad: 'companion ad', @@ -205,6 +205,7 @@ describe('sharethrough adapter spec', function () { expect(openRtbReq.cur).to.deep.equal(['USD']); expect(openRtbReq.tmax).to.equal(242); + expect(Object.keys(openRtbReq.site)).to.have.length(3); expect(openRtbReq.site.domain).not.to.be.undefined; expect(openRtbReq.site.page).not.to.be.undefined; expect(openRtbReq.site.ref).to.equal('https://referer.com'); @@ -256,6 +257,17 @@ describe('sharethrough adapter spec', function () { }); }); + describe('no referer provided', () => { + beforeEach(() => { + bidderRequest = {}; + }); + + it('should set referer to undefined', () => { + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; + expect(openRtbReq.site.ref).to.be.undefined; + }); + }); + describe('regulation', () => { describe('gdpr', () => { it('should populate request accordingly when gdpr applies', () => { @@ -419,17 +431,90 @@ describe('sharethrough adapter spec', function () { expect(videoImp.startdelay).to.equal(0); expect(videoImp.skipmin).to.equal(0); expect(videoImp.skipafter).to.equal(0); - expect(videoImp.placement).to.be.undefined; + expect(videoImp.placement).to.equal(1); expect(videoImp.delivery).to.be.undefined; expect(videoImp.companiontype).to.be.undefined; expect(videoImp.companionad).to.be.undefined; }); - it('should not return a video impression if context is outstream', () => { - bidRequests[1].mediaTypes.video.context = 'outstream'; - const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + describe('outstream', () => { + it('should use placement value if provided', () => { + bidRequests[1].mediaTypes.video.context = 'outstream'; + bidRequests[1].mediaTypes.video.placement = 3; + + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + const videoImp = builtRequest.data.imp[0].video; + + expect(videoImp.placement).to.equal(3); + }); + + it('should default placement to 4 if not provided', () => { + bidRequests[1].mediaTypes.video.context = 'outstream'; + + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + const videoImp = builtRequest.data.imp[0].video; + + expect(videoImp.placement).to.equal(4); + }); + }); + }); + + describe('first party data', () => { + const firstPartyData = { + site: { + name: 'example', + keywords: 'power tools, drills', + search: 'drill', + content: { + userrating: '4', + }, + ext: { + data: { + pageType: 'article', + category: 'repair', + }, + }, + }, + user: { + yob: 1985, + gender: 'm', + ext: { + data: { + registered: true, + interests: ['cars'], + }, + }, + }, + }; + + let configStub; + + beforeEach(() => { + configStub = sinon.stub(config, 'getConfig'); + configStub.withArgs('ortb2').returns(firstPartyData); + }); + + afterEach(() => { + configStub.restore(); + }); + + it('should include first party data in open rtb request, site section', () => { + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; + + expect(openRtbReq.site.name).to.equal(firstPartyData.site.name); + expect(openRtbReq.site.keywords).to.equal(firstPartyData.site.keywords); + expect(openRtbReq.site.search).to.equal(firstPartyData.site.search); + expect(openRtbReq.site.content).to.deep.equal(firstPartyData.site.content); + expect(openRtbReq.site.ext).to.deep.equal(firstPartyData.site.ext); + }); + + it('should include first party data in open rtb request, user section', () => { + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; - expect(builtRequest).to.be.undefined; + expect(openRtbReq.user.yob).to.equal(firstPartyData.user.yob); + expect(openRtbReq.user.gender).to.equal(firstPartyData.user.gender); + expect(openRtbReq.user.ext.data).to.deep.equal(firstPartyData.user.ext.data); + expect(openRtbReq.user.ext.eids).not.to.be.undefined; }); }); }); diff --git a/test/spec/modules/showheroes-bsBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js index ad2210c18c6..69e8343dfc9 100644 --- a/test/spec/modules/showheroes-bsBidAdapter_spec.js +++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js @@ -13,6 +13,7 @@ const adomain = ['showheroes.com']; const gdpr = { 'gdprConsent': { + 'apiVersion': 2, 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', 'gdprApplies': true } @@ -332,6 +333,7 @@ describe('shBidAdapter', function () { { 'cpm': 5, 'creativeId': 'c_38b373e1e31c18', + 'adUnitCode': 'adunit-code-1', 'currency': 'EUR', 'width': 640, 'height': 480, diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index ab06ddc8f51..9bbd472c7e0 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -13,10 +13,11 @@ import { getAdUnitDetail, getFilteredMediaTypes, getBids, - internal + internal, setupAdUnitMediaTypes } from '../../../modules/sizeMappingV2.js'; import { adUnitSetupChecks } from '../../../src/prebid.js'; +import {deepClone} from '../../../src/utils.js'; const AD_UNITS = [{ code: 'div-gpt-ad-1460505748561-0', @@ -193,49 +194,23 @@ describe('sizeMappingV2', function () { utils.logError.restore(); }); - it('should filter out adUnit if it does not contain the required property mediaTypes', function () { - let adUnits = utils.deepClone(AD_UNITS); - delete adUnits[0].mediaTypes; - // before checkAdUnitSetupHook is called, the length of adUnits should be '2' - expect(adUnits.length).to.equal(2); - adUnits = checkAdUnitSetupHook(adUnits); - - // after checkAdUnitSetupHook is called, the length of adUnits should be '1' - expect(adUnits.length).to.equal(1); - expect(adUnits[0].code).to.equal('div-gpt-ad-1460505748561-1'); - }); + describe('basic validation', () => { + let validateAdUnit; - it('should filter out adUnit if it does not contain the required property "bids"', function() { - let adUnits = utils.deepClone(AD_UNITS); - delete adUnits[0].mediaTypes; - // before checkAdUnitSetupHook is called, the length of the adUnits should equal '2' - expect(adUnits.length).to.equal(2); - adUnits = checkAdUnitSetupHook(adUnits); - - // after checkAdUnitSetupHook is called, the length of the adUnits should be '1' - expect(adUnits.length).to.equal(1); - expect(adUnits[0].code).to.equal('div-gpt-ad-1460505748561-1'); - }); - - it('should filter out adUnit if it has declared property mediaTypes with an empty object', function () { - let adUnits = utils.deepClone(AD_UNITS); - adUnits[0].mediaTypes = {}; - // before checkAdUnitSetupHook is called, the length of adUnits should be '2' - expect(adUnits.length).to.equal(2); - adUnits = checkAdUnitSetupHook(adUnits); - - // after checkAdUnitSetupHook is called, the length of adUnits should be '1' - expect(adUnits.length).to.equal(1); - expect(adUnits[0].code).to.equal('div-gpt-ad-1460505748561-1'); - }); + beforeEach(() => { + validateAdUnit = sinon.stub(adUnitSetupChecks, 'validateAdUnit'); + }); - it('should log an error message if Ad Unit does not contain the required property "mediaTypes"', function () { - let adUnits = utils.deepClone(AD_UNITS); - delete adUnits[0].mediaTypes; + afterEach(() => { + validateAdUnit.restore(); + }); - checkAdUnitSetupHook(adUnits); - sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, 'Detected adUnit.code \'div-gpt-ad-1460505748561-0\' did not have a \'mediaTypes\' object defined. This is a required field for the auction, so this adUnit has been removed.'); + it('should filter out adUnits that do not pass adUnitSetupChecks.validateAdUnit', () => { + validateAdUnit.returns(null); + const adUnits = checkAdUnitSetupHook(utils.deepClone(AD_UNITS)); + AD_UNITS.forEach((u) => sinon.assert.calledWith(validateAdUnit, u)); + expect(adUnits.length).to.equal(0); + }); }); describe('banner mediaTypes checks', function () { @@ -1106,14 +1081,14 @@ describe('sizeMappingV2', function () { internal.checkBidderSizeConfigFormat.restore(); internal.getActiveSizeBucket.restore(); }); - it('should return an empty array if the bidder sizeConfig object is not formatted correctly', function () { + it('should return an empty set if the bidder sizeConfig object is not formatted correctly', function () { const sizeConfig = [ { minViewPort: [], relevantMediaTypes: ['none'] }, { minViewPort: [700, 0], relevantMediaTypes: ['banner', 'video'] } ]; const activeViewport = [720, 600]; const relevantMediaTypes = getRelevantMediaTypesForBidder(sizeConfig, activeViewport); - expect(relevantMediaTypes).to.deep.equal([]); + expect(relevantMediaTypes.size).to.equal(0) }); it('should call function checkBidderSizeConfigFormat() once', function () { @@ -1140,220 +1115,51 @@ describe('sizeMappingV2', function () { sinon.assert.calledWith(internal.getActiveSizeBucket, sizeConfig, activeViewport); }); - it('should return the array contained in "relevantMediaTypes" property whose sizeBucket matches with the current viewport', function () { + it('should return the types contained in "relevantMediaTypes" property whose sizeBucket matches with the current viewport', function () { const sizeConfig = [ { minViewPort: [0, 0], relevantMediaTypes: ['none'] }, { minViewPort: [700, 0], relevantMediaTypes: ['banner', 'video'] } ]; const activeVewport = [720, 600]; const relevantMediaTypes = getRelevantMediaTypesForBidder(sizeConfig, activeVewport); - expect(relevantMediaTypes).to.deep.equal(['banner', 'video']); + expect([...relevantMediaTypes]).to.deep.equal(['banner', 'video']); }); }); - describe('getAdUnitDetail(auctionId, adUnit, labels)', function () { + describe('getAdUnitDetail', function () { const adUnitDetailFixture_1 = { - adUnitCode: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, // remove if < 750px - { minViewPort: [750, 0], sizes: [[300, 250], [300, 600]] }, // between 750px and 1199px - { minViewPort: [1200, 0], sizes: [[970, 90], [728, 90], [300, 250]] }, // between 1200px and 1599px - { minViewPort: [1600, 0], sizes: [[1000, 300], [970, 90], [728, 90], [300, 250]] } // greater than 1600px - ] - }, - video: { - context: 'instream', - sizeConfig: [ - { minViewPort: [0, 0], playerSize: [] }, - { minViewPort: [800, 0], playerSize: [[640, 400]] }, - { minViewPort: [1200, 0], playerSize: [] } - ] - }, - native: { - image: { - required: true, - sizes: [150, 50] - }, - title: { - required: true, - len: 80 - }, - sponsoredBy: { - required: true - }, - clickUrl: { - required: true - }, - privacyLink: { - required: false - }, - body: { - required: true - }, - icon: { - required: true, - sizes: [50, 50] - }, - sizeConfig: [ - { minViewPort: [0, 0], active: false }, - { minViewPort: [600, 0], active: true }, - { minViewPort: [1000, 0], active: false } - ] - } - }, sizeBucketToSizeMap: {}, activeViewport: {}, - transformedMediaTypes: {}, - cacheHits: 0, - instance: 1, - isLabelActivated: true, - }; - const adUnitDetailFixture_2 = { - adUnitCode: 'div-gpt-ad-1460505748561-1', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - }, - video: { - context: 'instream', - playerSize: [300, 460] - } - }, - sizeBucketToSizeMap: {}, - activeViewport: {}, - cacheHits: 0, - instance: 1, - isLabelActivated: true, transformedMediaTypes: { banner: {}, video: {} } } - // adunit with same code at adUnitDetailFixture_1 but differnet mediaTypes object - const adUnitDetailFixture_3 = { - adUnitCode: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] } - ] - } - }, - sizeBucketToSizeMap: {}, - activeViewport: {}, - transformedMediaTypes: {}, - cacheHits: 0, - instance: 1, - isLabelActivated: true, - } const labels = ['mobile']; beforeEach(function () { - sinon - .stub(sizeMappingInternalStore, 'getAuctionDetail') - .withArgs('a1b2c3') - .returns({ - usingSizeMappingV2: true, - adUnits: [adUnitDetailFixture_1] - }); - - sinon - .stub(sizeMappingInternalStore, 'setAuctionDetail') - .withArgs('a1b2c3', adUnitDetailFixture_2); - const getFilteredMediaTypesStub = sinon.stub(internal, 'getFilteredMediaTypes'); getFilteredMediaTypesStub .withArgs(AD_UNITS[1].mediaTypes) - .returns(adUnitDetailFixture_2); - - getFilteredMediaTypesStub - .withArgs(adUnitDetailFixture_3.mediaTypes) - .returns(adUnitDetailFixture_3); - + .returns(adUnitDetailFixture_1); sinon.spy(utils, 'logInfo'); sinon.spy(utils, 'deepEqual'); }); afterEach(function () { - sizeMappingInternalStore.getAuctionDetail.restore(); - sizeMappingInternalStore.setAuctionDetail.restore(); internal.getFilteredMediaTypes.restore(); utils.logInfo.restore(); utils.deepEqual.restore(); }); - it('should return adUnit detail object from "sizeMappingInternalStore" if adUnit is already present in the store', function () { - const [adUnit] = utils.deepClone(AD_UNITS); - const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit, labels); - sinon.assert.callCount(sizeMappingInternalStore.getAuctionDetail, 1); - sinon.assert.callCount(utils.deepEqual, 1); - sinon.assert.callCount(internal.getFilteredMediaTypes, 0); - expect(adUnitDetail.cacheHits).to.equal(1); - expect(adUnitDetail).to.deep.equal(adUnitDetailFixture_1); - }); - - it('should NOT return adunit detail object from "sizeMappingInternalStore" if adUnit with the SAME CODE BUT DIFFERENT MEDIATYPES OBJECT is present in the store', function () { - const [adUnit] = utils.deepClone(AD_UNITS); - adUnit.mediaTypes = { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [1000, 0], sizes: [[1000, 300], [1000, 90], [970, 250], [970, 90], [728, 90]] } - ] - } - }; - const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit, labels); - sinon.assert.callCount(sizeMappingInternalStore.getAuctionDetail, 1); - sinon.assert.callCount(utils.deepEqual, 1); - expect(adUnitDetail).to.not.deep.equal(adUnitDetailFixture_1); - sinon.assert.callCount(internal.getFilteredMediaTypes, 1); - }); - - it('should store value in "sizeMappingInterStore" object if adUnit is NOT preset in this object', function () { - const [, adUnit] = utils.deepClone(AD_UNITS); - const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit, labels); - sinon.assert.callCount(sizeMappingInternalStore.setAuctionDetail, 1); - sinon.assert.callCount(internal.getFilteredMediaTypes, 1); - expect(adUnitDetail).to.deep.equal(adUnitDetailFixture_2); - }); - it('should log info message to show the details for activeSizeBucket', function () { const [, adUnit] = utils.deepClone(AD_UNITS); - getAdUnitDetail('a1b2c3', adUnit, labels); + getAdUnitDetail(adUnit, labels, 1); sinon.assert.callCount(utils.logInfo, 1); - sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: div-gpt-ad-1460505748561-1(1) => Active size buckets after filtration: `, adUnitDetailFixture_2.sizeBucketToSizeMap); - }); - - it('should increment "instance" count if presence of "Identical ad units" is detected', function () { - const adUnit = { - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizeConfig: [{ minViewPort: [0, 0], sizes: [[300, 300]] }] - } - }, - bids: [{ - bidder: 'appnexus', - params: 12 - }] - }; - - internal.getFilteredMediaTypes.restore(); - - sinon.stub(internal, 'getFilteredMediaTypes') - .withArgs(adUnit.mediaTypes) - .returns({ mediaTypes: {}, sizeBucketToSizeMap: {}, activeViewPort: [], transformedMediaTypes: {} }); - - const adUnitDetail = getAdUnitDetail('a1b2c3', adUnit, labels); - sinon.assert.callCount(sizeMappingInternalStore.setAuctionDetail, 1); - sinon.assert.callCount(internal.getFilteredMediaTypes, 1); - expect(adUnitDetail.instance).to.equal(2); + sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: div-gpt-ad-1460505748561-1(1) => Active size buckets after filtration: `, adUnitDetailFixture_1.sizeBucketToSizeMap); }); it('should not execute "getFilteredMediaTypes" function if label is not activated on the ad unit', function () { const [adUnit] = utils.deepClone(AD_UNITS); adUnit.labelAny = ['tablet']; - getAdUnitDetail('a1b2c3', adUnit, labels); + getAdUnitDetail(adUnit, labels, 1); // assertions sinon.assert.callCount(internal.getFilteredMediaTypes, 0); @@ -1376,57 +1182,8 @@ describe('sizeMappingV2', function () { utils.getWindowTop.restore(); utils.logWarn.restore(); }); - it('should return filteredMediaTypes object with all four properties (mediaTypes, transformedMediaTypes, activeViewport, sizeBucketToSizeMap) evaluated correctly', function () { + it('should return filteredMediaTypes object with all properties (transformedMediaTypes, activeViewport, sizeBucketToSizeMap) evaluated correctly', function () { const [adUnit] = utils.deepClone(AD_UNITS); - const expectedMediaTypes = { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, // remove if < 750px - { minViewPort: [750, 0], sizes: [[300, 250], [300, 600]] }, // between 750px and 1199px - { minViewPort: [1200, 0], sizes: [[970, 90], [728, 90], [300, 250]] }, // between 1200px and 1599px - { minViewPort: [1600, 0], sizes: [[1000, 300], [970, 90], [728, 90], [300, 250]] } // greater than 1600px - ] - }, - video: { - context: 'instream', - sizeConfig: [ - { minViewPort: [0, 0], playerSize: [] }, - { minViewPort: [800, 0], playerSize: [[640, 400]] }, - { minViewPort: [1200, 0], playerSize: [] } - ] - }, - native: { - image: { - required: true, - sizes: [150, 50] - }, - title: { - required: true, - len: 80 - }, - sponsoredBy: { - required: true - }, - clickUrl: { - required: true - }, - privacyLink: { - required: false - }, - body: { - required: true - }, - icon: { - required: true, - sizes: [50, 50] - }, - sizeConfig: [ - { minViewPort: [0, 0], active: false }, - { minViewPort: [600, 0], active: true }, - { minViewPort: [1000, 0], active: false } - ] - } - }; const expectedSizeBucketToSizeMap = { banner: { activeSizeBucket: [1600, 0], @@ -1459,8 +1216,7 @@ describe('sizeMappingV2', function () { ] } }; - const { mediaTypes, sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = getFilteredMediaTypes(adUnit.mediaTypes); - expect(mediaTypes).to.deep.equal(expectedMediaTypes); + const { sizeBucketToSizeMap, activeViewport, transformedMediaTypes } = getFilteredMediaTypes(adUnit.mediaTypes); expect(activeViewport).to.deep.equal(expectedActiveViewport); expect(sizeBucketToSizeMap).to.deep.equal(expectedSizeBucketToSizeMap); expect(transformedMediaTypes).to.deep.equal(expectedTransformedMediaTypes); @@ -1478,7 +1234,8 @@ describe('sizeMappingV2', function () { }); }); - describe('getBids({ bidderCode, auctionId, bidderRequestId, adUnits, labels, src })', function () { + describe('setupAdUnitsForLabels', function () { + let adUnits, adUnitDetail; const basic_AdUnit = [{ code: 'adUnit1', mediaTypes: { @@ -1508,46 +1265,31 @@ describe('sizeMappingV2', function () { }], transactionId: '123456' }]; - const adUnitDetailFixture = { - adUnitCode: 'adUnit1', - transactionId: '123456', - sizes: [[300, 200], [400, 600]], - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [600, 0], sizes: [[300, 200], [400, 600]] } - ] - } - }, - sizeBucketToSizeMap: { - banner: { - activeSizeBucket: [[500, 0]], - activeSizeDimensions: [[300, 200], [400, 600]] - } - }, - activeViewport: [560, 260], - transformedMediaTypes: { - banner: { - filteredSizeConfig: [ - { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } - ], - sizeConfig: [ - { minViewPort: [0, 0], sizes: [[]] }, - { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } - ], - sizes: [[300, 200], [400, 600]] - } - }, - isLabelActivated: true, - instance: 1, - cacheHits: 0 - }; + + const bidderMap = (adUnit) => Object.fromEntries(adUnit.bids.map((bid) => [bid.bidder, bid])); + beforeEach(function () { + adUnits = deepClone(basic_AdUnit); + adUnitDetail = { + activeViewport: [560, 260], + transformedMediaTypes: { + banner: { + filteredSizeConfig: [ + { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } + ], + sizeConfig: [ + { minViewPort: [0, 0], sizes: [[]] }, + { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } + ], + sizes: [[300, 200], [400, 600]] + } + }, + isLabelActivated: true, + }; sinon .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', basic_AdUnit[0], []) - .returns(adUnitDetailFixture); + .withArgs(adUnits[0], []) + .callsFake(() => adUnitDetail); sinon.spy(internal, 'getRelevantMediaTypesForBidder'); @@ -1564,153 +1306,82 @@ describe('sizeMappingV2', function () { utils.logWarn.restore(); }); - it('should return an array of bids specific to the bidder', function () { - const expectedMediaTypes = { - banner: { - filteredSizeConfig: [ - { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } - ], - sizeConfig: [ - { minViewPort: [0, 0], sizes: [[]] }, - { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } - ], - sizes: [[300, 200], [400, 600]] - } + it('should update adUnit mediaTypes', function () { + adUnitDetail = { + activeViewport: [560, 260], + transformedMediaTypes: { + banner: { + filteredSizeConfig: [ + { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } + ], + sizeConfig: [ + { minViewPort: [0, 0], sizes: [[]] }, + { minViewPort: [500, 0], sizes: [[300, 200], [400, 600]] } + ], + sizes: [[300, 200], [400, 600]] + } + }, + isLabelActivated: true, }; - const bidRequests_1 = getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); - expect(bidRequests_1[0].mediaTypes).to.deep.equal(expectedMediaTypes); - expect(bidRequests_1[0].bidder).to.equal('appnexus'); - - const bidRequests_2 = getBids({ - bidderCode: 'rubicon', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0aa', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); - expect(bidRequests_2[0]).to.be.undefined; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; + + expect(actual.mediaTypes).to.deep.equal(adUnitDetail.transformedMediaTypes); + const bids = bidderMap(actual); + expect(bids.appnexus).to.not.be.undefined; + expect(bids.appnexus.mediaTypes).to.be.undefined; + expect(bids.rubicon).to.be.undefined; sinon.assert.callCount(internal.getRelevantMediaTypesForBidder, 1); }); it('should log an error message if ad unit is disabled because there are no active media types left after size config filtration', function () { - internal.getAdUnitDetail.restore(); - - const adUnit = utils.deepClone(basic_AdUnit); - adUnit[0].mediaTypes.banner.sizeConfig = [ + adUnits[0].mediaTypes.banner.sizeConfig = [ { minViewPort: [0, 0], sizes: [] }, { minViewPort: [600, 0], sizes: [[300, 200], [400, 600]] } ]; - const adUnitDetailFixture = { - adUnitCode: 'adUnit1', - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [600, 0], sizes: [[300, 200], [400, 600]] } - ] - } - }, - sizeBucketToSizeMap: { - banner: { - activeSizeBucket: [0, 0], - activeSizeDimensions: [[]] - } - }, + adUnitDetail = { activeViewport: [560, 260], transformedMediaTypes: {}, isLabelActivated: true, - instance: 1, - cacheHits: 0 }; - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0]) - .returns(adUnitDetailFixture); - - const bidRequests = getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: adUnit, - labels: [], - src: 'client' - }); - expect(bidRequests[0]).to.be.undefined; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; + expect(actual).to.be.undefined; sinon.assert.callCount(utils.logInfo, 1); sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1) => Ad unit disabled since there are no active media types after sizeConfig filtration.`); }); it('should throw an error if bidder level sizeConfig is not configured properly', function () { - internal.getAdUnitDetail.restore(); - - const adUnit = utils.deepClone(basic_AdUnit); - adUnit[0].bids[1].sizeConfig = [ + adUnits[0].bids[1].sizeConfig = [ { minViewPort: [], relevantMediaTypes: ['none'] }, { minViewPort: [700, 0], relevantMediaTypes: ['banner'] } ]; - - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0]) - .returns(adUnitDetailFixture); - - const bidRequests = getBids({ - bidderCode: 'rubicon', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: adUnit, - labels: [], - src: 'client' - }); - - expect(bidRequests[0]).to.not.be.undefined; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; + expect(actual).to.not.be.undefined; + const bids = bidderMap(actual); + expect(bids.rubicon.mediaTypes).to.be.undefined; sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Size Mapping V2:: Ad Unit: adUnit1(1), Bidder: rubicon => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remail active.`); + sinon.assert.calledWith(utils.logError, `Size Mapping V2:: Ad Unit: adUnit1(1), Bidder: rubicon => 'sizeConfig' is not configured properly. This bidder won't be eligible for sizeConfig checks and will remain active.`); }); - it('should ensure bidder relevantMediaTypes is a subset of active media types at the ad unit level', function () { - internal.getAdUnitDetail.restore(); - - const adUnit = utils.deepClone(basic_AdUnit); - adUnit[0].bids[1].sizeConfig = [ + it('should ensure only relevant sizes are in adUnit.mediaTypes', function () { + adUnits[0].bids[1].sizeConfig = [ { minViewPort: [0, 0], relevantMediaTypes: ['none'] }, { minViewPort: [400, 0], relevantMediaTypes: ['banner'] } ]; - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0]) - .returns(adUnitDetailFixture); - - const bidRequests = getBids({ - bidderCode: 'rubicon', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: adUnit, - labels: [], - src: 'client' - }); - expect(bidRequests[0]).to.not.be.undefined; - expect(bidRequests[0].mediaTypes.banner).to.not.be.undefined; - expect(bidRequests[0].mediaTypes.banner.sizes).to.deep.equal([[300, 200], [400, 600]]); + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; + expect(actual).to.not.be.undefined; + const bids = bidderMap(actual); + expect(bids.rubicon.mediaTypes).to.be.undefined; + expect(bids.appnexus.mediaTypes).to.be.undefined; + expect(actual.mediaTypes.banner).to.not.be.undefined; + expect(actual.mediaTypes.banner.sizes).to.deep.equal([[300, 200], [400, 600]]); }); - it('should logInfo if bidder relevantMediaTypes contains media type that is not active at the ad unit level', function () { - internal.getAdUnitDetail.restore(); - - const adUnit = utils.deepClone(basic_AdUnit); - adUnit[0].mediaTypes = { + it('should remove bidder if its relevantMediaTypes contains media type that is not active at the ad unit level', function () { + adUnits[0].mediaTypes = { banner: { sizeConfig: [ { minViewPort: [0, 0], sizes: [] }, @@ -1725,60 +1396,22 @@ describe('sizeMappingV2', function () { } }; - adUnit[0].bids[1].sizeConfig = [ + adUnits[0].bids[1].sizeConfig = [ { minViewPort: [0, 0], relevantMediaTypes: ['none'] }, { minViewPort: [200, 0], relevantMediaTypes: ['banner'] } ]; - const adUnitDetailFixture = { - adUnitCode: 'adUnit1', - mediaTypes: { - banner: { - sizeConfig: [ - { minViewPort: [0, 0], sizes: [] }, - { minViewPort: [700, 0], sizes: [[300, 200], [400, 600]] } - ] - }, - native: { - sizeConfig: [ - { minViewPort: [0, 0], active: false }, - { minViewPort: [400, 0], active: true } - ] - } - }, - sizeBucketToSizeMap: { - banner: { - activeSizeBucket: [0, 0], - activeSizeDimensions: [[]] - }, - native: { - activeSizeBucket: [400, 0], - activeSizeDimensions: 'NA' - } - }, + adUnitDetail = { activeViewport: [560, 260], transformedMediaTypes: { native: {} }, isLabelActivated: true, - instance: 1, - cacheHits: 0 }; - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', adUnit[0], []) - .returns(adUnitDetailFixture); - - const bidRequests = getBids({ - bidderCode: 'rubicon', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: adUnit, - labels: [], - src: 'client' - }); - expect(bidRequests[0]).to.be.undefined; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; + const bids = bidderMap(actual); + expect(bids.rubicon).to.be.undefined; sinon.assert.callCount(utils.logInfo, 1); sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1), Bidder: rubicon => 'relevantMediaTypes' does not match with any of the active mediaTypes at the Ad Unit level. This bidder is disabled.`); }); @@ -1788,38 +1421,19 @@ describe('sizeMappingV2', function () { .stub(utils, 'isValidMediaTypes') .returns(false); - getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); + try { + setupAdUnitMediaTypes(adUnits, []); + } finally { + utils.isValidMediaTypes.restore(); + } + sinon.assert.callCount(utils.logWarn, 1); sinon.assert.calledWith(utils.logWarn, `Size Mapping V2:: Ad Unit: adUnit1 => Ad unit has declared invalid 'mediaTypes' or has not declared a 'mediaTypes' property`); - - utils.isValidMediaTypes.restore(); }); it('should log a message if ad unit is disabled due to a failing label check', function () { - internal.getAdUnitDetail.restore(); - const adUnitDetail = Object.assign({}, adUnitDetailFixture); adUnitDetail.isLabelActivated = false; - sinon - .stub(internal, 'getAdUnitDetail') - .withArgs('6d51e2d7-1447-4242-b6af-aaa5525a2c6e', basic_AdUnit[0], []) - .returns(adUnitDetail); - - getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); - + setupAdUnitMediaTypes(adUnits, []); sinon.assert.callCount(utils.logInfo, 1); sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1) => Ad unit is disabled due to failing label check.`); }); @@ -1827,19 +1441,25 @@ describe('sizeMappingV2', function () { it('should log a message if bidder is disabled due to a failing label check', function () { const stub = sinon.stub(internal, 'isLabelActivated').returns(false); - getBids({ - bidderCode: 'appnexus', - auctionId: '6d51e2d7-1447-4242-b6af-aaa5525a2c6e', - bidderRequestId: '393a43193a0ac', - adUnits: basic_AdUnit, - labels: [], - src: 'client' - }); + try { + setupAdUnitMediaTypes(adUnits, []); + } finally { + stub.restore(); + } - sinon.assert.callCount(utils.logInfo, 1); + sinon.assert.callCount(utils.logInfo, 2); // called once for each bidder sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: adUnit1(1), Bidder: appnexus => Label check for this bidder has failed. This bidder is disabled.`); + }); - internal.isLabelActivated.restore(); - }) + it('should set adUnit.bids[].mediaTypes if the bid mediaTypes should differ from the adUnit', () => { + adUnits[0].mediaTypes.native = {}; + adUnits[0].bids[1].sizeConfig = [ + { minViewPort: [0, 0], relevantMediaTypes: ['banner'] } + ]; + adUnitDetail.transformedMediaTypes.native = {}; + const actual = setupAdUnitMediaTypes(adUnits, [])[0]; + const bids = bidderMap(actual); + expect(bids.rubicon.mediaTypes).to.deep.equal({banner: adUnitDetail.transformedMediaTypes.banner}); + }); }); }); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index faa288306a5..38df03652b1 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -127,6 +127,15 @@ describe('smaatoBidAdapterTest', () => { } }; + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }) + it('auction type is 1 (first price auction)', () => { const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); @@ -287,6 +296,23 @@ describe('smaatoBidAdapterTest', () => { expect(req.regs.ext.us_privacy).to.equal('uspConsentString'); }); + it('sends no schain if no schain exists', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.source.ext.schain).to.not.exist; + }); + + it('sends instl if instl exists', () => { + const instl = { instl: 1 }; + const bidRequestWithInstl = Object.assign({}, singleBannerBidRequest, {ortb2Imp: instl}); + + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + }); + it('sends tmax', () => { const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); @@ -302,12 +328,15 @@ describe('smaatoBidAdapterTest', () => { }); it('sends first party data', () => { - this.sandbox = sinon.sandbox.create() - this.sandbox.stub(config, 'getConfig').callsFake(key => { + sandbox.stub(config, 'getConfig').callsFake(key => { const config = { ortb2: { site: { - keywords: 'power tools,drills' + keywords: 'power tools,drills', + publisher: { + id: 'otherpublisherid', + name: 'publishername' + } }, user: { keywords: 'a,b', @@ -328,7 +357,6 @@ describe('smaatoBidAdapterTest', () => { expect(req.user.ext.consent).to.equal(CONSENT_STRING); expect(req.site.keywords).to.eql('power tools,drills'); expect(req.site.publisher.id).to.equal('publisherId'); - this.sandbox.restore(); }); it('has no user ids', () => { @@ -435,6 +463,16 @@ describe('smaatoBidAdapterTest', () => { expect(req.imp[0].bidfloor).to.be.equal(0.456); }); + it('sends instl if instl exists', () => { + const instl = { instl: 1 }; + const bidRequestWithInstl = Object.assign({}, singleVideoBidRequest, {ortb2Imp: instl}); + + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + }); + it('splits multi format bid requests', () => { const combinedBannerAndVideoBidRequest = { bidder: 'smaato', @@ -516,6 +554,17 @@ describe('smaatoBidAdapterTest', () => { expect(req.imp[1].video.sequence).to.be.equal(2); }); + it('sends instl if instl exists', () => { + const instl = { instl: 1 }; + const bidRequestWithInstl = Object.assign({}, longFormVideoBidRequest, {ortb2Imp: instl}); + + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + expect(req.imp[1].instl).to.equal(1); + }); + it('sends bidfloor when configured', () => { const longFormVideoBidRequestWithFloor = Object.assign({}, longFormVideoBidRequest); longFormVideoBidRequestWithFloor.getFloor = function(arg) { @@ -854,6 +903,29 @@ describe('smaatoBidAdapterTest', () => { expect(req.user.ext.eids).to.have.length(2); }); }); + + describe('schain in request', () => { + it('schain is added to source.ext.schain', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'asi', + 'sid': 'sid', + 'rid': 'rid', + 'hp': 1 + } + ] + }; + const bidRequestWithSchain = Object.assign({}, singleBannerBidRequest, {schain: schain}); + + const reqs = spec.buildRequests([bidRequestWithSchain], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.source.ext.schain).to.deep.equal(schain); + }); + }); }); describe('interpretResponse', () => { diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js new file mode 100644 index 00000000000..05fb1424dca --- /dev/null +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -0,0 +1,392 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/smarthubBidAdapter'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'smarthub' + +describe('SmartHubBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + partnerName: 'testname', + seat: 'testSeat', + token: 'testBanner', + iabCat: ['IAB1-1', 'IAB3-1', 'IAB4-3'], + minBidfloor: 10, + pos: 1, + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60, + } + }, + params: { + partnerName: 'testname', + seat: 'testSeat', + token: 'testVideo', + iabCat: ['IAB1-1', 'IAB3-1', 'IAB4-3'], + minBidfloor: 10, + pos: 1, + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + partnerName: 'testname', + seat: 'testSeat', + token: 'testToken', + iabCat: ['IAB1-1', 'IAB3-1', 'IAB4-3'], + minBidfloor: 10, + pos: 1, + } + } + ]; + + 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://testname-prebid.smart-hub.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.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.partnerName).to.be.a('string'); + expect(placement.seat).to.be.a('string'); + expect(placement.token).to.be.a('string'); + expect(placement.iabCat).to.be.an('array'); + expect(placement.minBidfloor).to.be.a('number'); + expect(placement.pos).to.be.within(0, 7); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + + 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[0].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[0].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); + expect(serverRequest).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.property('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + width: 300, + height: 250, + 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', 'width', 'height'); + 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.property('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.property('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/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 4e560c87df3..ddee2fa3347 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -189,6 +189,14 @@ describe('The smartx adapter', function () { domain: '', publisher: { id: '__name__' + }, + content: { + ext: { + prebid: { + name: 'pbjs', + version: '$prebid.version$' + } + } } }); }); @@ -525,6 +533,7 @@ describe('The smartx adapter', function () { bidderRequestObj.bidRequest.bids[0].params.outstream_options.title = 'abc'; bidderRequestObj.bidRequest.bids[0].params.outstream_options.skipOffset = 2; bidderRequestObj.bidRequest.bids[0].params.outstream_options.desiredBitrate = 123; + bidderRequestObj.bidRequest.bids[0].params.outstream_options.visibilityThreshold = 30; responses[0].renderer.render(responses[0]); diff --git a/test/spec/modules/sortableAnalyticsAdapter_spec.js b/test/spec/modules/sortableAnalyticsAdapter_spec.js index 258c4b8f74d..9300756eae2 100644 --- a/test/spec/modules/sortableAnalyticsAdapter_spec.js +++ b/test/spec/modules/sortableAnalyticsAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import sortableAnalyticsAdapter, {TIMEOUT_FOR_REGISTRY, DEFAULT_PBID_TIMEOUT} from 'modules/sortableAnalyticsAdapter.js'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import * as prebidGlobal from 'src/prebidGlobal.js'; import {server} from 'test/mocks/xhr.js'; @@ -148,7 +148,6 @@ describe('Sortable Analytics Adapter', function() { } } }); - sortableAnalyticsAdapter.enableAnalytics(initialConfig); }); @@ -203,11 +202,11 @@ describe('Sortable Analytics Adapter', function() { brc: 1, brid: ['10141593b1d84a', '37a8760be6db23'], rs: ['300x250', '728x90'], - btcp: [0.70, 0.50], + btcp: [0.70, 0.50].map(n => n * 0.95), btcc: 'USD', btin: true, btsrc: 'sortable', - c: [0.70, 0.50], + c: [0.70, 0.50].map(n => n * 0.95), cc: 'USD', did: null, inr: true, diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 729c48c28f4..09a61c82b6c 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -5,7 +5,7 @@ import * as utils from 'src/utils.js' const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; -const adUnitBidRequest = { +const baseBidRequest = { 'bidder': 'sovrn', 'params': { 'tagid': 403370 @@ -19,7 +19,7 @@ const adUnitBidRequest = { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', } -const bidderRequest = { +const baseBidderRequest = { refererInfo: { referer: 'http://example.com/page.html', } @@ -28,28 +28,35 @@ const bidderRequest = { describe('sovrnBidAdapter', function() { describe('isBidRequestValid', function () { it('should return true when required params found', function () { - expect(spec.isBidRequestValid(adUnitBidRequest)).to.equal(true); + expect(spec.isBidRequestValid(baseBidRequest)).to.equal(true); }); it('should return false when tagid not passed correctly', function () { - const bid = {...adUnitBidRequest} - const params = adUnitBidRequest.params - bid.params = {...params} - bid.params.tagid = 'ABCD' - expect(spec.isBidRequestValid(bid)).to.equal(false) + const bidRequest = { + ...baseBidRequest, + 'params': { + ...baseBidRequest.params, + 'tagid': 'ABCD' + }, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); it('should return false when require params are not passed', function () { - const bid = {...adUnitBidRequest} - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + const bidRequest = { + ...baseBidRequest, + 'params': {} + } + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); }); describe('buildRequests', function () { describe('basic bid parameters', function() { - const bidRequests = [adUnitBidRequest]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests([baseBidRequest], baseBidderRequest); + const payload = JSON.parse(request.data); it('sends bid request to our endpoint via POST', function () { expect(request.method).to.equal('POST'); @@ -60,15 +67,65 @@ describe('sovrnBidAdapter', function() { }); it('sets the proper banner object', function() { + const bannerBidRequest = { + ...baseBidRequest, + 'mediaTypes': { + banner: {} + } + } + const request = spec.buildRequests([bannerBidRequest], baseBidderRequest) + + const payload = JSON.parse(request.data) + const impression = payload.imp[0] + + expect(impression.banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) + expect(impression.banner.w).to.equal(1) + expect(impression.banner.h).to.equal(1) + }) + + it('sets the proper video object', function() { + const width = 640 + const height = 480 + const mimes = ['video/mp4', 'application/javascript'] + const protocols = [2, 5] + const minduration = 5 + const maxduration = 60 + const startdelay = 0 + const videoBidRequest = { + ...baseBidRequest, + 'mediaTypes': { + video: { + mimes, + protocols, + playerSize: [[width, height], [360, 240]], + minduration, + maxduration, + startdelay + } + } + } + const request = spec.buildRequests([videoBidRequest], baseBidderRequest) + const payload = JSON.parse(request.data) - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) - expect(payload.imp[0].banner.w).to.equal(1) - expect(payload.imp[0].banner.h).to.equal(1) + const impression = payload.imp[0] + + expect(impression.video.w).to.equal(width) + expect(impression.video.h).to.equal(height) + expect(impression.video.mimes).to.have.same.members(mimes) + expect(impression.video.protocols).to.have.same.members(protocols) + expect(impression.video.minduration).to.equal(minduration) + expect(impression.video.maxduration).to.equal(maxduration) + expect(impression.video.startdelay).to.equal(startdelay) }) - it('includes the ad unit code int the request', function() { - const payload = JSON.parse(request.data); - expect(payload.imp[0].adunitcode).to.equal('adunit-code') + it('gets correct site info', function() { + expect(payload.site.page).to.equal('http://example.com/page.html'); + expect(payload.site.domain).to.equal('example.com'); + }); + + it('includes the ad unit code in the request', function() { + const impression = payload.imp[0] + expect(impression.adunitcode).to.equal('adunit-code') }) it('converts tagid to string', function () { @@ -77,109 +134,79 @@ describe('sovrnBidAdapter', function() { }) it('accepts a single array as a size', function() { - const singleSize = [{ - 'bidder': 'sovrn', + const singleSizeBidRequest = { + ...baseBidRequest, 'params': { - 'tagid': '403370', 'iv': 'vet' }, - 'adUnitCode': 'adunit-code', 'sizes': [300, 250], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }] - const request = spec.buildRequests(singleSize, bidderRequest) + 'mediaTypes': { + banner: {} + }, + } + const request = spec.buildRequests([singleSizeBidRequest], baseBidderRequest) + const payload = JSON.parse(request.data) - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]) - expect(payload.imp[0].banner.w).to.equal(1) - expect(payload.imp[0].banner.h).to.equal(1) + const impression = payload.imp[0] + + expect(impression.banner.format).to.deep.equal([{w: 300, h: 250}]) + expect(impression.banner.w).to.equal(1) + expect(impression.banner.h).to.equal(1) }) it('sends \'iv\' as query param if present', function () { - const ivBidRequests = [{ - 'bidder': 'sovrn', - 'params': { - 'tagid': '403370', - 'iv': 'vet' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - const bidderRequest = { - refererInfo: { - referer: 'http://example.com/page.html', + const ivBidRequest = { + ...baseBidRequest, + params: { + iv: 'vet' } - }; - const request = spec.buildRequests(ivBidRequests, bidderRequest); + } + const request = spec.buildRequests([ivBidRequest], baseBidderRequest) expect(request.url).to.contain('iv=vet') }); it('sends gdpr info if exists', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; const bidderRequest = { + ...baseBidderRequest, 'bidderCode': 'sovrn', 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - gdprConsent: { - consentString: consentString, - gdprApplies: true + 'gdprConsent': { + 'consentString': 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + 'gdprApplies': true }, - refererInfo: { - referer: 'http://example.com/page.html', - } + 'bids': [baseBidRequest] }; - bidderRequest.bids = [adUnitBidRequest]; - const data = JSON.parse(spec.buildRequests([adUnitBidRequest], bidderRequest).data); + const { regs, user } = JSON.parse(spec.buildRequests([baseBidRequest], 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); - }); + expect(regs.ext.gdpr).to.exist.and.to.be.a('number') + expect(regs.ext.gdpr).to.equal(1) + expect(user.ext.consent).to.exist.and.to.be.a('string') + expect(user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString) + }) it('should send us_privacy if bidderRequest has a value for uspConsent', function () { - const uspString = '1NYN'; const bidderRequest = { + ...baseBidderRequest, 'bidderCode': 'sovrn', 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - uspConsent: uspString, - refererInfo: { - referer: 'http://example.com/page.html', - } - }; - bidderRequest.bids = [adUnitBidRequest]; + 'uspConsent': '1NYN', + 'bids': [baseBidRequest] + } - const data = JSON.parse(spec.buildRequests([adUnitBidRequest], bidderRequest).data); + const data = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) - expect(data.regs.ext['us_privacy']).to.equal(uspString); - }); + expect(data.regs.ext['us_privacy']).to.equal(bidderRequest.uspConsent) + }) it('should add schain if present', function() { - const schainRequests = [{ - 'bidder': 'sovrn', - 'params': { - 'tagid': 403370 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + const schainRequest = { + ...baseBidRequest, 'schain': { 'ver': '1.0', 'complete': 1, @@ -192,100 +219,89 @@ describe('sovrnBidAdapter', function() { } ] } - }].concat(adUnitBidRequest); - const bidderRequest = { - refererInfo: { - referer: 'http://example.com/page.html', - } - }; - const data = JSON.parse(spec.buildRequests(schainRequests, bidderRequest).data); + } + const schainRequests = [schainRequest, baseBidRequest] + + const data = JSON.parse(spec.buildRequests(schainRequests, baseBidderRequest).data) expect(data.source.ext.schain.nodes.length).to.equal(1) - }); + }) - it('should add ids to the bid request', function() { - const criteoIdRequest = [{ - 'bidder': 'sovrn', - 'params': { - 'tagid': 403370 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'userId': { - 'criteoId': 'A_CRITEO_ID', - 'tdid': 'SOMESORTOFID', - } - }].concat(adUnitBidRequest); - const bidderRequest = { - refererInfo: { - referer: 'http://example.com/page.html', + it('should add eds to the bid request', function() { + const criteoIdRequest = { + ...baseBidRequest, + userId: { + criteoId: 'A_CRITEO_ID', + tdid: 'SOMESORTOFID', } - }; - - const data = JSON.parse(spec.buildRequests(criteoIdRequest, bidderRequest).data); - expect(data.user.ext.eids[0].source).to.equal('criteo.com') - expect(data.user.ext.eids[0].uids[0].id).to.equal('A_CRITEO_ID') - expect(data.user.ext.eids[0].uids[0].atype).to.equal(1) - expect(data.user.ext.eids[1].source).to.equal('adserver.org') - expect(data.user.ext.eids[1].uids[0].id).to.equal('SOMESORTOFID') - expect(data.user.ext.eids[1].uids[0].ext.rtiPartner).to.equal('TDID') - expect(data.user.ext.eids[1].uids[0].atype).to.equal(1) - expect(data.user.ext.tpid[0].source).to.equal('criteo.com') - expect(data.user.ext.tpid[0].uid).to.equal('A_CRITEO_ID') - expect(data.user.ext.prebid_criteoid).to.equal('A_CRITEO_ID') - }); + } + const criteoIdRequests = [criteoIdRequest, baseBidRequest] + + const ext = JSON.parse(spec.buildRequests(criteoIdRequests, baseBidderRequest).data).user.ext + const firstEID = ext.eids[0] + const secondEID = ext.eids[1] + + expect(firstEID.source).to.equal('criteo.com') + expect(firstEID.uids[0].id).to.equal('A_CRITEO_ID') + expect(firstEID.uids[0].atype).to.equal(1) + expect(secondEID.source).to.equal('adserver.org') + expect(secondEID.uids[0].id).to.equal('SOMESORTOFID') + expect(secondEID.uids[0].ext.rtiPartner).to.equal('TDID') + expect(secondEID.uids[0].atype).to.equal(1) + expect(ext.tpid[0].source).to.equal('criteo.com') + expect(ext.tpid[0].uid).to.equal('A_CRITEO_ID') + expect(ext.prebid_criteoid).to.equal('A_CRITEO_ID') + }) it('should ignore empty segments', function() { - const request = spec.buildRequests([adUnitBidRequest], bidderRequest) + const request = spec.buildRequests([baseBidRequest], baseBidderRequest) const payload = JSON.parse(request.data) + expect(payload.imp[0].ext).to.be.undefined }) it('should pass the segments param value as trimmed deal ids array', function() { - const segmentsRequests = [{ - 'bidder': 'sovrn', - 'params': { - 'segments': ' test1,test2 ' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }] - const request = spec.buildRequests(segmentsRequests, bidderRequest) - const payload = JSON.parse(request.data) - expect(payload.imp[0].ext.deals[0]).to.equal('test1') - expect(payload.imp[0].ext.deals[1]).to.equal('test2') + const segmentsRequest = { + ...baseBidRequest, + params: { + segments: ' test1,test2 ' + } + } + const request = spec.buildRequests([segmentsRequest], baseBidderRequest) + const deals = JSON.parse(request.data).imp[0].ext.deals + + expect(deals[0]).to.equal('test1') + expect(deals[1]).to.equal('test2') }) it('should use the floor provided from the floor module if present', function() { - const floorBid = {...adUnitBidRequest, getFloor: () => ({currency: 'USD', floor: 1.10})} - floorBid.params = { - tagid: 1234, - bidfloor: 2.00 + const floorBid = { + ...baseBidRequest, + getFloor: () => ({currency: 'USD', floor: 1.10}), + params: { + tagid: 1234, + bidfloor: 2.00 + } } - const request = spec.buildRequests([floorBid], bidderRequest) + + const request = spec.buildRequests([floorBid], baseBidderRequest) const payload = JSON.parse(request.data) + expect(payload.imp[0].bidfloor).to.equal(1.10) }) it('should use the floor from the param if there is no floor from the floor module', function() { - const floorBid = {...adUnitBidRequest, getFloor: () => ({})} + const floorBid = { + ...baseBidRequest, + getFloor: () => ({}) + } floorBid.params = { tagid: 1234, bidfloor: 2.00 } - const request = spec.buildRequests([floorBid], bidderRequest) - const payload = JSON.parse(request.data) - expect(payload.imp[0].bidfloor).to.equal(2.00) + + const request = spec.buildRequests([floorBid], baseBidderRequest) + const impression = JSON.parse(request.data).imp[0] + + expect(impression.bidfloor).to.equal(2.00) }) describe('First Party Data', function () { let sandbox @@ -307,54 +323,79 @@ describe('sovrnBidAdapter', function() { data: 'some user data' } } - }; - return utils.deepAccess(cfg, key); - }); - const request = spec.buildRequests([adUnitBidRequest], bidderRequest) - const payload = JSON.parse(request.data) - expect(payload.user.data).to.equal('some user data') - expect(payload.site.keywords).to.equal('test keyword') - expect(payload.site.page).to.equal('http://example.com/page.html') - expect(payload.site.domain).to.equal('example.com') + } + return utils.deepAccess(cfg, key) + }) + + const request = spec.buildRequests([baseBidRequest], baseBidderRequest) + const { user, site } = JSON.parse(request.data) + + expect(user.data).to.equal('some user data') + expect(site.keywords).to.equal('test keyword') + expect(site.page).to.equal('http://example.com/page.html') + expect(site.domain).to.equal('example.com') }) it('should append impression first party data', function () { - const fpdBid = {...adUnitBidRequest} - fpdBid.ortb2Imp = { - ext: { - data: { - pbadslot: 'homepage-top-rect', - adUnitSpecificAttribute: '123' + const fpdBidRequest = { + ...baseBidRequest, + ortb2Imp: { + ext: { + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } } } } - const request = spec.buildRequests([fpdBid], bidderRequest) + + const request = spec.buildRequests([fpdBidRequest], baseBidderRequest) const payload = JSON.parse(request.data) + expect(payload.imp[0].ext.data.pbadslot).to.equal('homepage-top-rect') expect(payload.imp[0].ext.data.adUnitSpecificAttribute).to.equal('123') }) it('should not overwrite deals when impression fpd is present', function() { - const fpdBid = {...adUnitBidRequest} - fpdBid.params = {...adUnitBidRequest.params} - fpdBid.params.segments = 'seg1, seg2' - fpdBid.ortb2Imp = { - ext: { - data: { - pbadslot: 'homepage-top-rect', - adUnitSpecificAttribute: '123' + const fpdBid = { + ...baseBidRequest, + params: { + segments: 'seg1, seg2' + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } } } } - const request = spec.buildRequests([fpdBid], bidderRequest) - const payload = JSON.parse(request.data) - expect(payload.imp[0].ext.data.pbadslot).to.equal('homepage-top-rect') - expect(payload.imp[0].ext.data.adUnitSpecificAttribute).to.equal('123') - expect(payload.imp[0].ext.deals).to.deep.equal(['seg1', 'seg2']) + + const request = spec.buildRequests([fpdBid], baseBidderRequest) + const impression = JSON.parse(request.data).imp[0] + + expect(impression.ext.data.pbadslot).to.equal('homepage-top-rect') + expect(impression.ext.data.adUnitSpecificAttribute).to.equal('123') + expect(impression.ext.deals).to.deep.equal(['seg1', 'seg2']) }) }) }); describe('interpretResponse', function () { let response; + const baseResponse = { + 'requestId': '263c448586f5a1', + 'cpm': 0.45882675, + 'width': 728, + 'height': 90, + 'creativeId': 'creativelycreatedcreativecreative', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': decodeURIComponent(``), + 'ttl': 90, + 'meta': { advertiserDomains: [] } + } beforeEach(function () { response = { body: { @@ -376,106 +417,175 @@ describe('sovrnBidAdapter', function() { }); it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 728, - 'height': 90, - 'creativeId': 'creativelycreatedcreativecreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', + const expectedResponse = { + ...baseResponse, 'ad': decodeURIComponent(`>`), 'ttl': 60000, - 'meta': { advertiserDomains: [] } - }]; + }; + + const result = spec.interpretResponse(response); - let result = spec.interpretResponse(response); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0]).to.have.deep.keys(expectedResponse) }); it('crid should default to the bid id if not on the response', function () { delete response.body.seatbid[0].bid[0].crid; - let expectedResponse = [{ - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 728, - 'height': 90, + + const expectedResponse = { + ...baseResponse, 'creativeId': response.body.seatbid[0].bid[0].id, - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', 'ad': decodeURIComponent(``), - 'ttl': 90, - 'meta': { advertiserDomains: [] } - }]; + } - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); + const result = spec.interpretResponse(response); + + expect(result[0]).to.deep.equal(expectedResponse); }); it('should get correct bid response when dealId is passed', function () { response.body.seatbid[0].bid[0].dealid = 'baking'; - - let expectedResponse = [{ - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 728, - 'height': 90, - 'creativeId': 'creativelycreatedcreativecreative', + const expectedResponse = { + ...baseResponse, 'dealId': 'baking', - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ad': decodeURIComponent(``), - 'ttl': 90, - 'meta': { advertiserDomains: [] } - }]; + } + + const result = spec.interpretResponse(response) - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); + expect(result[0]).to.deep.equal(expectedResponse); }); it('should get correct bid response when ttl is set', function () { - response.body.seatbid[0].bid[0].ext = { 'ttl': 480 }; - - let expectedResponse = [{ - 'requestId': '263c448586f5a1', - 'cpm': 0.45882675, - 'width': 728, - 'height': 90, - 'creativeId': 'creativelycreatedcreativecreative', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ad': decodeURIComponent(``), + response.body.seatbid[0].bid[0].ext = { 'ttl': 480 } + + const expectedResponse = { + ...baseResponse, 'ttl': 480, - 'meta': { advertiserDomains: [] } - }]; + } + + const result = spec.interpretResponse(response) + + expect(result[0]).to.deep.equal(expectedResponse) + }) - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); + it('handles empty bid response', function () { + const response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [] + } + }; + + const result = spec.interpretResponse(response) + + expect(result.length).to.equal(0); + }); + }); + + describe('interpretResponse video', function () { + let videoResponse; + const bidAdm = 'key%3Dvalue'; + const decodedBidAdm = decodeURIComponent(bidAdm); + const baseVideoResponse = { + 'requestId': '263c448586f5a1', + 'cpm': 0.45882675, + 'width': 640, + 'height': 480, + 'creativeId': 'creativelycreatedcreativecreative', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'video', + 'ttl': 90, + 'meta': { advertiserDomains: [] }, + 'vastXml': decodedBidAdm + } + beforeEach(function () { + videoResponse = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', + 'crid': 'creativelycreatedcreativecreative', + 'impid': '263c448586f5a1', + 'price': 0.45882675, + 'nurl': '', + 'adm': bidAdm, + 'h': 480, + 'w': 640 + }] + }] + } + }; }); + it('should get the correct bid response', function () { + const expectedResponse = { + ...baseVideoResponse, + 'ttl': 60000, + }; + + const result = spec.interpretResponse(videoResponse); + + expect(result[0]).to.have.deep.keys(expectedResponse) + }); + + it('crid should default to the bid id if not on the response', function () { + delete videoResponse.body.seatbid[0].bid[0].crid; + + const expectedResponse = { + ...baseVideoResponse, + 'creativeId': videoResponse.body.seatbid[0].bid[0].id, + } + + const result = spec.interpretResponse(videoResponse); + + expect(result[0]).to.deep.equal(expectedResponse); + }); + + it('should get correct bid response when dealId is passed', function () { + videoResponse.body.seatbid[0].bid[0].dealid = 'baking'; + const expectedResponse = { + ...baseVideoResponse, + 'dealId': 'baking', + } + + const result = spec.interpretResponse(videoResponse) + + expect(result[0]).to.deep.equal(expectedResponse); + }); + + it('should get correct bid response when ttl is set', function () { + videoResponse.body.seatbid[0].bid[0].ext = { 'ttl': 480 } + + const expectedResponse = { + ...baseVideoResponse, + 'ttl': 480, + } + + const result = spec.interpretResponse(videoResponse) + + expect(result[0]).to.deep.equal(expectedResponse) + }) + it('handles empty bid response', function () { - let response = { + const response = { body: { 'id': '37386aade21a71', 'seatbid': [] } }; - let result = spec.interpretResponse(response); + + const result = spec.interpretResponse(response) + expect(result.length).to.equal(0); }); }); describe('getUserSyncs ', function() { - let syncOptions = { iframeEnabled: true, pixelEnabled: false }; - let iframeDisabledSyncOptions = { iframeEnabled: false, pixelEnabled: false }; - let serverResponse = [ + const syncOptions = { iframeEnabled: true, pixelEnabled: false }; + const iframeDisabledSyncOptions = { iframeEnabled: false, pixelEnabled: false }; + const serverResponse = [ { 'body': { 'id': '546956d68c757f', @@ -524,14 +634,14 @@ describe('sovrnBidAdapter', function() { ]; it('should return if iid present on server response & iframe syncs enabled', function() { - const expectedReturnStatement = [ - { - 'type': 'iframe', - 'url': 'https://ap.lijit.com/beacon?informer=13487408', - } - ]; + const expectedReturnStatement = { + 'type': 'iframe', + 'url': 'https://ap.lijit.com/beacon?informer=13487408', + } + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse); - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); }); it('should include gdpr consent string if present', function() { @@ -539,26 +649,26 @@ describe('sovrnBidAdapter', function() { gdprApplies: 1, consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } - const expectedReturnStatement = [ - { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, - } - ]; + const expectedReturnStatement = { + 'type': 'iframe', + 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, + } + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, ''); - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); }); it('should include us privacy string if present', function() { const uspString = '1NYN'; - const expectedReturnStatement = [ - { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, - } - ]; + const expectedReturnStatement = { + 'type': 'iframe', + 'url': `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, + } + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString); - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement); }); it('should include all privacy strings if present', function() { @@ -567,57 +677,35 @@ describe('sovrnBidAdapter', function() { consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } const uspString = '1NYN'; - const expectedReturnStatement = [ - { - 'type': 'iframe', - 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, - } - ]; - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString); - expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + const expectedReturnStatement = { + 'type': 'iframe', + 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, + } + + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString) + + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) }); it('should not return if iid missing on server response', function() { const returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; }); it('should not return if iframe syncs disabled', function() { const returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse); + expect(returnStatement).to.be.empty; }); it('should include pixel syncs', function() { - let pixelEnabledOptions = { iframeEnabled: false, pixelEnabled: true }; - const resp2 = { + const pixelEnabledOptions = { iframeEnabled: false, pixelEnabled: true } + + const otherResponce = { + ...serverResponse, 'body': { - 'id': '546956d68c757f-2', - 'seatbid': [ - { - 'bid': [ - { - 'id': 'a_448326_16c2ada014224bee815a90d2248322f5-2', - 'impid': '2a3826aae345f4', - 'price': 1.0099999904632568, - 'nurl': 'http://localhost/rtb/impression?bannerid=220958&campaignid=3890&rtb_tid=15588614-75d2-40ab-b27e-13d2127b3c2e&rpid=1295&seatid=seat1&zoneid=448326&cb=26900712&tid=a_448326_16c2ada014224bee815a90d2248322f5', - 'adm': 'yo a creative', - 'crid': 'cridprebidrtb', - 'w': 160, - 'h': 600 - }, - { - 'id': 'a_430392_beac4c1515da4576acf6cb9c5340b40c-2', - 'impid': '3cf96fd26ed4c5', - 'price': 1.0099999904632568, - 'nurl': 'http://localhost/rtb/impression?bannerid=220957&campaignid=3890&rtb_tid=5bc0e68b-3492-448d-a6f9-26fa3fd0b646&rpid=1295&seatid=seat1&zoneid=430392&cb=62735099&tid=a_430392_beac4c1515da4576acf6cb9c5340b40c', - 'adm': 'yo a creative', - 'crid': 'cridprebidrtb', - 'w': 300, - 'h': 250 - }, - ] - } - ], + ...serverResponse.body, 'ext': { 'iid': 13487408, sync: { @@ -631,10 +719,11 @@ describe('sovrnBidAdapter', function() { ] } } - }, - 'headers': {} + } } - const returnStatement = spec.getUserSyncs(pixelEnabledOptions, [...serverResponse, resp2]); + + const returnStatement = spec.getUserSyncs(pixelEnabledOptions, [...serverResponse, otherResponce]) + expect(returnStatement.length).to.equal(4); expect(returnStatement).to.deep.include.members([ { type: 'image', url: 'http://idprovider1.com' }, @@ -642,34 +731,25 @@ describe('sovrnBidAdapter', function() { { type: 'image', url: 'http://idprovider3.com' }, { type: 'image', url: 'http://idprovider4.com' } ]); - }); - }); + }) + }) describe('prebid 3 upgrade', function() { - const bidRequests = [{ - 'bidder': 'sovrn', + const bidRequest = { + ...baseBidRequest, 'params': { 'tagid': '403370' }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [ + 'mediaTypes': { + 'banner': { + 'sizes': [ [300, 250], [300, 600] ] } }, - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - const bidderRequest = { - refererInfo: { - referer: 'http://example.com/page.html', - } }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests([bidRequest], baseBidderRequest); const payload = JSON.parse(request.data); it('gets sizes from mediaTypes.banner', function() { diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index 75a269c93f7..a0c4837bb51 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -514,8 +514,35 @@ describe('SSPBC adapter', function () { }); it('should send gdpr data', function () { - expect(payload.regs).to.be.an('object').and.to.have.property('[ortb_extensions.gdpr]', 1); - expect(payload.user).to.be.an('object').and.to.have.property('[ortb_extensions.consent]', bidRequest.gdprConsent.consentString); + expect(payload.regs).to.be.an('object').and.to.have.property('gdpr', 1); + expect(payload.user).to.be.an('object').and.to.have.property('consent', bidRequest.gdprConsent.consentString); + }); + + it('should send net info and pvid', function () { + expect(payload.user).to.be.an('object').and.to.have.property('data').that.is.an('array'); + + const userData = payload.user.data; + expect(userData.length).to.equal(2); + + const netInfo = userData[0]; + expect(netInfo.id).to.equal('12'); + expect(netInfo.name).to.equal('NetInfo'); + expect(netInfo).to.have.property('segment').that.is.an('array'); + + const pvid = userData[1]; + expect(pvid.id).to.equal('7'); + expect(pvid.name).to.equal('pvid'); + expect(pvid).to.have.property('segment').that.is.an('array'); + expect(pvid.segment[0]).to.have.property('value'); + }); + + it('pvid should be constant on a single page view', function () { + const userData1 = payload.user.data; + const userData2 = payloadNative.user.data; + const pvid1 = userData1[1]; + const pvid2 = userData2[1]; + + expect(pvid1.segment[0].value).to.equal(pvid2.segment[0].value); }); it('should build correct native payload', function () { @@ -543,13 +570,16 @@ describe('SSPBC adapter', function () { expect(videoAssets).to.have.property('api').that.is.an('array'); }); - it('should create auxilary placement identifier (size_numUsed)', function () { + it('should create auxilary placement identifier (size_numUsed), that is constant for a given adUnit', function () { const extAssets1 = payload.imp && payload.imp[0].ext.data; const extAssets2 = payloadSingle.imp && payloadSingle.imp[0].ext.data; - // note that payload comes from first, and payloadSingle from second auction in the test run + /* + note that payload comes from first, and payloadSingle from second auction in the test run + also, since both have same adUnitName, value of pbsize property should be the same + */ expect(extAssets1).to.have.property('pbsize').that.equals('750x200_1') - expect(extAssets2).to.have.property('pbsize').that.equals('750x200_2') + expect(extAssets2).to.have.property('pbsize').that.equals('750x200_1') }); }); @@ -623,7 +653,7 @@ describe('SSPBC adapter', function () { let nativeBid = resultNative[0]; expect(nativeBid).to.have.keys('bidderCode', 'cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'native'); - expect(nativeBid.native).to.have.keys('image', 'icon', 'title', 'sponsoredBy', 'body', 'clickUrl', 'impressionTrackers'); + expect(nativeBid.native).to.have.keys('image', 'icon', 'title', 'sponsoredBy', 'body', 'clickUrl', 'impressionTrackers', 'javascriptTrackers'); }); }); @@ -638,8 +668,8 @@ describe('SSPBC adapter', function () { }); it('should send no syncs, if frame sync is not allowed', function () { - expect(syncResultImage).to.be.undefined; - expect(syncResultNone).to.be.undefined; + expect(syncResultImage).to.have.length(0); ; + expect(syncResultNone).to.have.length(0); ; }); }); @@ -656,7 +686,7 @@ describe('SSPBC adapter', function () { let notificationPayload = spec.onBidWon(bid); expect(notificationPayload).to.have.property('event').that.equals('bidWon'); expect(notificationPayload).to.have.property('requestId').that.equals(bid.auctionId); - expect(notificationPayload).to.have.property('adUnit').that.deep.equals([bid.adUnitCode]); + expect(notificationPayload).to.have.property('tagid').that.deep.equals([bid.adUnitCode]); expect(notificationPayload).to.have.property('siteId').that.is.an('array'); expect(notificationPayload).to.have.property('slotId').that.is.an('array'); }); @@ -677,7 +707,7 @@ describe('SSPBC adapter', function () { expect(notificationPayload).to.have.property('event').that.equals('timeout'); expect(notificationPayload).to.have.property('requestId').that.equals(bids_timeouted[0].auctionId); - expect(notificationPayload).to.have.property('adUnit').that.deep.equals([bids_timeouted[0].adUnitCode, bids_timeouted[1].adUnitCode]); + expect(notificationPayload).to.have.property('tagid').that.deep.equals([bids_timeouted[0].adUnitCode, bids_timeouted[1].adUnitCode]); }); }); }); diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index 6f24da85cfe..e723523de31 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -2,7 +2,7 @@ import {assert} from 'chai'; import {spec} from 'modules/stroeerCoreBidAdapter.js'; import * as utils from 'src/utils.js'; import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; describe('stroeerCore bid adapter', function () { let sandbox; diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index 5f3633ec311..b9a02799219 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -244,6 +244,13 @@ describe('synacormediaBidAdapter ', function () { rtiPartner: 'TDID' } }] + }, + { + source: 'neustar.biz', + uids: [{ + id: 'neustar809-044-23njhwer3', + atype: 1 + }] } ]; @@ -989,7 +996,7 @@ describe('synacormediaBidAdapter ', function () { netRevenue: true, mediaType: 'video', ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', - ttl: 60, + ttl: 420, meta: { advertiserDomains: ['psacentral.org'] }, videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' @@ -1010,7 +1017,7 @@ describe('synacormediaBidAdapter ', function () { netRevenue: true, mediaType: BANNER, ad: '', - ttl: 60 + ttl: 420 }); }); @@ -1032,7 +1039,7 @@ describe('synacormediaBidAdapter ', function () { netRevenue: true, mediaType: BANNER, ad: '', - ttl: 60 + ttl: 420 }); expect(resp[1]).to.eql({ @@ -1045,7 +1052,7 @@ describe('synacormediaBidAdapter ', function () { netRevenue: true, mediaType: BANNER, ad: '', - ttl: 60 + ttl: 420 }); }); @@ -1156,7 +1163,7 @@ describe('synacormediaBidAdapter ', function () { netRevenue: true, mediaType: 'video', ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', - ttl: 60, + ttl: 420, meta: { advertiserDomains: ['psacentral.org'] }, videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' @@ -1209,9 +1216,63 @@ describe('synacormediaBidAdapter ', function () { netRevenue: true, mediaType: BANNER, ad: '', - ttl: 60 + ttl: 420 }); }); + + it('should return ttl equal to DEFAULT_TTL_MAX if bid.exp and bid.ext["imds.tv"].ttl are both undefined', function() { + const br = { ...bidResponse }; + serverResponse.body.seatbid[0].bid.push(br); + const resp = spec.interpretResponse(serverResponse, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.have.property('ttl'); + expect(resp[0].ttl).to.equal(420); + }); + + it('should return ttl equal to bid.ext["imds.tv"].ttl if it is defined but bid.exp is undefined', function() { + let br = { ext: { 'imds.tv': { ttl: 4321 } }, ...bidResponse }; + serverResponse.body.seatbid[0].bid.push(br); + let resp = spec.interpretResponse(serverResponse, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.have.property('ttl'); + expect(resp[0].ttl).to.equal(4321); + }); + + it('should return ttl equal to bid.exp if bid.exp is less than or equal to DEFAULT_TTL_MAX and bid.ext["imds.tv"].ttl is undefined', function() { + const br = { exp: 123, ...bidResponse }; + serverResponse.body.seatbid[0].bid.push(br); + const resp = spec.interpretResponse(serverResponse, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.have.property('ttl'); + expect(resp[0].ttl).to.equal(123); + }); + + it('should return ttl equal to DEFAULT_TTL_MAX if bid.exp is greater than DEFAULT_TTL_MAX and bid.ext["imds.tv"].ttl is undefined', function() { + const br = { exp: 4321, ...bidResponse }; + serverResponse.body.seatbid[0].bid.push(br); + const resp = spec.interpretResponse(serverResponse, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.have.property('ttl'); + expect(resp[0].ttl).to.equal(420); + }); + + it('should return ttl equal to bid.exp if bid.exp is less than or equal to bid.ext["imds.tv"].ttl', function() { + const br = { exp: 1234, ext: { 'imds.tv': { ttl: 4321 } }, ...bidResponse }; + serverResponse.body.seatbid[0].bid.push(br); + const resp = spec.interpretResponse(serverResponse, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.have.property('ttl'); + expect(resp[0].ttl).to.equal(1234); + }); + + it('should return ttl equal to bid.ext["imds.tv"].ttl if bid.exp is greater than bid.ext["imds.tv"].ttl', function() { + const br = { exp: 4321, ext: { 'imds.tv': { ttl: 1234 } }, ...bidResponse }; + serverResponse.body.seatbid[0].bid.push(br); + const resp = spec.interpretResponse(serverResponse, bidRequest); + expect(resp).to.be.an('array').to.have.lengthOf(1); + expect(resp[0]).to.have.property('ttl'); + expect(resp[0].ttl).to.equal(1234); + }); }); describe('getUserSyncs', function () { it('should return a usersync when iframes is enabled', function () { @@ -1231,4 +1292,74 @@ describe('synacormediaBidAdapter ', function () { expect(usersyncs).to.be.an('array').that.is.empty; }); }); + + describe('Bid Requests with price module should use if available', function () { + let validVideoBidRequest = { + bidder: 'synacormedia', + params: { + bidfloor: '0.50', + seatId: 'prebid', + placementId: 'demo1', + pos: 1, + video: {} + }, + renderer: { + url: '../syncOutstreamPlayer.js' + }, + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream' + } + }, + adUnitCode: 'div-1', + transactionId: '0869f34e-090b-4b20-84ee-46ff41405a39', + sizes: [[300, 250]], + bidId: '22b3a2268d9f0e', + bidderRequestId: '1d195910597e13', + auctionId: '3375d336-2aea-4ee7-804c-6d26b621ad20', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }; + + let validBannerBidRequest = { + bidId: '9876abcd', + sizes: [[300, 250]], + params: { + bidfloor: '0.50', + seatId: 'prebid', + placementId: '1234', + } + }; + + let bidderRequest = { + refererInfo: { + referer: 'http://localhost:9999/' + }, + bidderCode: 'synacormedia', + auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110' + }; + + it('should return valid bidfloor using price module for banner/video impression', function () { + let bannerRequest = spec.buildRequests([validBannerBidRequest], bidderRequest); + let videoRequest = spec.buildRequests([validVideoBidRequest], bidderRequest); + + expect(bannerRequest.data.imp[0].bidfloor).to.equal(0.5); + expect(videoRequest.data.imp[0].bidfloor).to.equal(0.5); + + let priceModuleFloor = 3; + let floorResponse = { currency: 'USD', floor: priceModuleFloor }; + + validBannerBidRequest.getFloor = () => { return floorResponse; }; + validVideoBidRequest.getFloor = () => { return floorResponse; }; + + bannerRequest = spec.buildRequests([validBannerBidRequest], bidderRequest); + videoRequest = spec.buildRequests([validVideoBidRequest], bidderRequest); + + expect(bannerRequest.data.imp[0].bidfloor).to.equal(priceModuleFloor); + expect(videoRequest.data.imp[0].bidfloor).to.equal(priceModuleFloor); + }); + }); }); diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index 3fe691847b6..8866670df77 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -134,12 +134,6 @@ describe('Tappx bid adapter', function () { assert.isTrue(spec.isBidRequestValid(c_BIDREQUEST.bids[0]), JSON.stringify(c_BIDREQUEST)); }); - it('should return false when params are missing', function () { - let badBidRequestParam = JSON.parse(JSON.stringify(c_BIDREQUEST)); - delete badBidRequestParam.bids[0].params; - assert.isFalse(spec.isBidRequestValid(badBidRequestParam.bids[0])); - }); - it('should return false when tappxkey is missing', function () { let badBidRequestTpxkey = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; delete badBidRequestTpxkey.bids[0].params.tappxkey; @@ -165,24 +159,18 @@ describe('Tappx bid adapter', function () { assert.isTrue(spec.isBidRequestValid(badBidRequestNwEp.bids[0])); }); - it('should return false mimes param is missing', function () { - let badBidRequest_mimes = c_BIDDERREQUEST_V; - delete badBidRequest_mimes.bids.mediaTypes.video; - badBidRequest_mimes.bids.mediaTypes.video = {}; - badBidRequest_mimes.bids.mediaTypes.video.context = 'instream'; - badBidRequest_mimes.bids.mediaTypes.video.playerSize = [320, 250]; - assert.isFalse(spec.isBidRequestValid(badBidRequest_mimes.bids), badBidRequest_mimes); - }); - it('should return false for not instream/outstream requests', function () { let badBidRequest_v = c_BIDDERREQUEST_V; delete badBidRequest_v.bids.mediaTypes.banner; badBidRequest_v.bids.mediaTypes.video = {}; badBidRequest_v.bids.mediaTypes.video.context = ''; - badBidRequest_v.bids.mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; badBidRequest_v.bids.mediaTypes.video.playerSize = [320, 250]; assert.isFalse(spec.isBidRequestValid(badBidRequest_v.bids)); }); + + it('should export the TCF vendor ID', function () { + expect(spec.gvlid).to.equal(628); + }) }); /** @@ -228,7 +216,6 @@ describe('Tappx bid adapter', function () { validBidRequests_V[0].mediaTypes.video = {}; validBidRequests_V[0].mediaTypes.video.playerSize = [640, 480]; validBidRequests_V[0].mediaTypes.video.context = 'instream'; - validBidRequests_V[0].mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; bidderRequest_V.bids.mediaTypes.context = 'instream'; @@ -248,7 +235,6 @@ describe('Tappx bid adapter', function () { validBidRequests_Voutstream[0].mediaTypes.video = {}; validBidRequests_Voutstream[0].mediaTypes.video.playerSize = [640, 480]; validBidRequests_Voutstream[0].mediaTypes.video.context = 'outstream'; - validBidRequests_Voutstream[0].mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; bidderRequest_VOutstream.bids.mediaTypes.context = 'outstream'; @@ -269,7 +255,6 @@ describe('Tappx bid adapter', function () { validBidRequests_Voutstream[0].mediaTypes.video.rewarded = 1; validBidRequests_Voutstream[0].mediaTypes.video.playerSize = [640, 480]; validBidRequests_Voutstream[0].mediaTypes.video.context = 'outstream'; - validBidRequests_Voutstream[0].mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; bidderRequest_VOutstream.bids.mediaTypes.context = 'outstream'; diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js new file mode 100644 index 00000000000..0ce6f0fb70d --- /dev/null +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -0,0 +1,96 @@ +import { spec } from '../../../modules/targetVideoBidAdapter.js' + +describe('TargetVideo Bid Adapter', function() { + const bannerRequest = [{ + bidder: 'targetVideo', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + params: { + placementId: 12345, + } + }]; + + it('Test the bid validation function', function() { + const validBid = spec.isBidRequestValid(bannerRequest[0]); + const invalidBid = spec.isBidRequestValid(null); + + expect(validBid).to.be.true; + expect(invalidBid).to.be.false; + }); + + it('Test the request processing function', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + expect(request).to.not.be.empty; + + const payload = JSON.parse(request.data); + expect(payload).to.not.be.empty; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + expect(payload.tags[0].id).to.equal(12345); + expect(payload.tags[0].gpid).to.equal('targetVideo'); + expect(payload.tags[0].ad_types[0]).to.equal('video'); + }); + + it('Handle nobid responses', function () { + const responseBody = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + const bidderRequest = null; + + const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + expect(bidResponse.length).to.equal(0); + }); + + it('Test the response parsing function', function () { + const responseBody = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'https://www.target-video.com/', + 'rtb': { + 'video': { + 'player_width': 640, + 'player_height': 360, + 'asset_url': 'https://www.target-video.com/' + } + } + }] + }] + }; + const bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }] + }; + + const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + expect(bidResponse).to.not.be.empty; + + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.cpm).to.equal(0.675); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ad).to.include('') + expect(bid.ad).to.include('initPlayer') + }); +}); diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 83f5045cca1..75ed7452b57 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -614,15 +614,13 @@ describe('teadsBidAdapter', () => { } ]; - it('should add gpid if ortb2Imp.ext.data.pbadslot is present and is non empty (and ortb2Imp.ext.data.adserver.adslot is not present)', function () { + it('should add gpid if ortb2Imp.ext.gpid is present and is non empty', function () { const updatedBidRequests = bidRequests.map(function(bidRequest, index) { return { ...bidRequest, ortb2Imp: { ext: { - data: { - pbadslot: '1111/home-left-' + index - } + gpid: '1111/home-left-' + index } } }; @@ -635,41 +633,12 @@ describe('teadsBidAdapter', () => { expect(payload.data[1].gpid).to.equal('1111/home-left-1'); }); - it('should add gpid if ortb2Imp.ext.data.pbadslot is present and is non empty (even if ortb2Imp.ext.data.adserver.adslot is present and is non empty too)', function () { - const updatedBidRequests = bidRequests.map(function(bidRequest, index) { - return { - ...bidRequest, - ortb2Imp: { - ext: { - data: { - pbadslot: '1111/home-left-' + index, - adserver: { - adslot: '1111/home-left/div-' + index - } - } - } - } - }; - } - ); - const request = spec.buildRequests(updatedBidRequests, bidderResquestDefault); - const payload = JSON.parse(request.data); - - expect(payload.data[0].gpid).to.equal('1111/home-left-0'); - expect(payload.data[1].gpid).to.equal('1111/home-left-1'); - }); - - it('should not add gpid if both ortb2Imp.ext.data.pbadslot and ortb2Imp.ext.data.adserver.adslot are present but empty', function () { + it('should not add gpid if ortb2Imp.ext.gpid is present but empty', function () { const updatedBidRequests = bidRequests.map(bidRequest => ({ ...bidRequest, ortb2Imp: { ext: { - data: { - pbadslot: '', - adserver: { - adslot: '' - } - } + gpid: '' } } })); @@ -682,36 +651,22 @@ describe('teadsBidAdapter', () => { }); }); - it('should not add gpid if both ortb2Imp.ext.data.pbadslot and ortb2Imp.ext.data.adserver.adslot are not present', function () { - const request = spec.buildRequests(bidRequests, bidderResquestDefault); + it('should not add gpid if ortb2Imp.ext.gpid is not present', function () { + const updatedBidRequests = bidRequests.map(bidRequest => ({ + ...bidRequest, + ortb2Imp: { + ext: { + } + } + })); + + const request = spec.buildRequests(updatedBidRequests, bidderResquestDefault); const payload = JSON.parse(request.data); return payload.data.forEach(bid => { expect(bid).not.to.have.property('gpid'); }); }); - - it('should add gpid if ortb2Imp.ext.data.pbadslot is not present but ortb2Imp.ext.data.adserver.adslot is present and is non empty', function () { - const updatedBidRequests = bidRequests.map(function(bidRequest, index) { - return { - ...bidRequest, - ortb2Imp: { - ext: { - data: { - adserver: { - adslot: '1111/home-left-' + index - } - } - } - } - }; - }); - const request = spec.buildRequests(updatedBidRequests, bidderResquestDefault); - const payload = JSON.parse(request.data); - - expect(payload.data[0].gpid).to.equal('1111/home-left-0'); - expect(payload.data[1].gpid).to.equal('1111/home-left-1'); - }); }); function checkMediaTypesSizes(mediaTypes, expectedSizes) { diff --git a/test/spec/modules/telariaBidAdapter_spec.js b/test/spec/modules/telariaBidAdapter_spec.js index 25649115cc1..457dd568764 100644 --- a/test/spec/modules/telariaBidAdapter_spec.js +++ b/test/spec/modules/telariaBidAdapter_spec.js @@ -234,9 +234,7 @@ describe('TelariaAdapter', () => { }]; it('should get correct bid response', () => { - let expectedResponseKeys = ['bidderCode', 'width', 'height', 'statusMessage', 'adId', 'mediaType', 'source', - 'getStatusCode', 'getSize', 'requestId', 'cpm', 'creativeId', 'vastXml', - 'vastUrl', 'currency', 'netRevenue', 'ttl', 'ad', 'meta']; + let expectedResponseKeys = ['requestId', 'cpm', 'creativeId', 'vastXml', 'vastUrl', 'mediaType', 'width', 'height', 'currency', 'netRevenue', 'ttl', 'ad', 'meta']; let bidRequest = spec.buildRequests(stub, BIDDER_REQUEST)[0]; bidRequest.bidId = '1234'; diff --git a/test/spec/modules/trustpidSystem_spec.js b/test/spec/modules/trustpidSystem_spec.js new file mode 100644 index 00000000000..97e87e3a303 --- /dev/null +++ b/test/spec/modules/trustpidSystem_spec.js @@ -0,0 +1,232 @@ +import { expect } from 'chai'; +import { trustpidSubmodule } from 'modules/trustpidSystem.js'; +import { storage } from 'modules/trustpidSystem.js'; + +describe('trustpid System', () => { + const connectDataKey = 'fcIdConnectData'; + const connectDomainKey = 'fcIdConnectDomain'; + + const getStorageData = (idGraph) => { + if (!idGraph) { + idGraph = {id: 501, domain: ''}; + } + return { + 'connectId': { + 'idGraph': [idGraph], + } + } + }; + + it('should have the correct module name declared', () => { + expect(trustpidSubmodule.name).to.equal('trustpid'); + }); + + describe('trustpid getId()', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(connectDataKey); + storage.removeDataFromLocalStorage(connectDomainKey); + }); + + it('it should return object with key callback', () => { + expect(trustpidSubmodule.getId()).to.have.property('callback'); + }); + + it('should return object with key callback with value type - function', () => { + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData())); + expect(trustpidSubmodule.getId()).to.have.property('callback'); + expect(typeof trustpidSubmodule.getId().callback).to.be.equal('function'); + }); + + it('tests if localstorage & JSON works properly ', () => { + const idGraph = { + 'domain': 'domainValue', + 'umid': 'umidValue', + }; + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + expect(JSON.parse(storage.getDataFromLocalStorage(connectDataKey))).to.have.property('connectId'); + }); + + it('returns {callback: func} if domains don\'t match', () => { + const idGraph = { + 'domain': 'domainValue', + 'umid': 'umidValue', + }; + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('differentDomainValue')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + expect(trustpidSubmodule.getId()).to.have.property('callback'); + }); + + it('returns {id: {trustpid: data.trustpid}} if we have the right data stored in the localstorage ', () => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('uat.mno.link')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('id'); + expect(response.id).to.have.property('trustpid'); + expect(response.id.trustpid).to.be.equal('umidValue-xxxx'); + }); + + it('returns {trustpid: data.trustpid} if we have the right data stored in the localstorage right after the callback is called', (done) => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('uat.mno.link')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + response.callback(function (result) { + expect(result).to.not.be.null; + expect(result).to.have.property('trustpid'); + expect(result.trustpid).to.be.equal('umidValue-xxxx'); + done() + }) + } + }); + + it('returns null if domains don\'t match', (done) => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('differentDomainValue')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + expect(JSON.parse(storage.getDataFromLocalStorage(connectDomainKey))).to.be.equal('differentDomainValue'); + }, 100) + response.callback(function (result) { + expect(result).to.be.null; + done() + }) + } + }); + + it('returns {trustpid: data.trustpid} if we have the right data stored in the localstorage right after 500ms delay', (done) => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('uat.mno.link')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + }, 500); + response.callback(function (result) { + expect(result).to.not.be.null; + expect(result).to.have.property('trustpid'); + expect(result.trustpid).to.be.equal('umidValue-xxxx'); + done() + }) + } + }); + + it('returns null if we have the data stored in the localstorage after 500ms delay and the max (waiting) delay is only 200ms ', (done) => { + const idGraph = { + 'domain': 'uat.mno.link', + 'umid': 'umidValue', + }; + + const response = trustpidSubmodule.getId({params: {maxDelayTime: 200}}); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('uat.mno.link')); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + }, 500); + response.callback(function (result) { + expect(result).to.be.null; + done() + }) + } + }); + }); + + describe('trustpid decode()', () => { + const VALID_API_RESPONSES = [ + { + expected: '32a97f612', + payload: { + trustpid: '32a97f612' + } + }, + { + expected: '32a97f61', + payload: { + trustpid: '32a97f61', + } + }, + ]; + VALID_API_RESPONSES.forEach(responseData => { + it('should return a newly constructed object with the trustpid for a payload with {trustpid: value}', () => { + expect(trustpidSubmodule.decode(responseData.payload)).to.deep.equal( + {trustpid: responseData.expected} + ); + }); + }); + + [{}, '', {foo: 'bar'}].forEach((response) => { + it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => { + expect(trustpidSubmodule.decode(response)).to.be.null; + }); + }); + }); + + describe('trustpid messageHandler for acronyms', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(connectDataKey); + storage.removeDataFromLocalStorage(connectDomainKey); + }); + + const domains = [ + {domain: 'tmi.mno.link', acronym: 'ndye'}, + {domain: 'tmi.vodafone.de', acronym: 'pqnx'}, + {domain: 'tmi.telekom.de', acronym: 'avgw'}, + {domain: 'tmi.tmid.es', acronym: 'kjws'}, + {domain: 'uat.mno.link', acronym: 'xxxx'}, + {domain: 'es.tmiservice.orange.com', acronym: 'aplw'}, + ]; + + domains.forEach(({domain, acronym}) => { + it(`correctly sets trustpid value and acronym to ${acronym} for ${domain} domain`, (done) => { + const idGraph = { + 'domain': domain, + 'umid': 'umidValue', + }; + + storage.setDataInLocalStorage(connectDomainKey, JSON.stringify(domain)); + storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); + + const eventData = { + data: `{\"msgType\":\"MNOSELECTOR\",\"body\":{\"url\":\"https://${domain}/some/path\"}}` + }; + + window.dispatchEvent(new MessageEvent('message', eventData)); + + const response = trustpidSubmodule.getId(); + expect(response).to.have.property('id'); + expect(response.id).to.have.property('trustpid'); + expect(response.id.trustpid).to.be.equal(`umidValue-${acronym}`); + done(); + }); + }); + }); +}); diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 73bc9d45365..b34813948fc 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -401,30 +401,68 @@ describe('TrustXAdapter', function () { expect(payload.site.content).to.deep.equal(jsContent); }); - it('if segment is present in permutive targeting, payload must have right params', function () { - const permSegments = [{id: 'test_perm_1'}, {id: 'test_perm_2'}]; - const bidRequestsWithPermutiveTargeting = bidRequests.map((bid) => { - return Object.assign({ - rtd: { - p_standard: { - targeting: { - segments: permSegments - } + it('should have user.data filled from config ortb2.user.data', function () { + const userData = [ + { + name: 'someName', + segment: [1, 2, { anyKey: 'anyVal' }, 'segVal', { id: 'segId' }, { value: 'segValue' }, { id: 'segId2', name: 'segName' }] + }, + { + name: 'permutive.com', + segment: [1, 2, 'segVal', { id: 'segId' }, { anyKey: 'anyVal' }, { value: 'segValue' }, { id: 'segId2', name: 'segName' }] + }, + { + someKey: 'another data' + } + ]; + + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user.data' ? userData : null); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const payload = parseRequest(request.data); + expect(payload.user.data).to.deep.equal(userData); + getConfigStub.restore(); + }); + + it('should have right value in user.data when jwpsegments are present', function () { + const userData = [ + { + name: 'someName', + segment: [1, 2, { anyKey: 'anyVal' }, 'segVal', { id: 'segId' }, { value: 'segValue' }, { id: 'segId2', name: 'segName' }] + }, + { + name: 'permutive.com', + segment: [1, 2, 'segVal', { id: 'segId' }, { anyKey: 'anyVal' }, { value: 'segValue' }, { id: 'segId2', name: 'segName' }] + }, + { + someKey: 'another data' + } + ]; + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user.data' ? userData : null); + + const jsContent = {id: 'test_jw_content_id'}; + const jsSegments = ['test_seg_1', 'test_seg_2']; + const bidRequestsWithJwTargeting = Object.assign({}, bidRequests[0], { + rtd: { + jwplayer: { + targeting: { + segments: jsSegments, + content: jsContent } } - }, bid); + } }); - const request = spec.buildRequests(bidRequestsWithPermutiveTargeting, bidderRequest); - expect(request.data).to.be.an('string'); + const request = spec.buildRequests([bidRequestsWithJwTargeting], bidderRequest); const payload = parseRequest(request.data); - expect(payload).to.have.property('user'); expect(payload.user.data).to.deep.equal([{ - name: 'permutive', + name: 'iow_labs_pub_data', segment: [ - {name: 'p_standard', value: permSegments[0].id}, - {name: 'p_standard', value: permSegments[1].id} + {name: 'jwpseg', value: jsSegments[0]}, + {name: 'jwpseg', value: jsSegments[1]} ] - }]); + }, ...userData]); + getConfigStub.restore(); }); it('should contain the keyword values if it present in ortb2.(site/user)', function () { @@ -501,7 +539,7 @@ describe('TrustXAdapter', function () { getConfigStub.restore(); }); - it('shold be right tmax when timeout in config is less then timeout in bidderRequest', function() { + 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); @@ -510,7 +548,7 @@ describe('TrustXAdapter', function () { expect(payload.tmax).to.equal(2000); getConfigStub.restore(); }); - it('shold be right tmax when timeout in bidderRequest is less then timeout in config', function() { + 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); @@ -519,6 +557,81 @@ describe('TrustXAdapter', function () { expect(payload.tmax).to.equal(3000); getConfigStub.restore(); }); + it('should contain imp[].ext.data.adserver if available', function() { + const ortb2Imp = [{ + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/111111/slot' + }, + pbadslot: '/111111/slot' + } + } + }, { + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/222222/slot' + }, + pbadslot: '/222222/slot' + } + } + }]; + const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { + return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); + }); + const request = spec.buildRequests(bidRequestsWithOrtb2Imp, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].ext).to.deep.equal({ + divid: bidRequests[0].adUnitCode, + data: ortb2Imp[0].ext.data, + gpid: ortb2Imp[0].ext.data.adserver.adslot + }); + expect(payload.imp[1].ext).to.deep.equal({ + divid: bidRequests[1].adUnitCode, + data: ortb2Imp[1].ext.data, + gpid: ortb2Imp[1].ext.data.adserver.adslot + }); + expect(payload.imp[2].ext).to.deep.equal({ + divid: bidRequests[2].adUnitCode + }); + }); + it('should contain imp[].instl if available', function() { + const ortb2Imp = [{ + instl: 1 + }, { + instl: 2, + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/222222/slot' + }, + pbadslot: '/222222/slot' + } + } + }]; + const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { + return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); + }); + const request = spec.buildRequests(bidRequestsWithOrtb2Imp, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].instl).to.equal(1); + expect(payload.imp[1].ext).to.deep.equal({ + divid: bidRequests[1].adUnitCode, + data: ortb2Imp[1].ext.data, + gpid: ortb2Imp[1].ext.data.adserver.adslot + }); + expect(payload.imp[1].instl).to.equal(2); + expect(payload.imp[2].ext).to.deep.equal({ + divid: bidRequests[2].adUnitCode + }); + expect(payload.imp[2].instl).to.be.undefined; + }); it('all id like request fields must be a string', function () { const bidderRequestWithNumId = Object.assign({}, bidderRequest, { bidderRequestId: 123123, auctionId: 345345543 }); diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js new file mode 100644 index 00000000000..099e5e56c33 --- /dev/null +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -0,0 +1,1313 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ttdBidAdapter'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config'; + +describe('ttdBidAdapter', function () { + function testBuildRequests(bidRequests, bidderRequestBase) { + let clonedBidderRequest = deepClone(bidderRequestBase); + clonedBidderRequest.bids = bidRequests; + return spec.buildRequests(bidRequests, clonedBidderRequest); + } + + describe('isBidRequestValid', function() { + function makeBid() { + return { + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '22222222', + 'placementId': 'some-PlacementId_1', + 'siteId': 'testSiteId' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'adUnitCode': 'adunit-code', + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + } + + describe('core', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(makeBid())).to.equal(true); + }); + + it('should return false when publisherId not passed', function () { + let bid = makeBid(); + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when supplySourceId not passed', function () { + let bid = makeBid(); + delete bid.params.supplySourceId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is longer than 64 characters', function () { + let bid = makeBid(); + bid.params.publisherId = '1111111111111111111111111111111111111111111111111111111111111111111111'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when siteId not passed', function () { + let bid = makeBid(); + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when siteId is longer than 50 characters', function () { + let bid = makeBid(); + bid.params.siteId = '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111' + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when placementId not passed', function () { + let bid = makeBid(); + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when the placementId is longer than 128 characters', function () { + let bid = makeBid(); + bid.params.placementId = '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'; // 130 characters + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if neither mediaTypes.banner nor mediaTypes.video is passed', function () { + let bid = makeBid(); + delete bid.mediaTypes + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('banner', function () { + it('should return true if banner.pos is passed correctly', function () { + let bid = makeBid(); + bid.mediaTypes.banner.pos = 1; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('video', function () { + function makeBid() { + return { + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '22222222', + 'siteId': 'testSiteId123', + 'placementId': 'somePlacementId' + }, + 'mediaTypes': { + 'video': { + 'minduration': 5, + 'maxduration': 30, + 'playerSize': [640, 480], + 'api': [1, 3], + 'mimes': ['video/mp4'], + 'protocols': [2, 3, 5, 6] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + } + + it('should return true if required parameters are passed', function () { + let bid = makeBid(); + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if maxduration is missing', function () { + let bid = makeBid(); + delete bid.mediaTypes.video.maxduration; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if api is missing', function () { + let bid = makeBid(); + delete bid.mediaTypes.video.api; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if mimes is missing', function () { + let bid = makeBid(); + delete bid.mediaTypes.video.mimes; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if protocols is missing', function () { + let bid = makeBid(); + delete bid.mediaTypes.video.protocols; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + }); + + describe('getUserSyncs', function () { + it('to check the user sync iframe', function () { + const syncOptions = { + pixelEnabled: true + }; + const gdprConsentString = 'BON3G4EON3G4EAAABAENAA____ABl____A'; + const gdprConsent = { + consentString: gdprConsentString, + gdprApplies: true + }; + const uspConsent = '1YYY'; + + let syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + let params = new URLSearchParams(new URL(syncs[0].url).search); + expect(params.get('us_privacy')).to.equal(uspConsent); + expect(params.get('ust')).to.equal('image'); + expect(params.get('gdpr')).to.equal('1'); + expect(params.get('gdpr_consent')).to.equal(gdprConsentString); + }); + }); + + describe('buildRequests-banner', function () { + const baseBannerBidRequests = [{ + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '13144370', + 'placementId': '1gaa015', + 'siteId': 'testSiteId123' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'sizes': [[300, 250], [300, 600]], + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '8651474f-58b1-4368-b812-84f8c937a099', + 'bidId': '243310435309b5', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; + + const baseBidderRequest = { + 'bidderCode': 'ttd', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'bidderRequestId': '18084284054531', + 'auctionStart': 1540945362095, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'https://www.example.com/test', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://www.example.com/test' + ] + }, + 'start': 1540945362099, + 'doneCbCallCount': 0 + }; + + it('sends bid request to our endpoint that makes sense', function () { + const request = testBuildRequests(baseBannerBidRequests, baseBidderRequest); + expect(request.method).to.equal('POST'); + expect(request.url).to.be.not.empty; + expect(request.data).to.be.not.null; + }); + + it('sets impression id to ad unit\'s bid id', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].id).to.equal('243310435309b5'); + }); + + it('sends bid requests to the correct endpoint', function () { + const url = testBuildRequests(baseBannerBidRequests, baseBidderRequest).url; + expect(url).to.equal('https://direct.adsrvr.org/bid/bidder/supplier'); + }); + + it('sends publisher id, site id, and placement id', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; + expect(requestBody.site).to.be.not.null; + expect(requestBody.site.publisher).to.be.not.null; + expect(requestBody.imp[0].tagid).to.be.not.null; + expect(requestBody.site.publisher.id).to.equal(baseBannerBidRequests[0].params.publisherId); + expect(requestBody.site.id).to.equal(baseBannerBidRequests[0].params.siteId); + expect(requestBody.imp[0].tagid).to.equal(baseBannerBidRequests[0].params.placementId); + }); + + it('includes the ad size in the bid request', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].banner.format[0].w).to.equal(300); + expect(requestBody.imp[0].banner.format[0].h).to.equal(250); + expect(requestBody.imp[0].banner.format[1].w).to.equal(300); + expect(requestBody.imp[0].banner.format[1].h).to.equal(600); + }); + + it('includes the detected referer in the bid request', 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; + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.imp[0].banner.pos).to.equal(1); + }); + + it('sets the banner expansion direction correctly if sent', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + const expdir = [1, 3] + clonedBannerRequests[0].params.banner = { + expdir: expdir + }; + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.imp[0].banner.expdir).to.equal(expdir); + }); + + it('sets keywords properly if sent', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + + config.setConfig({ortb2: { + site: { + keywords: 'highViewability, clothing, holiday shopping' + } + }}); + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + config.resetConfig(); + expect(requestBody.ext.ttdprebid.keywords).to.deep.equal(['highViewability', 'clothing', 'holiday shopping']); + }); + + it('sets ext properly', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.ext.ttdprebid.pbjs).to.equal('$prebid.version$'); + }); + + it('adds gdpr consent info to the request', function () { + let consentString = 'BON3G4EON3G4EAAABAENAA____ABl____A'; + let clonedBidderRequest = deepClone(baseBidderRequest); + clonedBidderRequest.gdprConsent = { + consentString: consentString, + gdprApplies: true + }; + + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + expect(requestBody.user.ext.consent).to.equal(consentString); + expect(requestBody.regs.ext.gdpr).to.equal(1); + }); + + it('adds usp consent info to the request', function () { + let consentString = 'BON3G4EON3G4EAAABAENAA____ABl____A'; + let clonedBidderRequest = deepClone(baseBidderRequest); + clonedBidderRequest.uspConsent = consentString; + + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + expect(requestBody.regs.ext.us_privacy).to.equal(consentString); + }); + + it('adds coppa consent info to the request', function () { + let clonedBidderRequest = deepClone(baseBidderRequest); + + config.setConfig({coppa: true}); + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + config.resetConfig(); + expect(requestBody.regs.coppa).to.equal(1); + }); + + it('adds schain info to the request', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 1 + }] + }; + let clonedBannerBidRequests = deepClone(baseBannerBidRequests); + clonedBannerBidRequests[0].schain = schain; + + const requestBody = testBuildRequests(clonedBannerBidRequests, baseBidderRequest).data; + expect(requestBody.source.ext.schain).to.deep.equal(schain); + }); + + it('adds unified ID info to the request', function () { + const TDID = '00000000-0000-0000-0000-000000000000'; + let clonedBannerRequests = deepClone(baseBannerBidRequests); + clonedBannerRequests[0].userId = { + tdid: TDID + }; + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.user.buyeruid).to.equal(TDID); + }); + + it('adds unified ID and UID2 info to user.ext.eids in the request', function () { + const TDID = '00000000-0000-0000-0000-000000000000'; + const UID2 = '99999999-9999-9999-9999-999999999999'; + let clonedBannerRequests = deepClone(baseBannerBidRequests); + clonedBannerRequests[0].userId = { + tdid: TDID, + uid2: { + id: UID2 + } + }; + const expectedEids = [ + { + source: 'adserver.org', + uids: [ + { + atype: 1, + ext: { + rtiPartner: 'TDID' + }, + id: TDID + } + ] + }, + { + source: 'uidapi.com', + uids: [ + { + atype: 3, + id: UID2 + } + ] + } + ]; + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.user.ext.eids).to.deep.equal(expectedEids); + }); + + it('adds first party site data to the request', function () { + let clonedBidderRequest = deepClone(baseBidderRequest); + + config.setConfig({ortb2: { + 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' + } + }}); + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + config.resetConfig(); + expect(requestBody.site.name).to.equal('example'); + expect(requestBody.site.domain).to.equal('page.example.com'); + expect(requestBody.site.cat[0]).to.equal('IAB2'); + expect(requestBody.site.sectioncat[0]).to.equal('IAB2-2'); + expect(requestBody.site.pagecat[0]).to.equal('IAB2-2'); + expect(requestBody.site.page).to.equal('https://page.example.com/here.html'); + expect(requestBody.site.ref).to.equal('https://ref.example.com'); + expect(requestBody.site.keywords).to.equal('power tools, drills'); + }); + }); + + describe('buildRequests-banner-multiple', function () { + const baseBannerMultipleBidRequests = [{ + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '13144370', + 'placementId': 'bottom' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'sizes': [[300, 250], [300, 600]], + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '8651474f-58b1-4368-b812-84f8c937a099', + 'bidId': 'small', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }, { + 'bidder': 'ttd', + 'params': { + 'publisherId': '13144370', + 'placementId': 'top', + 'siteId': 'testSite123' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'sizes': [[728, 90]], + 'adUnitCode': 'div-gpt-ad-91515710-0', + 'transactionId': '825c1228-ca8c-4657-b40f-2df500621527', + 'bidId': 'large', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; + + const baseBidderRequest = { + 'bidderCode': 'ttd', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'bidderRequestId': '18084284054531', + 'auctionStart': 1540945362095, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'https://www.test.com', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://www.test.com' + ] + }, + 'start': 1540945362099, + 'doneCbCallCount': 0 + }; + + it('sends multiple impressions', function () { + const requestBody = testBuildRequests(baseBannerMultipleBidRequests, baseBidderRequest).data; + expect(requestBody.imp.length).to.equal(2); + }); + + it('sends the right tag ids for each ad unit', function () { + const requestBody = testBuildRequests(baseBannerMultipleBidRequests, baseBidderRequest).data; + requestBody.imp.forEach(imp => { + if (imp.id === 'small') { + expect(imp.tagid).to.equal('bottom'); + } else if (imp.id === 'large') { + expect(imp.tagid).to.equal('top'); + } else { + assert.fail('no matching impression id found'); + } + }); + }); + + it('sends the sizes for each ad unit', function () { + const requestBody = testBuildRequests(baseBannerMultipleBidRequests, baseBidderRequest).data; + requestBody.imp.forEach(imp => { + if (imp.id === 'small') { + expect(imp.banner.format[0].w).to.equal(300); + expect(imp.banner.format[0].h).to.equal(250); + expect(imp.banner.format[1].w).to.equal(300); + expect(imp.banner.format[1].h).to.equal(600); + } else if (imp.id === 'large') { + expect(imp.banner.format[0].w).to.equal(728); + expect(imp.banner.format[0].h).to.equal(90); + } else { + assert.fail('no matching impression id found'); + } + }); + }); + }); + + describe('buildRequests-display-video-multiformat', function () { + const baseMultiformatBidRequests = [{ + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '13144370', + 'placementId': '1gaa015' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'api': [1, 3], + 'mimes': ['video/mp4'], + 'protocols': [2, 3, 5, 6], + 'minduration': 5, + 'maxduration': 30 + }, + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '8651474f-58b1-4368-b812-84f8c937a099', + 'bidId': '243310435309b5', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; + + const baseBidderRequest = { + 'bidderCode': 'ttd', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'bidderRequestId': '18084284054531', + 'auctionStart': 1540945362095, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'https://www.example.com/test', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://www.example.com/test' + ] + }, + 'start': 1540945362099, + 'doneCbCallCount': 0 + }; + + it('includes the video ad size in the bid request', function () { + const requestBody = testBuildRequests(baseMultiformatBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.w).to.equal(640); + expect(requestBody.imp[0].video.h).to.equal(480); + }); + + it('includes the banner ad size in the bid request', function () { + const requestBody = testBuildRequests(baseMultiformatBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].banner.format[0].w).to.equal(300); + expect(requestBody.imp[0].banner.format[0].h).to.equal(250); + expect(requestBody.imp[0].banner.format[1].w).to.equal(300); + expect(requestBody.imp[0].banner.format[1].h).to.equal(600); + }); + }); + + describe('buildRequests-video', function () { + const baseVideoBidRequests = [{ + 'bidder': 'ttd', + 'params': { + 'supplySourceId': 'supplier', + 'publisherId': '13144370', + 'placementId': '1gaa015' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'api': [1, 3], + 'mimes': ['video/mp4'], + 'protocols': [2, 3, 5, 6], + 'minduration': 5, + 'maxduration': 30 + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '8651474f-58b1-4368-b812-84f8c937a099', + 'bidId': '243310435309b5', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; + + const baseBidderRequest = { + 'bidderCode': 'ttd', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'bidderRequestId': '18084284054531', + 'auctionStart': 1540945362095, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'https://www.example.com/test', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://www.example.com/test' + ] + }, + 'start': 1540945362099, + 'doneCbCallCount': 0 + }; + + it('includes the ad size in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.w).to.equal(640); + expect(requestBody.imp[0].video.h).to.equal(480); + }); + + it('includes the mimes in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.mimes[0]).to.equal('video/mp4'); + }); + + it('includes the min and max duration in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.minduration).to.equal(5); + expect(requestBody.imp[0].video.maxduration).to.equal(30); + }); + + it('sets the minduration to 0 if missing', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + delete clonedVideoRequests[0].mediaTypes.video.minduration + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.minduration).to.equal(0); + }); + + it('includes the api frameworks in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.api[0]).to.equal(1); + expect(requestBody.imp[0].video.api[1]).to.equal(3); + }); + + it('includes the protocols in the bid request', function () { + const requestBody = testBuildRequests(baseVideoBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.protocols[0]).to.equal(2); + expect(requestBody.imp[0].video.protocols[1]).to.equal(3); + expect(requestBody.imp[0].video.protocols[2]).to.equal(5); + expect(requestBody.imp[0].video.protocols[3]).to.equal(6); + }); + + it('sets skip correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.skip = 1; + clonedVideoRequests[0].mediaTypes.video.skipmin = 5; + clonedVideoRequests[0].mediaTypes.video.skipafter = 10; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.skip).to.equal(1); + expect(requestBody.imp[0].video.skipmin).to.equal(5); + expect(requestBody.imp[0].video.skipafter).to.equal(10); + }); + + it('sets bitrate correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.minbitrate = 100; + clonedVideoRequests[0].mediaTypes.video.maxbitrate = 500; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.minbitrate).to.equal(100); + expect(requestBody.imp[0].video.maxbitrate).to.equal(500); + }); + + it('sets pos correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.pos = 1; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.pos).to.equal(1); + }); + + it('sets playbackmethod correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.playbackmethod = [1]; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.playbackmethod[0]).to.equal(1); + }); + + it('sets startdelay correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.startdelay = -1; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.startdelay).to.equal(-1); + }); + + it('sets placement correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.placement = 3; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.placement).to.equal(3); + }); + }); + + describe('interpretResponse-empty', function () { + it('should handle empty response', function () { + let result = spec.interpretResponse({}); + expect(result.length).to.equal(0); + }); + + it('should handle empty seatbid response', function () { + let response = { + body: { + 'id': '5e5c23a5ba71e78', + 'seatbid': [] + } + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('interpretResponse-simple-display', function () { + const incoming = { + body: { + 'id': '5e5c23a5ba71e78', + 'seatbid': [ + { + 'bid': [ + { + 'id': '6vmb3isptf', + 'crid': 'ttdscreative', + 'impid': '322add653672f68', + 'price': 1.22, + 'adm': '', + 'cat': [], + 'h': 90, + 'w': 728, + 'ttl': 60, + 'dealid': 'ttd-dealid-1', + 'adomain': ['advertiser.com'], + 'ext': { + 'mediatype': 1 + } + } + ], + 'seat': 'MOCK' + } + ], + 'cur': 'EUR', + 'bidid': '5e5c23a5ba71e78' + } + }; + + const serverRequest = { + 'method': 'POST', + 'url': 'https://direct.adsrvr.org/bid/bidder/supplier', + 'data': { + 'id': 'c47237df-c108-419f-9c2b-da513dc3c133', + 'imp': [ + { + 'id': '322add653672f68', + 'tagid': 'simple', + 'banner': { + 'w': 728, + 'h': 90, + 'format': [ + { + 'w': 728, + 'h': 90 + } + ] + } + } + ], + 'site': { + 'page': 'http://www.test.com', + 'publisher': { + 'id': '111' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36', + 'dnt': 0, + 'language': 'en-US', + 'connectiontype': 0 + }, + 'user': {}, + 'at': 1, + 'cur': [ + 'USD' + ], + 'regs': {}, + 'ext': { + 'ttdprebid': { + 'ver': 'TTD-PREBID-2019.11.12', + 'pbjs': '2.31.0' + } + } + }, + 'options': { + 'withCredentials': true + } + }; + + const expectedBid = { + 'requestId': '322add653672f68', + 'cpm': 1.22, + 'width': 728, + 'height': 90, + 'creativeId': 'ttdscreative', + 'dealId': 'ttd-dealid-1', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 60, + 'ad': '', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['advertiser.com'] + } + }; + + it('should get the correct bid response', function () { + let result = spec.interpretResponse(incoming, serverRequest); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedBid); + }); + }); + + describe('interpretResponse-multiple-display', function () { + const incoming = { + 'body': { + 'id': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'small', + 'impid': 'small', + 'price': 4.25, + 'adm': 'Default Test Ad Tag', + 'cid': 'campaignId132', + 'crid': 'creativeId999', + 'adomain': [ + 'http://foo' + ], + 'dealid': null, + 'w': 300, + 'h': 600, + 'cat': [], + 'ext': { + 'mediatype': 1 + } + }, + { + 'id': 'large', + 'impid': 'large', + 'price': 5.25, + 'adm': 'Default Test Ad Tag', + 'cid': 'campaignId132', + 'crid': 'creativeId222', + 'adomain': [ + 'http://foo2' + ], + 'dealid': null, + 'w': 728, + 'h': 90, + 'cat': [], + 'ext': { + 'mediatype': 1 + } + } + ], + 'seat': 'supplyVendorBuyerId132' + } + ], + 'cur': 'USD' + } + }; + + const expectedBids = [ + { + 'requestId': 'small', + 'cpm': 4.25, + 'width': 300, + 'height': 600, + 'creativeId': 'creativeId999', + 'currency': 'USD', + 'dealId': null, + 'netRevenue': true, + 'ttl': 360, + 'ad': 'Default Test Ad Tag', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['http://foo'] + } + }, + { + 'requestId': 'large', + 'cpm': 5.25, + 'width': 728, + 'height': 90, + 'creativeId': 'creativeId222', + 'currency': 'USD', + 'dealId': null, + 'netRevenue': true, + 'ttl': 360, + 'ad': 'Default Test Ad Tag', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['http://foo2'] + } + } + ]; + + const serverRequest = { + 'method': 'POST', + 'url': 'https://direct.adsrvr.org/bid/bidder/supplier', + 'data': { + 'id': 'c47237df-c108-419f-9c2b-da513dc3c133', + 'imp': [ + { + 'id': 'small', + 'tagid': 'test1', + 'banner': { + 'w': 300, + 'h': 600, + 'format': [ + { + 'w': 300, + 'h': 600 + } + ] + } + }, + { + 'id': 'large', + 'tagid': 'test2', + 'banner': { + 'w': 728, + 'h': 90, + 'format': [ + { + 'w': 728, + 'h': 90 + } + ] + } + } + ], + 'site': { + 'page': 'http://www.test.com', + 'publisher': { + 'id': '111' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36', + 'dnt': 0, + 'language': 'en-US', + 'connectiontype': 0 + }, + 'user': {}, + 'at': 1, + 'cur': [ + 'USD' + ], + 'regs': {}, + 'ext': { + 'ttdprebid': { + 'ver': 'TTD-PREBID-2019.11.12', + 'pbjs': '2.31.0' + } + } + }, + 'options': { + 'withCredentials': true + } + }; + + it('should get the correct bid response', function () { + let result = spec.interpretResponse(incoming, serverRequest); + expect(result.length).to.equal(2); + expect(result).to.deep.equal(expectedBids); + }); + }); + + describe('interpretResponse-simple-video', function () { + const incoming = { + 'body': { + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'crid': 'mokivv6m', + 'ext': { + 'advid': '7ieo6xk', + 'agid': '7q9n3s2', + 'deal': { + 'dealid': '7013542' + }, + 'imptrackers': [], + 'viewabilityvendors': [], + 'mediatype': 2 + }, + 'h': 480, + 'impid': '2eabb87dfbcae4', + 'nurl': 'https://insight.adsrvr.org/enduser/vast?iid=00000000-0000-0000-0000-000000000000&crid=v3pek2eh&wp=${AUCTION_PRICE}&aid=&wpc=&sfe=0&puid=&tdid=00000000-0000-0000-0000-000000000000&pid=&ag=&adv=&sig=AAAAAAAAAAAAAA.&cf=&fq=0&td_s=&rcats=&mcat=&mste=&mfld=4&mssi=&mfsi=&uhow=&agsa=&rgco=&rgre=&rgme=&rgci=&rgz=&svbttd=0&dt=&osf=&os=&br=&rlangs=en&mlang=en&svpid=&did=&rcxt=&lat=&lon=&tmpc=&daid=&vp=0&osi=&osv=&dc=0&vcc=QAFIAVABiAECwAEDyAED0AED6AEG8AEBgAIDigIMCAIIBQgDCAYICwgMmgIECAEIAqACA6gCAsACAA..&sv=noop&pidi=&advi=&cmpi=&agi=&cridi=&svi=&cmp=&skip=1&c=&dur=&crrelr=', + 'price': 13.6, + 'ttl': 500, + 'w': 600 + } + ], + 'seat': 'supplyVendorBuyerId132' + } + ] + }, + 'headers': {} + }; + + const serverRequest = { + 'method': 'POST', + 'url': 'https://direct.bid.adsrvr.org/bid/bidder/supplier', + 'data': { + 'id': 'e94ec12d-ae1d-4ed7-abd1-eb3198ce3b63', + 'imp': [ + { + 'id': '2eabb87dfbcae4', + 'tagid': 'video', + 'video': { + 'api': [ + 1, + 3 + ], + 'mimes': [ + 'video/mp4' + ], + 'minduration': 5, + 'maxduration': 30, + 'w': 640, + 'h': 480, + 'protocols': [ + 2, + 3, + 5, + 6 + ] + } + } + ], + 'site': { + 'page': 'http://www.test.com', + 'publisher': { + 'id': '111' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', + 'dnt': 0, + 'language': 'en-US', + 'connectiontype': 0 + }, + 'user': {}, + 'at': 1, + 'cur': [ + 'USD' + ], + 'regs': {}, + 'ext': { + 'ttdprebid': { + 'ver': 'TTD-PREBID-2019.11.12', + 'pbjs': '3.10.0' + } + } + }, + 'options': { + 'withCredentials': true + } + }; + + const expectedBid = + { + 'requestId': '2eabb87dfbcae4', + 'cpm': 13.6, + 'creativeId': 'mokivv6m', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 500, + 'width': 640, + 'height': 480, + 'mediaType': 'video', + 'vastUrl': 'https://insight.adsrvr.org/enduser/vast?iid=00000000-0000-0000-0000-000000000000&crid=v3pek2eh&wp=13.6&aid=&wpc=&sfe=0&puid=&tdid=00000000-0000-0000-0000-000000000000&pid=&ag=&adv=&sig=AAAAAAAAAAAAAA.&cf=&fq=0&td_s=&rcats=&mcat=&mste=&mfld=4&mssi=&mfsi=&uhow=&agsa=&rgco=&rgre=&rgme=&rgci=&rgz=&svbttd=0&dt=&osf=&os=&br=&rlangs=en&mlang=en&svpid=&did=&rcxt=&lat=&lon=&tmpc=&daid=&vp=0&osi=&osv=&dc=0&vcc=QAFIAVABiAECwAEDyAED0AED6AEG8AEBgAIDigIMCAIIBQgDCAYICwgMmgIECAEIAqACA6gCAsACAA..&sv=noop&pidi=&advi=&cmpi=&agi=&cridi=&svi=&cmp=&skip=1&c=&dur=&crrelr=', + 'meta': {} + }; + + it('should get the correct bid response if nurl is returned', function () { + let result = spec.interpretResponse(incoming, serverRequest); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedBid); + }); + + it('should get the correct bid response if adm is returned', function () { + const vastXml = "2.0574840600:00:30 \"Click ]]>"; + let admIncoming = deepClone(incoming); + delete admIncoming.body.seatbid[0].bid[0].nurl; + admIncoming.body.seatbid[0].bid[0].adm = vastXml; + + let vastXmlExpectedBid = deepClone(expectedBid); + delete vastXmlExpectedBid.vastUrl; + vastXmlExpectedBid.vastXml = vastXml; + + let result = spec.interpretResponse(admIncoming, serverRequest); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(vastXmlExpectedBid); + }); + }); + + describe('interpretResponse-display-and-video', function () { + const incoming = { + 'body': { + 'id': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'small', + 'impid': 'small', + 'price': 4.25, + 'adm': 'Default Test Ad Tag', + 'cid': 'campaignId132', + 'crid': 'creativeId999', + 'adomain': [ + 'http://foo' + ], + 'dealid': null, + 'w': 300, + 'h': 600, + 'cat': [], + 'ext': { + 'mediatype': 1 + } + }, + { + 'crid': 'mokivv6m', + 'ext': { + 'advid': '7ieo6xk', + 'agid': '7q9n3s2', + 'deal': { + 'dealid': '7013542' + }, + 'imptrackers': [], + 'viewabilityvendors': [], + 'mediatype': 2 + }, + 'h': 480, + 'impid': '2eabb87dfbcae4', + 'nurl': 'https://insight.adsrvr.org/enduser/vast?iid=00000000-0000-0000-0000-000000000000&crid=v3pek2eh&wp=${AUCTION_PRICE}&aid=&wpc=&sfe=0&puid=&tdid=00000000-0000-0000-0000-000000000000&pid=&ag=&adv=&sig=AAAAAAAAAAAAAA.&cf=&fq=0&td_s=&rcats=&mcat=&mste=&mfld=4&mssi=&mfsi=&uhow=&agsa=&rgco=&rgre=&rgme=&rgci=&rgz=&svbttd=0&dt=&osf=&os=&br=&rlangs=en&mlang=en&svpid=&did=&rcxt=&lat=&lon=&tmpc=&daid=&vp=0&osi=&osv=&dc=0&vcc=QAFIAVABiAECwAEDyAED0AED6AEG8AEBgAIDigIMCAIIBQgDCAYICwgMmgIECAEIAqACA6gCAsACAA..&sv=noop&pidi=&advi=&cmpi=&agi=&cridi=&svi=&cmp=&skip=1&c=&dur=&crrelr=', + 'price': 13.6, + 'ttl': 500, + 'w': 600 + } + ], + 'seat': 'supplyVendorBuyerId132' + } + ], + 'cur': 'USD' + } + }; + + const expectedBids = [ + { + 'requestId': 'small', + 'cpm': 4.25, + 'width': 300, + 'height': 600, + 'creativeId': 'creativeId999', + 'currency': 'USD', + 'dealId': null, + 'netRevenue': true, + 'ttl': 360, + 'ad': 'Default Test Ad Tag', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['http://foo'] + } + }, + { + 'requestId': '2eabb87dfbcae4', + 'cpm': 13.6, + 'creativeId': 'mokivv6m', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 500, + 'width': 640, + 'height': 480, + 'mediaType': 'video', + 'vastUrl': 'https://insight.adsrvr.org/enduser/vast?iid=00000000-0000-0000-0000-000000000000&crid=v3pek2eh&wp=13.6&aid=&wpc=&sfe=0&puid=&tdid=00000000-0000-0000-0000-000000000000&pid=&ag=&adv=&sig=AAAAAAAAAAAAAA.&cf=&fq=0&td_s=&rcats=&mcat=&mste=&mfld=4&mssi=&mfsi=&uhow=&agsa=&rgco=&rgre=&rgme=&rgci=&rgz=&svbttd=0&dt=&osf=&os=&br=&rlangs=en&mlang=en&svpid=&did=&rcxt=&lat=&lon=&tmpc=&daid=&vp=0&osi=&osv=&dc=0&vcc=QAFIAVABiAECwAEDyAED0AED6AEG8AEBgAIDigIMCAIIBQgDCAYICwgMmgIECAEIAqACA6gCAsACAA..&sv=noop&pidi=&advi=&cmpi=&agi=&cridi=&svi=&cmp=&skip=1&c=&dur=&crrelr=', + 'meta': {} + } + ]; + + const serverRequest = { + 'method': 'POST', + 'url': 'https://direct.adsrvr.org/bid/bidder/supplier', + 'data': { + 'id': 'c47237df-c108-419f-9c2b-da513dc3c133', + 'imp': [ + { + 'id': 'small', + 'tagid': 'test1', + 'banner': { + 'w': 300, + 'h': 600, + 'format': [ + { + 'w': 300, + 'h': 600 + } + ] + }, + }, + { + 'id': '2eabb87dfbcae4', + 'tagid': 'video', + 'video': { + 'api': [ + 1, + 3 + ], + 'mimes': [ + 'video/mp4' + ], + 'minduration': 5, + 'maxduration': 30, + 'w': 640, + 'h': 480, + 'protocols': [ + 2, + 3, + 5, + 6 + ] + } + } + ], + 'site': { + 'page': 'http://www.test.com', + 'publisher': { + 'id': '111' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36', + 'dnt': 0, + 'language': 'en-US', + 'connectiontype': 0 + }, + 'user': {}, + 'at': 1, + 'cur': [ + 'USD' + ], + 'regs': {}, + 'ext': { + 'ttdprebid': { + 'ver': 'TTD-PREBID-2019.11.12', + 'pbjs': '2.31.0' + } + } + }, + 'options': { + 'withCredentials': true + } + }; + + it('should get the correct bid response', function () { + let result = spec.interpretResponse(incoming, serverRequest); + expect(result.length).to.equal(2); + expect(result).to.deep.equal(expectedBids); + }); + }); +}); diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index 56217fe3561..c24f63c0b99 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -1,5 +1,7 @@ -import { expect } from 'chai'; -import { spec } from 'modules/undertoneBidAdapter.js'; +import {expect} from 'chai'; +import {spec} from 'modules/undertoneBidAdapter.js'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {deepClone} from '../../../src/utils'; const URL = 'https://hb.undertone.com/hb'; const BIDDER_CODE = 'undertone'; @@ -276,6 +278,57 @@ describe('Undertone Adapter', () => { sandbox.restore(); }); + describe('getFloor', function () { + it('should send 0 floor when getFloor is undefined', function() { + const request = spec.buildRequests(videoBidReq, bidderReq); + const bidReq = JSON.parse(request.data)['x-ut-hb-params'][0]; + expect(bidReq.mediaType).to.deep.equal(VIDEO); + expect(bidReq.bidfloor).to.deep.equal(0); + }); + it('should send mocked floor when defined on video media-type', function() { + const clonedVideoBidReqArr = deepClone(videoBidReq); + const mockedFloorResponse = { + currency: 'USD', + floor: 2.3 + }; + clonedVideoBidReqArr[1].getFloor = () => mockedFloorResponse; + + const request = spec.buildRequests(clonedVideoBidReqArr, bidderReq); + const bidReq1 = JSON.parse(request.data)['x-ut-hb-params'][0]; + const bidReq2 = JSON.parse(request.data)['x-ut-hb-params'][1]; + expect(bidReq1.mediaType).to.deep.equal(VIDEO); + expect(bidReq1.bidfloor).to.deep.equal(0); + + expect(bidReq2.mediaType).to.deep.equal(VIDEO); + expect(bidReq2.bidfloor).to.deep.equal(mockedFloorResponse.floor); + }); + it('should send mocked floor on banner media-type', function() { + const clonedValidBidReqArr = [deepClone(validBidReq)]; + const mockedFloorResponse = { + currency: 'USD', + floor: 2.3 + }; + clonedValidBidReqArr[0].getFloor = () => mockedFloorResponse; + + const request = spec.buildRequests(clonedValidBidReqArr, bidderReq); + const bidReq = JSON.parse(request.data)['x-ut-hb-params'][0]; + expect(bidReq.mediaType).to.deep.equal(BANNER); + expect(bidReq.bidfloor).to.deep.equal(mockedFloorResponse.floor); + }); + it('should send 0 floor on invalid currency', function() { + const clonedValidBidReqArr = [deepClone(validBidReq)]; + const mockedFloorResponse = { + currency: 'EUR', + floor: 2.3 + }; + clonedValidBidReqArr[0].getFloor = () => mockedFloorResponse; + + const request = spec.buildRequests(clonedValidBidReqArr, bidderReq); + const bidReq = JSON.parse(request.data)['x-ut-hb-params'][0]; + expect(bidReq.mediaType).to.deep.equal(BANNER); + expect(bidReq.bidfloor).to.deep.equal(0); + }); + }); describe('supply chain', function () { it('should send supply chain if found on first bid', function () { const request = spec.buildRequests(supplyChainedBidReqs, bidderReq); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 0190bceca70..ceb83b87ebb 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -14,7 +14,7 @@ import { import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import {getGlobal} from 'src/prebidGlobal.js'; import { @@ -23,7 +23,7 @@ import { setConsentConfig } from 'modules/consentManagement.js'; import {server} from 'test/mocks/xhr.js'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; import {unifiedIdSubmodule} from 'modules/unifiedIdSystem.js'; import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; import {id5IdSubmodule} from 'modules/id5IdSystem.js'; @@ -36,7 +36,7 @@ import {nextrollIdSubmodule} from 'modules/nextrollIdSystem.js'; import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; import {sharedIdSystemSubmodule} from 'modules/sharedIdSystem.js'; -import {haloIdSubmodule} from 'modules/haloIdSystem.js'; +import {hadronIdSubmodule} from 'modules/hadronIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; @@ -49,6 +49,10 @@ import {flocIdSubmodule} from 'modules/flocIdSystem.js' import {amxIdSubmodule} from '../../../modules/amxIdSystem.js'; import {akamaiDAPIdSubmodule} from 'modules/akamaiDAPIdSystem.js' import {kinessoIdSubmodule} from 'modules/kinessoIdSystem.js' +import {adqueryIdSubmodule} from 'modules/adqueryIdSystem.js'; +import * as mockGpt from '../integration/faker/googletag.js'; +import 'src/prebid.js'; +import {hook} from '../../../src/hook.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -105,24 +109,36 @@ describe('User ID', function () { } before(function () { + hook.ready(); localStorage.removeItem(PBJS_USER_ID_OPTOUT_NAME); }); beforeEach(function () { + // TODO: this whole suite needs to be redesigned; it is passing by accident + // some tests do not pass if consent data is available + // (there are functions here with signature `getId(config, storedId)`, but storedId is actually consentData) + // also, this file is ginormous; do we really need to test *all* id systems as one? + resetConsentData(); coreStorage.setCookie(CONSENT_LOCAL_STORAGE_NAME, '', EXPIRED_COOKIE_DATE); }); describe('Decorate Ad Units', function () { beforeEach(function () { + // reset mockGpt so nothing else interferes + mockGpt.disable(); + mockGpt.enable(); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('pubcid_alt', 'altpubcid200000', (new Date(Date.now() + 5000).toUTCString())); sinon.spy(coreStorage, 'setCookie'); + sinon.stub(utils, 'logWarn'); }); afterEach(function () { + mockGpt.enable(); $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); coreStorage.setCookie.restore(); + utils.logWarn.restore(); }); after(function () { @@ -321,6 +337,50 @@ describe('User ID', function () { expect((getGlobal()).getUserIdsAsEids()).to.deep.equal(createEidsArray((getGlobal()).getUserIds())); }); + it('should set googletag ppid correctly', function () { + let adUnits = [getAdUnitMock()]; + setSubmoduleRegistry([amxIdSubmodule, sharedIdSystemSubmodule, identityLinkSubmodule]); + init(config); + + config.setConfig({ + userSync: { + ppid: 'pubcid.org', + userIds: [ + { name: 'amxId', value: {'amxId': 'amx-id-value-amx-id-value-amx-id-value'} }, + { name: 'pubCommonId', value: {'pubcid': 'pubCommon-id-value-pubCommon-id-value'} }, + { name: 'identityLink', value: {'idl_env': 'identityLink-id-value-identityLink-id-value'} }, + ] + } + }); + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + requestBidsHook(() => {}, {adUnits}); + // ppid should have been set without dashes and stuff + expect(window.googletag._ppid).to.equal('pubCommonidvaluepubCommonidvalue'); + }); + + it('should log a warning if PPID too big or small', function () { + let adUnits = [getAdUnitMock()]; + setSubmoduleRegistry([sharedIdSystemSubmodule]); + init(config); + + config.setConfig({ + userSync: { + ppid: 'pubcid.org', + userIds: [ + { name: 'pubCommonId', value: {'pubcid': 'pubcommonIdValue'} }, + ] + } + }); + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + requestBidsHook(() => {}, {adUnits}); + // ppid should NOT have been set + expect(window.googletag._ppid).to.equal(undefined); + // a warning should have been emmited + expect(utils.logWarn.args[0][0]).to.exist.and.to.contain('User ID - Googletag Publisher Provided ID for pubcid.org is not between 32 and 150 characters - pubcommonIdValue'); + }); + it('pbjs.refreshUserIds refreshes', function() { let sandbox = sinon.createSandbox(); @@ -508,7 +568,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -516,14 +576,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -534,7 +594,7 @@ describe('User ID', function () { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -551,7 +611,7 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); @@ -572,8 +632,8 @@ describe('User ID', function () { expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 23 configurations should result in 23 submodules add', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + it('config with 24 configurations should result in 24 submodules add', function () { + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -606,8 +666,8 @@ describe('User ID', function () { name: 'intentIqId', storage: {name: 'intentIqId', type: 'cookie'} }, { - name: 'haloId', - storage: {name: 'haloId', type: 'cookie'} + name: 'hadronId', + storage: {name: 'hadronId', type: 'cookie'} }, { name: 'zeotapIdPlus' }, { @@ -638,14 +698,17 @@ describe('User ID', function () { }, { name: 'kpuid', storage: {name: 'kpuid', type: 'cookie'} + }, { + name: 'qid', + storage: {name: 'qid', type: 'html5'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 23 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 24 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ @@ -661,7 +724,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -676,7 +739,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -1584,28 +1647,28 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook from haloId html5', function (done) { + it('test hook from hadronId html5', function (done) { // simulate existing browser local storage values - localStorage.setItem('haloId', JSON.stringify({'haloId': 'random-ls-identifier'})); - localStorage.setItem('haloId_exp', ''); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'random-ls-identifier'})); + localStorage.setItem('hadronId_exp', ''); - setSubmoduleRegistry([haloIdSubmodule]); + setSubmoduleRegistry([hadronIdSubmodule]); init(config); - config.setConfig(getConfigMock(['haloId', 'haloId', 'html5'])); + config.setConfig(getConfigMock(['hadronId', 'hadronId', 'html5'])); requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.haloId'); - expect(bid.userId.haloId).to.equal('random-ls-identifier'); + expect(bid).to.have.deep.nested.property('userId.hadronId'); + expect(bid.userId.hadronId).to.equal('random-ls-identifier'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'audigent.com', uids: [{id: 'random-ls-identifier', atype: 1}] }); }); }); - localStorage.removeItem('haloId'); - localStorage.removeItem('haloId_exp', ''); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp', ''); done(); }, {adUnits}); }); @@ -1772,7 +1835,38 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, netId, haloId, Criteo, UID 2.0, admixerId, amxId, dmdId, kpuid and mwOpenLinkId have data to pass', function (done) { + it('test hook from qid html5', (done) => { + // simulate existing localStorage values + localStorage.setItem('qid', 'testqid'); + localStorage.setItem('qid_exp', ''); + + setSubmoduleRegistry([adqueryIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['qid', 'qid', 'html5'])); + + requestBidsHook(() => { + adUnits.forEach((adUnit) => { + adUnit.bids.forEach((bid) => { + expect(bid).to.have.deep.nested.property('userId.qid'); + expect(bid.userId.qid).to.equal('testqid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'adquery.io', + uids: [{ + id: 'testqid', + atype: 1, + }] + }); + }); + }); + + // clear LS + localStorage.removeItem('qid'); + localStorage.removeItem('qid_exp'); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, netId, hadronId, Criteo, UID 2.0, admixerId, amxId, dmdId, kpuid, qid and mwOpenLinkId have data to pass', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1782,19 +1876,20 @@ 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('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (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())); coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); - // amxId only supports localStorage localStorage.setItem('amxId', 'test_amxid_id'); localStorage.setItem('amxId_exp', (new Date(Date.now() + 5000)).toUTCString()); - - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + // qid only supports localStorage + localStorage.setItem('qid', 'testqid'); + localStorage.setItem('qid_exp', (new Date(Date.now() + 5000)).toUTCString()); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1805,7 +1900,7 @@ describe('User ID', function () { ['netId', 'netId', 'cookie'], ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], - ['haloId', 'haloId', 'cookie'], + ['hadronId', 'hadronId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'], @@ -1813,7 +1908,8 @@ describe('User ID', function () { ['admixerId', 'admixerId', 'cookie'], ['amxId', 'amxId', 'html5'], ['deepintentId', 'deepintentId', 'cookie'], - ['kpuid', 'kpuid', 'cookie'])); + ['kpuid', 'kpuid', 'cookie'], + ['qid', 'qid', 'html5'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1845,9 +1941,9 @@ describe('User ID', function () { // also check that zeotapIdPlus id data was copied to bid expect(bid).to.have.deep.nested.property('userId.IDP'); expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that haloId id was copied to bid - expect(bid).to.have.deep.nested.property('userId.haloId'); - expect(bid.userId.haloId).to.equal('testHaloId'); + // 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'); // 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'); @@ -1871,7 +1967,10 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.kpuid'); expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(bid.userIdAsEids.length).to.equal(17); + expect(bid).to.have.deep.nested.property('userId.qid'); + expect(bid.userId.qid).to.equal('testqid'); + + expect(bid.userIdAsEids.length).to.equal(18); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1883,7 +1982,7 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); @@ -1892,11 +1991,13 @@ describe('User ID', function () { coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); localStorage.removeItem('amxId'); localStorage.removeItem('amxId_exp'); + localStorage.removeItem('qid'); + localStorage.removeItem('qid_exp'); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, dmdId, intentIqId, zeotapIdPlus, criteo, netId, haloId, UID 2.0, admixerId, kpuid and mwOpenLinkId have their modules added before and after init', function (done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, dmdId, intentIqId, zeotapIdPlus, criteo, netId, hadronId, UID 2.0, admixerId, kpuid and mwOpenLinkId have their modules added before and after init', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1905,7 +2006,7 @@ 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('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('dmdId', 'testdmdId', (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())); @@ -1929,7 +2030,7 @@ describe('User ID', function () { attachIdSystem(netIdSubmodule); attachIdSystem(intentIqIdSubmodule); attachIdSystem(zeotapIdPlusSubmodule); - attachIdSystem(haloIdSubmodule); + attachIdSystem(hadronIdSubmodule); attachIdSystem(dmdIdSubmodule); attachIdSystem(criteoIdSubmodule); attachIdSystem(mwOpenLinkIdSubModule); @@ -1947,7 +2048,7 @@ describe('User ID', function () { ['netId', 'netId', 'cookie'], ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], - ['haloId', 'haloId', 'cookie'], + ['hadronId', 'hadronId', 'cookie'], ['dmdId', 'dmdId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], ['mwOpenLinkId', 'mwol', 'cookie'], @@ -1985,9 +2086,9 @@ describe('User ID', function () { // also check that zeotapIdPlus id data was copied to bid expect(bid).to.have.deep.nested.property('userId.IDP'); expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that haloId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.haloId'); - expect(bid.userId.haloId).to.equal('testHaloId'); + // 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'); // also check that dmdId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.dmdId'); expect(bid.userId.dmdId).to.equal('testdmdId'); @@ -2023,7 +2124,7 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); @@ -2073,7 +2174,7 @@ 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('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (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()); @@ -2081,8 +2182,10 @@ describe('User ID', function () { localStorage.setItem('amxId', 'test_amxid_id'); localStorage.setItem('amxId_exp', new Date(Date.now() + 5000).toUTCString()) coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); + localStorage.setItem('qid', 'testqid'); + localStorage.setItem('qid_exp', new Date(Date.now() + 5000).toUTCString()) - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ @@ -2107,7 +2210,7 @@ describe('User ID', function () { }, { name: 'zeotapIdPlus' }, { - name: 'haloId', storage: {name: 'haloId', type: 'cookie'} + name: 'hadronId', storage: {name: 'hadronId', type: 'cookie'} }, { name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} }, { @@ -2120,6 +2223,8 @@ describe('User ID', function () { name: 'amxId', storage: {name: 'amxId', type: 'html5'} }, { name: 'kpuid', storage: {name: 'kpuid', type: 'cookie'} + }, { + name: 'qid', storage: {name: 'qid', type: 'html5'} }] } }); @@ -2171,9 +2276,9 @@ describe('User ID', function () { // also check that zeotapIdPlus id data was copied to bid expect(bid).to.have.deep.nested.property('userId.IDP'); expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that haloId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.haloId'); - expect(bid.userId.haloId).to.equal('testHaloId'); + // 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.uid2).to.deep.equal({ id: 'Sample_AD_Token' }); @@ -2191,7 +2296,10 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.kpuid'); expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(bid.userIdAsEids.length).to.equal(15); + + expect(bid).to.have.deep.nested.property('userId.qid'); + expect(bid.userId.qid).to.equal('testqid'); + expect(bid.userIdAsEids.length).to.equal(16); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -2202,7 +2310,7 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); @@ -2553,5 +2661,97 @@ describe('User ID', function () { }); }); }); + + describe('handles config with ESP configuration in user sync object', function() { + describe('Call registerSignalSources to register signal sources with gtag', function () { + it('pbjs.registerSignalSources should be defined', () => { + expect(typeof (getGlobal()).registerSignalSources).to.equal('function'); + }); + }) + + describe('Call getEncryptedEidsForSource to get encrypted Eids for source', function() { + const signalSources = ['pubcid.org']; + + it('pbjs.getEncryptedEidsForSource should be defined', () => { + expect(typeof (getGlobal()).getEncryptedEidsForSource).to.equal('function'); + }); + + it('pbjs.getEncryptedEidsForSource should return the string without encryption if encryption is false', (done) => { + setSubmoduleRegistry([sharedIdSystemSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + 'name': 'sharedId', + 'storage': { + 'type': 'cookie', + 'name': '_pubcid', + 'expires': 365 + } + }, + { + 'name': 'pubcid.org' + } + ] + }, + }); + const encrypt = false; + (getGlobal()).getEncryptedEidsForSource(signalSources[0], encrypt).then((data) => { + let users = (getGlobal()).getUserIdsAsEids(); + expect(data).to.equal(users[0].uids[0].id); + done(); + }).catch(done); + }); + + it('pbjs.getEncryptedEidsForSource should return the string base64 encryption if encryption is true', (done) => { + const encrypt = true; + (getGlobal()).getEncryptedEidsForSource(signalSources[0], encrypt).then((result) => { + expect(result.startsWith('1||')).to.true; + done(); + }).catch(done); + }); + + it('pbjs.getEncryptedEidsForSource should return string if custom function is defined', (done) => { + const getCustomSignal = () => { + return '{"keywords":["tech","auto"]}'; + } + const expectedString = '1||eyJrZXl3b3JkcyI6WyJ0ZWNoIiwiYXV0byJdfQ=='; + const encrypt = false; + const source = 'pubmatic.com'; + (getGlobal()).getEncryptedEidsForSource(source, encrypt, getCustomSignal).then((result) => { + expect(result).to.equal(expectedString); + done(); + }).catch(done); + }); + + it('pbjs.getUserIdsAsEidBySource', () => { + const users = { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '11111', + 'atype': 1 + } + ] + } + setSubmoduleRegistry([sharedIdSystemSubmodule, amxIdSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'pubCommonId', value: {'pubcid': '11111'} + }, { + name: 'amxId', value: {'amxId': 'amx-id-value-amx-id-value-amx-id-value'} + }] + } + }); + expect(typeof (getGlobal()).getUserIdsAsEidBySource).to.equal('function'); + expect((getGlobal()).getUserIdsAsEidBySource(signalSources[0])).to.deep.equal(users); + }); + }) + }); }) }); diff --git a/test/spec/modules/vibrantmediaBidAdapter_spec.js b/test/spec/modules/vibrantmediaBidAdapter_spec.js new file mode 100644 index 00000000000..c6ce7d52fb3 --- /dev/null +++ b/test/spec/modules/vibrantmediaBidAdapter_spec.js @@ -0,0 +1,1237 @@ +import {expect} from 'chai'; +import {spec} from 'modules/vibrantmediaBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from 'src/mediaTypes.js'; +import {INSTREAM, OUTSTREAM} from 'src/video.js'; + +const EXPECTED_PREBID_SERVER_URL = 'https://prebid.intellitxt.com/prebid'; + +const BANNER_AD = + 'Test Banner Ad UnitHello!'; +const VIDEO_AD = 'Test Video Ad Unit' + + ''; + +const VALID_BANNER_BID_PARAMS = Object.freeze({ + member: '1234', + invCode: 'ABCD', + placementId: '10433394' +}); + +const VALID_VIDEO_BID_PARAMS = Object.freeze({ + member: '1234', + invCode: 'ABCD', + placementId: '10433394', + video: { + skippable: false, + playback_method: 'auto_play_sound_off' + } +}); + +const VALID_NATIVE_BID_PARAMS = VALID_BANNER_BID_PARAMS; + +const DEFAULT_BID_SIZES = [[300, 250], [600, 240]]; + +const VALID_CONSENT_STRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + +const getValidBidderRequest = (bidRequests) => { + return Object.freeze({ + bidderCode: 'vibrantmedia', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: VALID_CONSENT_STRING, + vendorData: {}, + gdprApplies: true, + }, + bids: bidRequests, + }); +}; + +describe('VibrantMediaBidAdapter', function () { + const adapter = newBidder(spec); + + describe('constants', function () { + expect(spec.code).to.equal('vibrantmedia'); + expect(spec.supportedMediaTypes).to.deep.equal([BANNER, NATIVE, VIDEO]); + }); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('transformBidParams', function () { + it('transforms bid params correctly', function () { + expect(spec.transformBidParams(VALID_VIDEO_BID_PARAMS)).to.deep.equal(VALID_VIDEO_BID_PARAMS); + }); + }) + + let bidRequest; + + beforeEach(function () { + bidRequest = { + bidder: 'vibrantmedia', + params: { + // Filled in by individual tests + }, + mediaTypes: { + // Filled in by individual tests + }, + adUnitCode: 'test-div', + transactionId: '13579acef87623', + placementId: '7623587623857', + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + }); + + describe('isBidRequestValid', function () { + describe('with banner bid requests', function () { + beforeEach(function () { + bidRequest.mediaTypes.banner = { + sizes: DEFAULT_BID_SIZES, + }; + }); + + it('should return true for a valid banner bid request', function () { + bidRequest.params = VALID_BANNER_BID_PARAMS; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid banner bid request with a member id and inventory code', function () { + bidRequest.params = { + member: '1234', + invCode: 'ABCD', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid banner bid request with a placement id', function () { + bidRequest.params = { + placementId: '10433394', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for a valid banner bid request but with a member id and no inventory code', function () { + bidRequest.params = { + member: '1234', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid banner bid request but with no member id and an inventory code', function () { + bidRequest.params = { + invCode: 'ABCD', + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid banner bid request but with no supported media types', function () { + bidRequest.params = { + placementId: '10433394', + }; + delete bidRequest.mediaTypes.banner; + bidRequest.mediaTypes.unsupported = { + sizes: DEFAULT_BID_SIZES, + } + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid banner bid request but with no params', function () { + bidRequest.params = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('with video bid requests', function () { + describe('with sizes attribute', function () { + const validVideoMediaTypes = { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + minduration: 1, + maxduration: 60, + skip: 0, + skipafter: 5, + playbackmethod: [2], + protocols: [1, 2, 3] + }; + + it('should return true for a valid video bid request', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for an instream video bid request', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + context: INSTREAM, + sizes: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a video bid request with an unknown context', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + context: 'fake', + sizes: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a video bid request with no context', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + sizes: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return true for a valid video bid request with a member id and inventory code', function () { + bidRequest.params = { + member: '1234', + invCode: 'ABCD', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid video bid request with a placement id', function () { + bidRequest.params = { + placementId: '10433394', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for a valid video bid request but with a member id and no inventory code', function () { + bidRequest.params = { + member: '1234', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid video bid request but with no member id and an inventory code', function () { + bidRequest.params = { + invCode: 'ABCD', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid video bid request but with no params', function () { + bidRequest.params = {}; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('with playerSize attribute', function () { + const validVideoMediaTypes = { + context: OUTSTREAM, + playerSize: DEFAULT_BID_SIZES, + minduration: 1, + maxduration: 60, + skip: 0, + skipafter: 5, + playbackmethod: [2], + protocols: [1, 2, 3] + }; + + beforeEach(function () { + bidRequest.mediaTypes.video = { + // Filled in by individual tests + }; + }); + + it('should return true for a valid video bid request', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for an instream video bid request', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + context: INSTREAM, + playerSize: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a video bid request with an unknown context', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + context: 'fake', + playerSize: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a video bid request with no context', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes.video = { + playerSize: DEFAULT_BID_SIZES, + }; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return true for a valid video bid request with a member id and inventory code', function () { + bidRequest.params = { + member: '1234', + invCode: 'ABCD', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid video bid request with a placement id', function () { + bidRequest.params = { + placementId: '10433394', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for a valid video bid request but with a member id and no inventory code', function () { + bidRequest.params = { + member: '1234', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid video bid request but with no member id and an inventory code', function () { + bidRequest.params = { + invCode: 'ABCD', + }; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid video bid request but with no params', function () { + bidRequest.params = {}; + bidRequest.mediaTypes.video = validVideoMediaTypes; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + }); + + describe('with native bid requests', function () { + beforeEach(function () { + bidRequest.mediaTypes.native = { + image: { + required: true, + // Sizes is filled in by individual tests + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + } + }; + }); + + it('should return true for a valid native bid request with a single size', function () { + bidRequest.params = VALID_NATIVE_BID_PARAMS; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid native bid request with multiple sizes', function () { + bidRequest.params = VALID_NATIVE_BID_PARAMS; + bidRequest.mediaTypes.native.image.sizes = [[300, 250], [300, 600]]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid native bid request with a member id and inventory code', function () { + bidRequest.params = { + member: '1234', + invCode: 'ABCD', + }; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true for a valid native bid request with a placement id', function () { + bidRequest.params = { + placementId: '10433394', + }; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false for a valid native bid request but with a member id and no inventory code', function () { + bidRequest.params = { + member: '1234', + }; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid native bid request but with no member id and an inventory code', function () { + bidRequest.params = { + invCode: 'ABCD', + }; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a valid native bid request but with no params', function () { + bidRequest.params = {}; + bidRequest.mediaTypes.native.image.sizes = [300, 250]; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false for a native bid request with no image property', function () { + bidRequest.params = VALID_NATIVE_BID_PARAMS; + delete bidRequest.mediaTypes.native.image; + + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + }); + + describe('buildRequests', function () { + let bidRequests; + + beforeEach(function () { + bidRequests = [bidRequest]; + + bidRequests[0].params = VALID_BANNER_BID_PARAMS; + bidRequests[0].mediaTypes.banner = { + sizes: DEFAULT_BID_SIZES, + }; + }); + + it('should use HTTP POST', function () { + const request = spec.buildRequests(bidRequests, {}); + expect(request.method).to.equal('POST'); + }); + + it('should use the correct prebid server URL', function () { + const request = spec.buildRequests(bidRequests, {}); + expect(request.url).to.equal(EXPECTED_PREBID_SERVER_URL); + }); + + it('should add the page URL to the server request', function () { + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + + expect(payload.url).to.exist; + expect(payload.url).to.be.a('string'); + }); + + it('should add GDPR consent to the server request, where present', function () { + const bidderRequest = { + bidderCode: 'vibrantmedia', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: VALID_CONSENT_STRING, + gdprApplies: true, + }, + bids: bidRequests, + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + // TODO: Check that we should not be implementing withCredentials + // expect(request.options).to.deep.equal({withCredentials: true}); + + const payload = JSON.parse(request.data); + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consentString).to.exist.and.to.equal(VALID_CONSENT_STRING); + expect(payload.gdpr.gdprApplies).to.exist.and.to.be.true; + }); + + it('should add USP consent to the server request, where present', function () { + const bidderRequest = { + bidderCode: 'vibrantmedia', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + uspConsent: { + cmpApi: 'iab', + timeout: 10000, + consentData: { + testDatum: true + } + }, + bids: bidRequests + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + // TODO: Check that we should not be implementing withCredentials + // expect(request.options).to.deep.equal({withCredentials: true}); + + const payload = JSON.parse(request.data); + expect(payload.usp).to.exist; + expect(payload.usp.cmpApi).to.exist.and.to.equal('iab'); + expect(payload.usp.timeout).to.exist.and.to.equal(10000); + expect(payload.usp.consentData).to.exist.and.to.deep.equal({ + testDatum: true + }); + }); + + it('should add GDPR and USP consent to the server request, where both present', function () { + const bidderRequest = { + bidderCode: 'vibrantmedia', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: VALID_CONSENT_STRING, + gdprApplies: true, + }, + uspConsent: { + cmpApi: 'iab', + timeout: 10000, + consentData: { + testDatum: true + } + }, + bids: bidRequests, + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + // TODO: Check that we should not be implementing withCredentials + // expect(request.options).to.deep.equal({withCredentials: true}); + + const payload = JSON.parse(request.data); + + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consentString).to.exist.and.to.equal(VALID_CONSENT_STRING); + expect(payload.gdpr.gdprApplies).to.exist.and.to.be.true; + + expect(payload.usp).to.exist; + expect(payload.usp.cmpApi).to.exist.and.to.equal('iab'); + expect(payload.usp.timeout).to.exist.and.to.equal(10000); + expect(payload.usp.consentData).to.exist.and.to.deep.equal({ + testDatum: true + }); + }); + + it('should add window dimensions to the server request', function () { + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + + expect(payload.window).to.exist; + expect(payload.window.width).to.equal(window.innerWidth); + expect(payload.window.height).to.equal(window.innerHeight); + }); + + it('should add the top-level sizes to the bid request, if present', function () { + bidRequest.params = VALID_BANNER_BID_PARAMS; + bidRequest.sizes = DEFAULT_BID_SIZES; + bidRequest.mediaTypes = { + banner: {}, + }; + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(1); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].sizes).to.deep.equal(DEFAULT_BID_SIZES); + expect(payload.biddata[0].mediaTypes).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({}); + }); + + it('should add the list of bids to the bid request, if present', function () { + const testBid = { + bidder: 'testBidder', + params: { + placement: '12345' + } + }; + + bidRequest.params = VALID_BANNER_BID_PARAMS; + bidRequest.bids = [testBid]; + bidRequest.mediaTypes = { + banner: {}, + }; + + // These will be present in the list of bids instead + delete bidRequest.bidId; + delete bidRequest.transactionId; + delete bidRequest.bidder; + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(1); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].bids.length).to.equal(1); + expect(payload.biddata[0].bids[0]).to.deep.equal(testBid); + expect(payload.biddata[0].mediaTypes).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({}); + }); + + it('should add the correct bid data to the server request for one bid request', function () { + bidRequest.params = VALID_BANNER_BID_PARAMS; + bidRequest.mediaTypes = { + banner: { + sizes: DEFAULT_BID_SIZES, + }, + }; + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(1); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].sizes).to.be.undefined; + expect(payload.biddata[0].mediaTypes).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + }); + + it('should add the correct bid data to the server request for multiple bid requests', function () { + bidRequest.params = VALID_BANNER_BID_PARAMS; + bidRequest.mediaTypes = { + banner: { + sizes: DEFAULT_BID_SIZES, + }, + }; + const bid2 = { + bidder: 'vibrantmedia', + params: VALID_VIDEO_BID_PARAMS, + mediaTypes: { + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + } + }, + adUnitCode: 'video-div', + bidId: '30b31c1838de1f', + placementId: '135797531abcdef', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }; + const bid3 = { + bidder: 'vibrantmedia', + params: VALID_NATIVE_BID_PARAMS, + mediaTypes: { + native: { + image: { + required: true, + sizes: [300, 250] + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + } + } + }, + adUnitCode: 'native-div', + bidId: '30b31c1838de14', + placementId: '918273645abcdef', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }; + + bidRequests.push(bid2, bid3); + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(3); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].mediaTypes).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[1]).to.exist; + expect(payload.biddata[1].code).to.equal(bid2.adUnitCode); + expect(payload.biddata[1].id).to.equal(bid2.placementId); + expect(payload.biddata[1].bidder).to.equal(bid2.bidder); + expect(payload.biddata[1].mediaTypes).to.exist; + expect(payload.biddata[1].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[1].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[2]).to.exist; + expect(payload.biddata[2].code).to.equal(bid3.adUnitCode); + expect(payload.biddata[2].id).to.equal(bid3.placementId); + expect(payload.biddata[2].bidder).to.equal(bid3.bidder); + expect(payload.biddata[2].mediaTypes[NATIVE]).to.exist; + expect(payload.biddata[2].mediaTypes[NATIVE]).to.deep.equal(bid3.mediaTypes.native); + }); + + it('should add the correct bid data to the bid request where a bid has multiple media types', function () { + bidRequest.params = VALID_VIDEO_BID_PARAMS; + bidRequest.mediaTypes = { + banner: { + sizes: DEFAULT_BID_SIZES, + }, + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }, + native: { + image: { + required: true, + sizes: [300, 250] + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + } + } + }; + bidRequest.adUnitCode = 'mixed-div'; + + const bid2 = { + bidder: 'vibrantmedia', + params: VALID_VIDEO_BID_PARAMS, + mediaTypes: { + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + } + }, + adUnitCode: 'video-div', + bidId: '30b31c1838de1a', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + + bidRequests.push(bid2); + + const request = spec.buildRequests(bidRequests, {}, true); + const payload = JSON.parse(request.data); + + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(2); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].mediaTypes).to.exist; + expect(Object.keys(payload.biddata[0].mediaTypes).length).to.equal(3); + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[0].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[0].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[0].code).to.equal(bidRequest.adUnitCode); + expect(payload.biddata[0].id).to.equal(bidRequest.placementId); + expect(payload.biddata[0].bidder).to.equal(bidRequest.bidder); + expect(payload.biddata[0].mediaTypes[NATIVE]).to.exist; + expect(payload.biddata[0].mediaTypes[NATIVE]).to.deep.equal(bidRequest.mediaTypes.native); + expect(payload.biddata[1]).to.exist; + expect(payload.biddata[1].code).to.equal(bid2.adUnitCode); + expect(payload.biddata[1].id).to.equal(bid2.placementId); + expect(payload.biddata[1].bidder).to.equal(bid2.bidder); + expect(payload.biddata[1].mediaTypes).to.exist; + expect(Object.keys(payload.biddata[1].mediaTypes).length).to.equal(1); + expect(payload.biddata[1].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[1].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }); + }); + }); + + describe('interpretResponse', function () { + it('returns a valid Prebid API response object for a banner Prebid Server response', function () { + const prebidServerResponse = { + body: [{ + mediaType: 'banner', + requestId: '12345', + cpm: 1, + currency: 'USD', + width: 640, + height: 240, + ad: BANNER_AD, + ttl: 300, + creativeId: '86f4aef9-2f17-421d-84db-5c9814bf4a79', + netRevenue: false, + meta: Object.freeze({ + advertiser: '105600', + width: 300, + height: 250, + isCustom: '1', + progressBar: false, + mpuSrc: '//images.intellitxt.com/a/105600/Genpact/genpact.jpg', + clickURL: '{{click}}' + }) + }] + }; + + const interpretedResponse = spec.interpretResponse(prebidServerResponse, {}); + + expect(interpretedResponse).to.be.a('array'); + expect(interpretedResponse.length).to.equal(1); + + const interpretedBid = interpretedResponse[0]; + + expect(interpretedBid.mediaType).to.equal('banner'); + expect(interpretedBid.requestId).to.equal('12345'); + expect(interpretedBid.cpm).to.equal(1); + expect(interpretedBid.currency).to.equal('USD'); + expect(interpretedBid.width).to.equal(640); + expect(interpretedBid.height).to.equal(240); + expect(interpretedBid.ad).to.equal(BANNER_AD); + expect(interpretedBid.ttl).to.equal(300); + expect(interpretedBid.creativeId).to.equal('86f4aef9-2f17-421d-84db-5c9814bf4a79'); + expect(interpretedBid.netRevenue).to.be.false; + expect(interpretedBid.meta).to.deep.equal(prebidServerResponse.body[0].meta); + expect(interpretedBid.renderer).to.be.undefined; + expect(interpretedBid.adResponse).to.deep.equal(prebidServerResponse); + }); + + it('returns a valid Prebid API response object for a video Prebid Server response', function () { + const prebidServerResponse = Object.freeze({ + body: [{ + mediaType: 'video', + requestId: '67890', + cpm: 2, + currency: 'USD', + width: 600, + height: 300, + ad: VIDEO_AD, + ttl: 300, + creativeId: '248e8e0c-2f17-421d-84db-5c9814bf4a79', + netRevenue: false, + meta: { + advertiser: '105600', + width: 300, + height: 250, + isCustom: '1', + progressBar: false, + mpuSrc: '//images.intellitxt.com/a/105600/Genpact/genpact.jpg', + clickURL: '{{click}}' + }, + vastUrl: 'https://www.example.com/myVastVideo' + }] + }); + + const interpretedResponse = spec.interpretResponse(prebidServerResponse, {}); + + expect(interpretedResponse).to.be.a('array'); + expect(interpretedResponse.length).to.equal(1); + + const interpretedBid = interpretedResponse[0]; + + expect(interpretedBid.mediaType).to.equal('video'); + expect(interpretedBid.requestId).to.equal('67890'); + expect(interpretedBid.cpm).to.equal(2); + expect(interpretedBid.currency).to.equal('USD'); + expect(interpretedBid.width).to.equal(600); + expect(interpretedBid.height).to.equal(300); + expect(interpretedBid.ad).to.equal(VIDEO_AD); + expect(interpretedBid.ttl).to.equal(300); + expect(interpretedBid.creativeId).to.equal('248e8e0c-2f17-421d-84db-5c9814bf4a79'); + expect(interpretedBid.netRevenue).to.be.false; + expect(interpretedBid.meta).to.deep.equal(prebidServerResponse.body[0].meta); + expect(interpretedBid.renderer).to.be.undefined; + expect(interpretedBid.adResponse).to.deep.equal(prebidServerResponse); + }); + + it('returns a valid Prebid API response object for a native Prebid Server response', function () { + const prebidServerResponse = Object.freeze({ + body: [{ + mediaType: 'native', + requestId: '13579', + cpm: 3, + currency: 'USD', + width: 240, + height: 300, + ad: 'https://www.example.com/native-display.html', + ttl: 300, + creativeId: 'd28e8e0c-2f17-421d-84db-5c9814bf4a81', + netRevenue: false, + meta: {}, + title: 'Test native ad bid for 13579', + sponsoredBy: 'Vibrant Media Ltd', + clickUrl: 'https://www.example.com/native-ct.html', + image: { + url: 'https://www.example.com/native-display.html', + width: 240, + height: 300 + } + }] + }); + + const interpretedResponse = spec.interpretResponse(prebidServerResponse, {}); + + expect(interpretedResponse).to.be.a('array'); + expect(interpretedResponse.length).to.equal(1); + + const interpretedBid = interpretedResponse[0]; + + expect(interpretedBid.mediaType).to.equal('native'); + expect(interpretedBid.requestId).to.equal('13579'); + expect(interpretedBid.cpm).to.equal(3); + expect(interpretedBid.currency).to.equal('USD'); + expect(interpretedBid.width).to.equal(240); + expect(interpretedBid.height).to.equal(300); + expect(interpretedBid.ad).to.equal('https://www.example.com/native-display.html'); + expect(interpretedBid.ttl).to.equal(300); + expect(interpretedBid.creativeId).to.equal('d28e8e0c-2f17-421d-84db-5c9814bf4a81'); + expect(interpretedBid.netRevenue).to.be.false; + expect(interpretedBid.meta).to.deep.equal(prebidServerResponse.body[0].meta); + expect(interpretedBid.renderer).to.be.undefined; + expect(interpretedBid.adResponse).to.deep.equal(prebidServerResponse); + }); + + it('returns a valid Prebid API response object for a multi-bid Prebid Server response', function () { + const prebidServerResponse = Object.freeze({ + body: [ + { + mediaType: 'banner', + requestId: '12345', + cpm: 3, + currency: 'USD', + width: 640, + height: 240, + ad: BANNER_AD, + ttl: 300, + creativeId: '86f4aef9-2f17-421d-84db-5c9814bf4a79', + netRevenue: false, + meta: { + advertiser: '105600', + width: 300, + height: 250, + isCustom: '1', + progressBar: false, + mpuSrc: '//images.intellitxt.com/a/105600/Genpact/genpact.jpg', + clickURL: '{{click}}' + } + }, + { + mediaType: 'video', + requestId: '67890', + cpm: 4, + currency: 'USD', + width: 300, + height: 300, + ad: VIDEO_AD, + ttl: 300, + creativeId: 'd28e8e0c-2f17-421d-84db-5c9814bf4a79', + netRevenue: false, + meta: { + advertiser: '105600', + width: 300, + height: 250, + isCustom: '1', + progressBar: false, + mpuSrc: '//images.intellitxt.com/a/105600/Genpact/genpact.jpg', + clickURL: '{{click}}' + }, + vastUrl: 'https://www.example.com/myVastVideo' + }, + { + mediaType: 'native', + requestId: '13579', + cpm: 5, + currency: 'USD', + width: 640, + height: 240, + ad: 'https://www.example.com/native-display.html', + ttl: 300, + creativeId: '888e8e0c-2f17-421d-84db-5c9814bf4a81', + netRevenue: false, + meta: {}, + title: 'Test native ad bid for 13579', + sponsoredBy: 'Vibrant Media Ltd', + clickUrl: 'https://www.example.com/native-ct.html', + image: { + url: 'https://www.example.com/native-display.html', + width: 640, + height: 240 + } + } + ] + }); + + const interpretedResponse = spec.interpretResponse(prebidServerResponse, {}); + + expect(interpretedResponse).to.be.a('array'); + expect(interpretedResponse.length).to.equal(3); + + const interpretedBannerBid = interpretedResponse[0]; + + expect(interpretedBannerBid.mediaType).to.equal('banner'); + expect(interpretedBannerBid.requestId).to.equal('12345'); + expect(interpretedBannerBid.cpm).to.equal(3); + expect(interpretedBannerBid.currency).to.equal('USD'); + expect(interpretedBannerBid.width).to.equal(640); + expect(interpretedBannerBid.height).to.equal(240); + expect(interpretedBannerBid.ad).to.equal(BANNER_AD); + expect(interpretedBannerBid.ttl).to.equal(300); + expect(interpretedBannerBid.creativeId).to.equal('86f4aef9-2f17-421d-84db-5c9814bf4a79'); + expect(interpretedBannerBid.netRevenue).to.be.false; + expect(interpretedBannerBid.meta).to.deep.equal(prebidServerResponse.body[0].meta); + expect(interpretedBannerBid.renderer).to.be.undefined; + expect(interpretedBannerBid.adResponse).to.deep.equal(prebidServerResponse); + + const interpretedVideoBid = interpretedResponse[1]; + + expect(interpretedVideoBid.mediaType).to.equal('video'); + expect(interpretedVideoBid.requestId).to.equal('67890'); + expect(interpretedVideoBid.cpm).to.equal(4); + expect(interpretedVideoBid.currency).to.equal('USD'); + expect(interpretedVideoBid.width).to.equal(300); + expect(interpretedVideoBid.height).to.equal(300); + expect(interpretedVideoBid.ad).to.equal(VIDEO_AD); + expect(interpretedVideoBid.ttl).to.equal(300); + expect(interpretedVideoBid.creativeId).to.equal('d28e8e0c-2f17-421d-84db-5c9814bf4a79'); + expect(interpretedVideoBid.netRevenue).to.be.false; + expect(interpretedVideoBid.meta).to.deep.equal(prebidServerResponse.body[1].meta); + expect(interpretedVideoBid.renderer).to.be.undefined; + expect(interpretedVideoBid.adResponse).to.deep.equal(prebidServerResponse); + + const interpretedNativeBid = interpretedResponse[2]; + + expect(interpretedNativeBid.mediaType).to.equal('native'); + expect(interpretedNativeBid.requestId).to.equal('13579'); + expect(interpretedNativeBid.cpm).to.equal(5); + expect(interpretedNativeBid.currency).to.equal('USD'); + expect(interpretedNativeBid.width).to.equal(640); + expect(interpretedNativeBid.height).to.equal(240); + expect(interpretedNativeBid.ad).to.equal('https://www.example.com/native-display.html'); + expect(interpretedNativeBid.ttl).to.equal(300); + expect(interpretedNativeBid.creativeId).to.equal('888e8e0c-2f17-421d-84db-5c9814bf4a81'); + expect(interpretedNativeBid.netRevenue).to.be.false; + expect(interpretedNativeBid.meta).to.deep.equal(prebidServerResponse.body[2].meta); + expect(interpretedNativeBid.renderer).to.be.undefined; + expect(interpretedNativeBid.adResponse).to.deep.equal(prebidServerResponse); + }); + }); + + describe('Flow tests', function () { + describe('For successive API calls to the public functions', function () { + it('should succeed with one media type per bid', function () { + const transformedBannerBidParams = spec.transformBidParams(VALID_BANNER_BID_PARAMS); + const transformedVideoBidParams = spec.transformBidParams(VALID_VIDEO_BID_PARAMS); + const transformedNativeBidParams = spec.transformBidParams(VALID_NATIVE_BID_PARAMS); + + const bannerBid = { + bidder: 'vibrantmedia', + params: transformedBannerBidParams, + mediaTypes: { + banner: { + sizes: DEFAULT_BID_SIZES, + }, + }, + adUnitCode: 'banner-div', + bidId: '30b31c1838de11', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + const videoBid = { + bidder: 'vibrantmedia', + params: transformedVideoBidParams, + mediaTypes: { + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }, + }, + adUnitCode: 'video-div', + bidId: '30b31c1838de15', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + const nativeBid = { + bidder: 'vibrantmedia', + params: transformedNativeBidParams, + mediaTypes: { + native: { + image: { + required: true, + sizes: [300, 250] + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + } + } + }, + adUnitCode: 'native-div', + bidId: '30b31c1838de12', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + + expect(spec.isBidRequestValid(bannerBid)).to.be.true; + expect(spec.isBidRequestValid(videoBid)).to.be.true; + expect(spec.isBidRequestValid(nativeBid)).to.be.true; + + const bidRequests = [bannerBid, videoBid, nativeBid]; + const validBidderRequest = getValidBidderRequest(bidRequests); + const serverRequest = spec.buildRequests(bidRequests, validBidderRequest); + expect(serverRequest.method).to.equal('POST'); + expect(serverRequest.url).to.equal(EXPECTED_PREBID_SERVER_URL); + + const payload = JSON.parse(serverRequest.data); + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(3); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bannerBid.adUnitCode); + expect(payload.biddata[0].id).to.equal(bannerBid.placementId); + expect(payload.biddata[0].bidder).to.equal(bannerBid.bidder); + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[1]).to.exist; + expect(payload.biddata[1].code).to.equal(videoBid.adUnitCode); + expect(payload.biddata[1].id).to.equal(videoBid.placementId); + expect(payload.biddata[1].bidder).to.equal(videoBid.bidder); + expect(payload.biddata[1].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[1].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES + }); + expect(payload.biddata[2]).to.exist; + expect(payload.biddata[2].code).to.equal(nativeBid.adUnitCode); + expect(payload.biddata[2].id).to.equal(nativeBid.placementId); + expect(payload.biddata[2].bidder).to.equal(nativeBid.bidder); + expect(payload.biddata[2].mediaTypes[NATIVE]).to.exist; + expect(payload.biddata[2].mediaTypes[NATIVE]).to.deep.equal(nativeBid.mediaTypes.native); + + // From here, the API would call the Prebid Server and call interpretResponse, which is covered by tests elsewhere + }); + + it('should succeed with multiple media types for a single bid', function () { + const bidParams = spec.transformBidParams(VALID_VIDEO_BID_PARAMS); + const bid = { + bidder: 'vibrantmedia', + params: bidParams, + mediaTypes: { + banner: { + sizes: DEFAULT_BID_SIZES + }, + video: { + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES + }, + native: { + sizes: DEFAULT_BID_SIZES + } + }, + adUnitCode: 'test-div', + bidId: '30b31c1838de13', + bidderRequestId: '22edbae2733bf6', + placementId: '293857832abfef', + auctionId: '1d1a030790a475', + }; + + expect(spec.isBidRequestValid(bid)).to.be.true; + + const bidRequests = [bid]; + const validBidderRequest = getValidBidderRequest(bidRequests); + const serverRequest = spec.buildRequests(bidRequests, validBidderRequest); + expect(serverRequest.method).to.equal('POST'); + expect(serverRequest.url).to.equal(EXPECTED_PREBID_SERVER_URL); + + const payload = JSON.parse(serverRequest.data); + expect(payload.biddata).to.exist; + expect(payload.biddata.length).to.equal(1); + expect(payload.biddata[0]).to.exist; + expect(payload.biddata[0].code).to.equal(bid.adUnitCode); + expect(payload.biddata[0].id).to.equal(bid.placementId); + expect(payload.biddata[0].bidder).to.equal(bid.bidder); + expect(payload.biddata[0].mediaTypes[BANNER]).to.exist; + expect(payload.biddata[0].mediaTypes[BANNER]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[0].mediaTypes[VIDEO]).to.exist; + expect(payload.biddata[0].mediaTypes[VIDEO]).to.deep.equal({ + context: OUTSTREAM, + sizes: DEFAULT_BID_SIZES, + }); + expect(payload.biddata[0].mediaTypes[NATIVE]).to.exist; + expect(payload.biddata[0].mediaTypes[NATIVE]).to.deep.equal({ + sizes: DEFAULT_BID_SIZES, + }); + + // From here, the API would call the Prebid Server and call interpretResponse, which is covered by tests elsewhere + }); + }); + }); +}); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 35f510fd6ee..0b5dadce09f 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -37,7 +37,8 @@ const BID = { 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', - 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a' + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc' }; const BIDDER_REQUEST = { @@ -163,6 +164,7 @@ describe('VidazooBidAdapter', function () { uniqueDealId: `${hashUrl}_${Date.now().toString()}`, bidderVersion: adapter.version, prebidVersion: version, + schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 37452914e79..8aa127faef2 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -4,6 +4,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import { INSTREAM } from '../../../src/video'; const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; +const PIXELS = ['/test.png', '/test2.png?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}'] describe('vidoomyBidAdapter', function() { const adapter = newBidder(spec); @@ -15,7 +16,8 @@ describe('vidoomyBidAdapter', function() { 'bidder': 'vidoomy', 'params': { pid: '123123', - id: '123123' + id: '123123', + bidfloor: 0.5 }, 'adUnitCode': 'code', 'sizes': [[300, 250]] @@ -31,6 +33,11 @@ describe('vidoomyBidAdapter', function() { expect(spec.isBidRequestValid(bid)).to.equal(false); }); + it('should return false when bidfloor is invalid', function () { + bid.params.bidfloor = 'not a number'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when id is empty', function () { bid.params.id = ''; expect(spec.isBidRequestValid(bid)).to.equal(false); @@ -101,24 +108,24 @@ describe('vidoomyBidAdapter', function() { }); it('attaches source and version to endpoint URL as query params', function () { - expect(request[0].url).to.include(ENDPOINT); - expect(request[1].url).to.include(ENDPOINT); + expect(request[0].url).to.equal(ENDPOINT); + expect(request[1].url).to.equal(ENDPOINT); }); it('only accepts first width and height sizes', function () { - expect(request[0].url).to.include('w=300'); - expect(request[0].url).to.include('h=250'); - expect(request[0].url).to.not.include('w=200'); - expect(request[0].url).to.not.include('h=100'); - expect(request[1].url).to.include('w=400'); - expect(request[1].url).to.include('h=225'); + expect('' + request[0].data.w).to.equal('300'); + expect('' + request[0].data.h).to.equal('250'); + expect('' + request[0].data.w).to.not.equal('200'); + expect('' + request[0].data.h).to.not.equal('100'); + expect('' + request[1].data.w).to.equal('400'); + expect('' + request[1].data.h).to.equal('225'); }); it('should send id and pid parameters', function () { - expect(request[0].url).to.include('id=123123'); - expect(request[0].url).to.include('pid=123123'); - expect(request[1].url).to.include('id=456456'); - expect(request[1].url).to.include('pid=456456'); + expect('' + request[0].data.id).to.equal('123123'); + expect('' + request[0].data.pid).to.equal('123123'); + expect('' + request[1].data.id).to.equal('456456'); + expect('' + request[1].data.pid).to.equal('456456'); }); }); @@ -182,7 +189,8 @@ describe('vidoomyBidAdapter', function() { 'networkName': null, 'primaryCatId': 'IAB3-1', 'secondaryCatIds': null - } + }, + 'pixels': PIXELS } } @@ -206,5 +214,22 @@ describe('vidoomyBidAdapter', function() { expect(result[0].requestId).to.equal(serverResponseBanner.body.requestId); }); + + it('should sync user cookies', function () { + const GDPR_CONSENT = 'GDPR_TEST' + const result = spec.getUserSyncs({ + pixelEnabled: true + }, [serverResponseBanner], { consentString: GDPR_CONSENT, gdprApplies: 1 }, null) + expect(result).to.eql([ + { + type: 'image', + url: PIXELS[0] + }, + { + type: 'image', + url: `/test2.png?gdpr=1&gdpr_consent=${GDPR_CONSENT}` + } + ]) + }); }); }); diff --git a/test/spec/modules/viewability_spec.js b/test/spec/modules/viewability_spec.js new file mode 100644 index 00000000000..ab2753daf53 --- /dev/null +++ b/test/spec/modules/viewability_spec.js @@ -0,0 +1,280 @@ +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import * as utils from 'src/utils.js'; +import * as viewability from 'modules/viewability.js'; + +describe('viewability test', () => { + describe('start measurement', () => { + let sandbox; + let intersectionObserverStub; + let setTimeoutStub; + let observeCalled; + let unobserveCalled; + let ti = 1; + beforeEach(() => { + observeCalled = false; + unobserveCalled = false; + sandbox = sinon.sandbox.create(); + + let fakeIntersectionObserver = (stateChange, options) => { + return { + observe: (element) => { + observeCalled = true; + stateChange([{ isIntersecting: true }]); + }, + unobserve: (element) => { + unobserveCalled = true; + }, + }; + }; + + intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); + setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => { + callback(); + return ti++; + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should trigger appropriate callbacks', () => { + viewability.startMeasurement('0', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); + + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + expect(observeCalled).to.equal(true); + expect(unobserveCalled).to.equal(true); + }); + + it('should trigger img tracker', () => { + let triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); + viewability.startMeasurement('1', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(triggerPixelSpy.callCount).to.equal(1); + }); + + it('should trigger js tracker', () => { + let insertHtmlIntoIframeSpy = sandbox.spy(utils, ['insertHtmlIntoIframe']); + viewability.startMeasurement('2', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(insertHtmlIntoIframeSpy.callCount).to.equal(1); + }); + + it('should trigger callback tracker', () => { + let callbackFired = false; + viewability.startMeasurement('3', {}, { method: 'callback', value: () => { callbackFired = true; } }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(callbackFired).to.equal(true); + }); + + it('should check for vid uniqueness', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.startMeasurement('4', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(0); + + viewability.startMeasurement('4', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide an unregistered vid`, '4')).to.equal(true); + }); + + it('should check for valid criteria', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.startMeasurement('5', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: missing criteria`, { timeInView: 1000 })).to.equal(true); + }); + + it('should check for valid tracker', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.startMeasurement('6', {}, { method: 'callback', value: 'string' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: invalid tracker`, { method: 'callback', value: 'string' })).to.equal(true); + }); + + it('should check if element provided', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.startMeasurement('7', undefined, { method: 'js', value: 'http://my.tracker/123.js' }, { timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: no html element provided`)).to.equal(true); + }); + }); + + describe('stop measurement', () => { + let sandbox; + let intersectionObserverStub; + let setTimeoutStub; + let clearTimeoutStub; + let observeCalled; + let unobserveCalled; + let stateChangeBackup; + let ti = 1; + beforeEach(() => { + observeCalled = false; + unobserveCalled = false; + sandbox = sinon.sandbox.create(); + + let fakeIntersectionObserver = (stateChange, options) => { + return { + observe: (element) => { + stateChangeBackup = stateChange; + observeCalled = true; + stateChange([{ isIntersecting: true }]); + }, + unobserve: (element) => { + unobserveCalled = true; + }, + }; + }; + + intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); + setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => { + // skipping the callback + return ti++; + }); + clearTimeoutStub = sandbox.stub(window, 'clearTimeout').callsFake((timeoutId) => { }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should clear the timeout', () => { + viewability.startMeasurement('10', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); + stateChangeBackup([{ isIntersecting: false }]); + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + sinon.assert.called(clearTimeoutStub); + expect(observeCalled).to.equal(true); + }); + + it('should unobserve', () => { + viewability.startMeasurement('11', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + expect(observeCalled).to.equal(true); + expect(unobserveCalled).to.equal(false); + + viewability.stopMeasurement('11'); + expect(unobserveCalled).to.equal(true); + sinon.assert.called(clearTimeoutStub); + }); + + it('should check for vid existence', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.stopMeasurement('100'); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide a registered vid`, '100')).to.equal(true); + }); + }); + + describe('handle creative messages', () => { + let sandbox; + let intersectionObserverStub; + let setTimeoutStub; + let observeCalled; + let unobserveCalled; + let ti = 1; + let getElementsByTagStub; + let getElementByIdStub; + + let fakeContentWindow = {}; + beforeEach(() => { + observeCalled = false; + unobserveCalled = false; + sandbox = sinon.sandbox.create(); + + let fakeIntersectionObserver = (stateChange, options) => { + return { + observe: (element) => { + observeCalled = true; + stateChange([{ isIntersecting: true }]); + }, + unobserve: (element) => { + unobserveCalled = true; + }, + }; + }; + + intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); + setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => { + callback(); + return ti++; + }); + + getElementsByTagStub = sandbox.stub(document, 'getElementsByTagName').callsFake((tagName) => { + return [{ + contentWindow: fakeContentWindow, + }]; + }); + getElementByIdStub = sandbox.stub(document, 'getElementById').callsFake((id) => { + return {}; + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should find element by contentWindow', () => { + let viewabilityRecord = { + vid: 1000, + tracker: { + value: 'http://my.tracker/123', + method: 'img', + }, + criteria: { inViewThreshold: 0.5, timeInView: 1000 }, + message: 'Prebid Viewability', + action: 'startMeasurement', + }; + let data = JSON.stringify(viewabilityRecord); + + viewability.receiveMessage({ + data: data, + source: fakeContentWindow, + }); + + sinon.assert.called(getElementsByTagStub); + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + expect(observeCalled).to.equal(true); + expect(unobserveCalled).to.equal(true); + }); + + it('should find element by id', () => { + let viewabilityRecord = { + vid: 1001, + tracker: { + value: 'http://my.tracker/123', + method: 'img', + }, + criteria: { inViewThreshold: 0.5, timeInView: 1000 }, + message: 'Prebid Viewability', + action: 'startMeasurement', + elementId: '1', + }; + let data = JSON.stringify(viewabilityRecord); + viewability.receiveMessage({ + data: data, + }); + + sinon.assert.called(getElementByIdStub); + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + expect(observeCalled).to.equal(true); + expect(unobserveCalled).to.equal(true); + }); + + it('should stop measurement', () => { + let viewabilityRecord = { + vid: 1001, + message: 'Prebid Viewability', + action: 'stopMeasurement', + }; + let data = JSON.stringify(viewabilityRecord); + viewability.receiveMessage({ + data: data, + }); + + expect(unobserveCalled).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index c3d2d216586..4aaaf996f58 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/visxBidAdapter.js'; import { config } from 'src/config.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from 'src/utils.js'; +import { makeSlot } from '../integration/faker/googletag.js'; describe('VisxAdapter', function () { const adapter = newBidder(spec); @@ -17,7 +18,7 @@ describe('VisxAdapter', function () { let bid = { 'bidder': 'visx', 'params': { - 'uid': '903536' + 'uid': 903536 }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], @@ -39,6 +40,15 @@ describe('VisxAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); + it('should return false when uid can not be parsed as number', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 'sdvsdv' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('it should fail on invalid video bid', function () { let videoBid = Object.assign({}, bid); videoBid.mediaTypes = { @@ -101,7 +111,7 @@ describe('VisxAdapter', function () { { 'bidder': 'visx', 'params': { - 'uid': 903535 + 'uid': '903535' }, 'adUnitCode': 'adunit-code-2', 'sizes': [[728, 90], [300, 250]], @@ -145,17 +155,17 @@ describe('VisxAdapter', function () { const expectedFullImps = [{ 'id': '30b31c1838de1e', 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, - 'ext': {'bidder': {'uid': 903535}} + 'ext': {'bidder': {'uid': 903535, 'adslotExists': false}} }, { 'id': '3150ccb55da321', 'banner': {'format': [{'w': 728, 'h': 90}, {'w': 300, 'h': 250}]}, - 'ext': {'bidder': {'uid': 903535}} + 'ext': {'bidder': {'uid': 903535, 'adslotExists': false}} }, { 'id': '42dbe3a7168a6a', 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, - 'ext': {'bidder': {'uid': 903536}} + 'ext': {'bidder': {'uid': 903536, 'adslotExists': false}} }, { 'id': '39a4e3a7168a6a', @@ -452,7 +462,122 @@ describe('VisxAdapter', function () { 'imp': [{ 'id': '39aff3a7169a6a', 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, - 'ext': {'bidder': {'uid': 903538}} + 'ext': {'bidder': {'uid': 903538, 'adslotExists': false}} + }], + 'tmax': 3000, + 'cur': ['EUR'], + 'source': { + 'ext': { + 'wrapperType': 'Prebid_js', + 'wrapperVersion': '$prebid.version$' + } + }, + 'site': {'page': referrer} + }); + }); + }); + + describe('buildRequests (check ad slot exists)', function () { + function parseRequest(url) { + const res = {}; + (url.split('?')[1] || '').split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + const bidderRequest = { + timeout: 3000, + refererInfo: { + referer: 'https://example.com' + } + }; + const referrer = bidderRequest.refererInfo.referer; + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': 903535 + }, + 'adUnitCode': 'visx-adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': 903535 + }, + 'adUnitCode': 'visx-adunit-code-2', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + let sandbox; + let documentStub; + + before(function() { + sandbox = sinon.sandbox.create(); + documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs('visx-adunit-code-1').returns({ + id: 'visx-adunit-code-1' + }); + documentStub.withArgs('visx-adunit-element-2').returns({ + id: 'visx-adunit-element-2' + }); + }); + + after(function() { + sandbox.restore(); + }); + + it('should find ad slot by ad unit code as element id', function () { + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const payload = parseRequest(request.url); + expect(payload).to.be.an('object'); + expect(payload).to.have.property('auids', '903535'); + + const postData = request.data; + expect(postData).to.be.an('object'); + expect(postData).to.deep.equal({ + 'id': '22edbae2733bf6', + 'imp': [{ + 'id': '30b31c1838de1e', + 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, + 'ext': {'bidder': {'uid': 903535, 'adslotExists': true}} + }], + 'tmax': 3000, + 'cur': ['EUR'], + 'source': { + 'ext': { + 'wrapperType': 'Prebid_js', + 'wrapperVersion': '$prebid.version$' + } + }, + 'site': {'page': referrer} + }); + }); + + it('should find ad slot by ad unit code as adUnitPath', function () { + makeSlot({code: 'visx-adunit-code-2', divId: 'visx-adunit-element-2'}); + + const request = spec.buildRequests([bidRequests[1]], bidderRequest); + const payload = parseRequest(request.url); + expect(payload).to.be.an('object'); + expect(payload).to.have.property('auids', '903535'); + + const postData = request.data; + expect(postData).to.be.an('object'); + expect(postData).to.deep.equal({ + 'id': '22edbae2733bf6', + 'imp': [{ + 'id': '30b31c1838de1e', + 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, + 'ext': {'bidder': {'uid': 903535, 'adslotExists': true}} }], 'tmax': 3000, 'cur': ['EUR'], @@ -1147,9 +1272,10 @@ describe('VisxAdapter', function () { }); it('onTimeout', function () { - const data = { timeout: 3000, bidId: '23423', params: { uid: 1 } }; + const data = [{ timeout: 3000, adUnitCode: 'adunit-code-1', auctionId: '1cbd2feafe5e8b', bidder: 'visx', bidId: '23423', params: [{ uid: '1' }] }]; + const expectedData = [{ ...data[0], params: [{ uid: 1 }] }]; spec.onTimeout(data); - expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/bid_timeout?data=' + JSON.stringify(data))).to.equal(true); + expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/bid_timeout//' + JSON.stringify(expectedData))).to.equal(true); }); }); diff --git a/test/spec/modules/weboramaRtdProvider_spec.js b/test/spec/modules/weboramaRtdProvider_spec.js index 155f26990a7..0f0af4efe2f 100644 --- a/test/spec/modules/weboramaRtdProvider_spec.js +++ b/test/spec/modules/weboramaRtdProvider_spec.js @@ -1,13 +1,27 @@ -import { setBigseaContextualProfile, weboramaSubmodule } from 'modules/weboramaRtdProvider.js'; -import { server } from 'test/mocks/xhr.js'; -import {config} from 'src/config.js'; - -const responseHeader = {'Content-Type': 'application/json'}; - -// TODO fix it - -describe('weboramaRtdProvider', function() { - describe('weboramaSubmodule', function() { +import { + weboramaSubmodule +} from 'modules/weboramaRtdProvider.js'; +import { + server +} from 'test/mocks/xhr.js'; +import { + storage, + DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY +} from '../../../modules/weboramaRtdProvider.js'; +import { + config +} from 'src/config.js'; +import { + getGlobal +} from 'src/prebidGlobal.js'; +import 'src/prebid.js'; + +const responseHeader = { + 'Content-Type': 'application/json' +}; + +describe('weboramaRtdProvider', function () { + describe('weboramaSubmodule', function () { it('successfully instantiates and call contextual api', function () { const moduleConfig = { params: { @@ -18,271 +32,967 @@ describe('weboramaRtdProvider', function() { } }; - expect(weboramaSubmodule.init(moduleConfig)).to.equal(true); - - let request = server.requests[0]; - - expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); - expect(request.method).to.equal('GET') + expect(weboramaSubmodule.init(moduleConfig)).to.equal(true); }); - it('instantiate without token should fail', function () { + + it('instantiate without contextual token should fail', function () { const moduleConfig = { params: { weboCtxConf: {} } }; - expect(weboramaSubmodule.init(moduleConfig)).to.equal(false); + expect(weboramaSubmodule.init(moduleConfig)).to.equal(false); }); - }); - describe('Add Contextual Data', function() { - beforeEach(function() { - let conf = { - site: { - ext: { - data: { - inventory: ['value1'] - } - } - }, - user: { - ext: { - data: { - visitor: ['value2'] - } - } - }, - cur: ['USD'] - }; - - config.setConfig({ortb2: conf}); - }); - it('should set targeting and ortb2 if omit setTargeting', function() { + it('instantiate with empty weboUserData conf should return true', function () { const moduleConfig = { params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setOrtb2: true, - } + weboUserDataConf: {} } }; - const data = { - webo_ctx: ['foo', 'bar'], - webo_ds: ['baz'], - }; - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + expect(weboramaSubmodule.init(moduleConfig)).to.equal(true); + }); + }); - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); + describe('Handle Set Targeting', function () { + let sandbox; - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + beforeEach(function () { + sandbox = sinon.sandbox.create(); - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, - }); + storage.removeDataFromLocalStorage('webo_wam2gam_entry'); - const ortb2 = config.getConfig('ortb2'); + getGlobal().setConfig({ + ortb2: undefined + }); + }); - expect(ortb2.site.ext.data.webo_ctx).to.deep.equal(data.webo_ctx); - expect(ortb2.site.ext.data.webo_ds).to.deep.equal(data.webo_ds); + afterEach(function () { + sandbox.restore(); }); - it('should set targeting and ortb2 with setTargeting=true', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, - setOrtb2: true, + describe('Add Contextual Data', function () { + it('should set gam targeting and send to bidders by default', function () { + let onDataResponse = {}; + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + onData: (data, site) => { + onDataResponse = { + data: data, + site: site, + }; + }, + } } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - webo_ds: ['baz'], - }; - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); - - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); - - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); - - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + 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/profile?token=foo&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(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_ctx=foo;webo_ctx=bar;webo_ds=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_ctx=foo,bar|webo_ds=baz'); + 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.adUnits[0].bids[4].ortb2).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + expect(getGlobal().getConfig('ortb2')).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + expect(onDataResponse).to.deep.equal({ + data: data, + site: true, + }); }); - const ortb2 = config.getConfig('ortb2'); - - expect(ortb2.site.ext.data.webo_ctx).to.deep.equal(data.webo_ctx); - expect(ortb2.site.ext.data.webo_ds).to.deep.equal(data.webo_ds); - }); - it('should set targeting and ortb2 only webo_ctx with setTargeting=true', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, - setOrtb2: true, + it('should set gam targeting but not send to bidders with setPrebidTargeting=true/sendToBidders=false', function () { + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: true, + sendToBidders: false, + } } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - }; - - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); - - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); - - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); - - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + } + }, { + 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/profile?token=foo&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(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ + foo: ['bar'] + }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.be.undefined; + expect(getGlobal().getConfig('ortb2')).to.be.undefined; }); - const ortb2 = config.getConfig('ortb2'); + it('should set gam targeting but not send to bidders with (submodule override) setPrebidTargeting=true/(global) sendToBidders=false', function () { + let onDataResponse = {}; + const moduleConfig = { + params: { + setPrebidTargeting: false, + sendToBidders: false, + onData: (data, site) => { + onDataResponse = { + data: data, + site: site, + }; + }, + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: true, // submodule parameter will override module parameter + } + } + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }] + }] + }; + 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/profile?token=foo&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(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(1); + expect(getGlobal().getConfig('ortb2')).to.be.undefined; + + expect(onDataResponse).to.deep.equal({ + data: data, + site: true, + }); + }); - expect(ortb2.site.ext.data.webo_ctx).to.deep.equal(data.webo_ctx); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); - }); - it('should set only targeting and not ortb2 with setTargeting=true and setOrtb2=false', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, - setOrtb2: false, + it('should not set gam targeting with setPrebidTargeting=false but send to bidders', function () { + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: false, + } } + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + } + }, { + bidder: 'other', + }] + }] } - }; - const data = { - webo_ctx: ['foo', 'bar'], - }; - - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); - - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); + const onDoneSpy = sinon.spy(); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, - }); + let request = server.requests[0]; - const ortb2 = config.getConfig('ortb2'); + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; - expect(ortb2.site.ext.data).to.not.have.property('webo_ctx'); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); - }); - it('should set only targeting and not ortb2 with setTargeting=true and omit setOrtb2', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, - } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - }; + request.respond(200, responseHeader, JSON.stringify(data)); - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + expect(onDoneSpy.calledOnce).to.be.true; - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + expect(targeting).to.deep.equal({}); - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;webo_ctx=foo;webo_ctx=bar;webo_ds=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar|webo_ctx=foo,bar|webo_ds=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ + foo: ['bar'], + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar', + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }, + visitor: { + baz: 'bam', + } + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + expect(getGlobal().getConfig('ortb2')).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); }); - const ortb2 = config.getConfig('ortb2'); - - expect(ortb2.site.ext.data).to.not.have.property('webo_ctx'); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); + it('should use default profile in case of api error', function () { + const defaultProfile = { + webo_ctx: ['baz'], + }; + let onDataResponse = {}; + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: true, + defaultProfile: defaultProfile, + onData: (data, site) => { + onDataResponse = { + data: data, + site: site, + }; + }, + } + } + }; + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + 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/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(500, responseHeader); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + 'adunit2': defaultProfile, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_ctx=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_ctx=baz'); + 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.adUnits[0].bids[4].ortb2).to.deep.equal({ + site: { + ext: { + data: defaultProfile + }, + } + }); + expect(getGlobal().getConfig('ortb2')).to.deep.equal({ + site: { + ext: { + data: defaultProfile + }, + } + }); + expect(onDataResponse).to.deep.equal({ + data: defaultProfile, + site: true, + }); + }); }); - it('should set only ortb2 with setTargeting=false', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: false, - setOrtb2: true, + describe('Add user-centric data (WAM2GAM)', function () { + it('should set gam targeting from local storage and send to bidders by default', function () { + let onDataResponse = {}; + const moduleConfig = { + params: { + weboUserDataConf: { + accountID: 12345, + onData: (data, site) => { + onDataResponse = { + data: data, + site: site, + }; + }, + } } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - }; - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); - - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); - - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); - - expect(targeting).to.deep.equal({}); - - const ortb2 = config.getConfig('ortb2'); - - expect(ortb2.site.ext.data.webo_ctx).to.deep.equal(data.webo_ctx); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); - }); - it('should use default profile in case of api error', function() { - const defaultProfile = { - webo_ctx: ['baz'], - }; - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, - defaultProfile: defaultProfile, + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + 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(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_cs=foo;webo_cs=bar;webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_cs=foo,bar|webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + visitor: data + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + user: { + ext: { + data: data + }, } - } - }; - - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + }); + expect(getGlobal().getConfig('ortb2')).to.deep.equal({ + user: { + ext: { + data: data + }, + } + }); + expect(onDataResponse).to.deep.equal({ + data: data, + site: false, + }); + }); - let request = server.requests[0]; - request.respond(500, responseHeader); + it('should set gam targeting but not send to bidders with setPrebidTargeting=true/sendToBidders=false', function () { + const moduleConfig = { + params: { + weboUserDataConf: { + setPrebidTargeting: true, + sendToBidders: false + } + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + } + }, { + 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(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ + foo: ['bar'] + }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar' + }, + visitor: { + baz: 'bam' + } + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.be.undefined; + expect(getGlobal().getConfig('ortb2')).to.be.undefined; + }); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + it('should set gam targeting but not send to bidders with (submodule override) setPrebidTargeting=true/(global) sendToBidders=false', function () { + let onDataResponse = {}; + const moduleConfig = { + params: { + setPrebidTargeting: false, + sendToBidders: false, + onData: (data, site) => { + onDataResponse = { + data: data, + site: site, + }; + }, + weboUserDataConf: { + setPrebidTargeting: true, // submodule parameter will override module parameter + } + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }] + }] + }; + 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(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(1); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); + expect(getGlobal().getConfig('ortb2')).to.be.undefined; + expect(onDataResponse).to.deep.equal({ + data: data, + site: false, + }); + }); - expect(targeting).to.deep.equal({ - 'adunit1': defaultProfile, - 'adunit2': defaultProfile, + it('should not set gam targeting with setPrebidTargeting=false but send to bidders', function () { + const moduleConfig = { + params: { + weboUserDataConf: { + setPrebidTargeting: false, + } + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }, { + bidder: 'pubmatic', + params: { + dctr: 'foo=bar' + } + }, { + bidder: 'appnexus', + params: { + keywords: { + foo: ['bar'] + } + } + }, { + bidder: 'rubicon', + params: { + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + } + } + }, { + 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(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({}); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;webo_cs=foo;webo_cs=bar;webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar|webo_cs=foo,bar|webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ + foo: ['bar'], + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: { + foo: 'bar', + }, + visitor: { + baz: 'bam', + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + } + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + user: { + ext: { + data: data + }, + } + }); + expect(getGlobal().getConfig('ortb2')).to.deep.equal({ + user: { + ext: { + data: data + }, + } + }); }); - const ortb2 = config.getConfig('ortb2'); + it('should use default profile in case of nothing on local storage', function () { + const defaultProfile = { + webo_audiences: ['baz'] + }; + const moduleConfig = { + params: { + weboUserDataConf: { + setPrebidTargeting: true, + defaultProfile: defaultProfile, + } + } + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + 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(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + 'adunit2': defaultProfile, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + visitor: defaultProfile + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + user: { + ext: { + data: defaultProfile + }, + } + }); + expect(getGlobal().getConfig('ortb2')).to.deep.equal({ + user: { + ext: { + data: defaultProfile + }, + } + }); + }); - expect(ortb2.site.ext.data).to.not.have.property('webo_ctx'); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); + it('should use default profile if cant read from local storage', function () { + const defaultProfile = { + webo_audiences: ['baz'] + }; + let onDataResponse = {}; + const moduleConfig = { + params: { + weboUserDataConf: { + setPrebidTargeting: true, + defaultProfile: defaultProfile, + onData: (data, site) => { + onDataResponse = { + data: data, + site: site, + }; + }, + } + } + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + 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(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + 'adunit2': defaultProfile, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + visitor: defaultProfile + }); + expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + user: { + ext: { + data: defaultProfile + }, + } + }); + expect(getGlobal().getConfig('ortb2')).to.deep.equal({ + user: { + ext: { + data: defaultProfile + }, + } + }); + expect(onDataResponse).to.deep.equal({ + data: defaultProfile, + site: false, + }); + }); }); }); }); diff --git a/test/spec/modules/welectBidAdapter_spec.js b/test/spec/modules/welectBidAdapter_spec.js new file mode 100644 index 00000000000..2f2af35eaec --- /dev/null +++ b/test/spec/modules/welectBidAdapter_spec.js @@ -0,0 +1,211 @@ +import { expect } from 'chai'; +import { spec as adapter } from 'modules/welectBidAdapter.js'; + +describe('WelectAdapter', function () { + describe('Check methods existance', 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'); + }); + }); + + describe('Check method isBidRequestValid return', function () { + let bid = { + bidder: 'welect', + params: { + placementId: 'exampleAlias', + domain: 'www.welect.de' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + }; + let bid2 = { + bidder: 'welect', + params: { + domain: 'www.welect.de' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360] + } + }, + }; + + it('should be true', function () { + expect(adapter.isBidRequestValid(bid)).to.be.true; + }); + + it('should be false because the placementId is missing', function () { + expect(adapter.isBidRequestValid(bid2)).to.be.false; + }); + }); + + describe('Check buildRequests method', function () { + // Bids to be formatted + let bid1 = { + bidder: 'welect', + params: { + placementId: 'exampleAlias' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bidId: 'abdc' + }; + let bid2 = { + bidder: 'welect', + params: { + placementId: 'exampleAlias', + domain: 'www.welect2.de' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bidId: 'abdc', + gdprConsent: { + gdprApplies: 1, + gdprConsent: 'some_string' + } + }; + + let data1 = { + bid_id: 'abdc', + width: 640, + height: 360 + } + + let data2 = { + bid_id: 'abdc', + width: 640, + height: 360, + gdpr_consent: { + gdprApplies: 1, + tcString: 'some_string' + } + } + + // Formatted requets + let request1 = { + method: 'POST', + url: 'https://www.welect.de/api/v2/preflight/exampleAlias', + data: data1, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + }; + + let request2 = { + method: 'POST', + url: 'https://www.welect2.de/api/v2/preflight/exampleAlias', + data: data2, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + } + + it('defaults to www.welect.de, without gdpr object', function () { + expect(adapter.buildRequests([bid1])).to.deep.equal([request1]); + }) + + it('must return the right formatted requests, with gdpr object', function () { + expect(adapter.buildRequests([bid2])).to.deep.equal([request2]); + }); + }); + + describe('Check interpretResponse method return', function () { + // invalid server response + let unavailableResponse = { + body: { + available: false + } + }; + + let availableResponse = { + body: { + available: true, + bidResponse: { + ad: { + video: 'some vast url' + }, + meta: { + advertiserDomains: [], + }, + cpm: 17, + creativeId: 'svmpreview', + currency: 'EUR', + netRevenue: true, + requestId: 'some bid id', + ttl: 120, + vastUrl: 'some vast url', + height: 640, + width: 320 + } + } + } + // bid Request + let bid = { + data: { + bid_id: 'some bid id', + width: 640, + height: 320, + gdpr_consent: { + gdprApplies: 1, + tcString: 'some_string' + } + }, + method: 'POST', + url: 'https://www.welect.de/api/v2/preflight/exampleAlias', + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + }; + // Formatted reponse + let result = { + ad: { + video: 'some vast url' + }, + meta: { + advertiserDomains: [] + }, + cpm: 17, + creativeId: 'svmpreview', + currency: 'EUR', + height: 640, + netRevenue: true, + requestId: 'some bid id', + ttl: 120, + vastUrl: 'some vast url', + width: 320 + } + + it('if response reflects unavailability, should be empty', function () { + expect(adapter.interpretResponse(unavailableResponse, bid)).to.deep.equal([]); + }); + + it('if response reflects availability, should equal result', function () { + expect(adapter.interpretResponse(availableResponse, bid)).to.deep.equal([result]) + }) + }); +}); diff --git a/test/spec/modules/widespaceBidAdapter_spec.js b/test/spec/modules/widespaceBidAdapter_spec.js index af8d505b4f4..0a0af1f1229 100644 --- a/test/spec/modules/widespaceBidAdapter_spec.js +++ b/test/spec/modules/widespaceBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import {spec, storage} from 'modules/widespaceBidAdapter.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {includes} from 'src/polyfill.js'; describe('+widespaceAdatperTest', function () { // Dummy bid request diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahoosspBidAdapter_spec.js index 5eb82b399cc..e301218741c 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahoosspBidAdapter_spec.js @@ -11,7 +11,7 @@ const DEFAULT_AD_UNIT_CODE = '/19968336/header-bid-tag-1'; const DEFAULT_AD_UNIT_TYPE = 'banner'; const DEFAULT_PARAMS_BID_OVERRIDE = {}; const DEFAULT_VIDEO_CONTEXT = 'instream'; -const ADAPTER_VERSION = '1.0.1'; +const ADAPTER_VERSION = '1.0.2'; const PREBID_VERSION = '$prebid.version$'; const INTEGRATION_METHOD = 'prebid.js'; @@ -177,6 +177,16 @@ describe('YahooSSP Bid Adapter:', () => { expect(obj).to.be.an('object'); }); + describe('Validate basic properties', () => { + it('should define the correct bidder code', () => { + expect(spec.code).to.equal('yahoossp') + }); + + it('should define the correct vendor ID', () => { + expect(spec.gvlid).to.equal(25) + }); + }); + describe('getUserSyncs()', () => { const IMAGE_PIXEL_URL = 'http://image-pixel.com/foo/bar?1234&baz=true'; const IFRAME_ONE_URL = 'http://image-iframe.com/foo/bar?1234&baz=true'; @@ -656,6 +666,33 @@ describe('YahooSSP Bid Adapter:', () => { const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.imp[0].ext.data).to.deep.equal(validBidRequests[0].ortb2Imp.ext.data); }); + // adUnit.ortb2Imp.instl + it(`should allow adUnit.ortb2Imp.instl numeric boolean "1" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: 1 + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.deep.equal(validBidRequests[0].ortb2Imp.instl); + }); + + it(`should prevent adUnit.ortb2Imp.instl boolean "true" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: true + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.not.exist; + }); + + it(`should prevent adUnit.ortb2Imp.instl boolean "false" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: false + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.not.exist; + }); }); describe('e2etest mode support:', () => { @@ -1328,5 +1365,13 @@ describe('YahooSSP Bid Adapter:', () => { expect(response[0].ttl).to.equal(500); }); }); + + describe('Aliasing support', () => { + it('should return undefined as the bidder code value', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].bidderCode).to.be.undefined; + }); + }); }); }); diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js new file mode 100644 index 00000000000..833f883fb7c --- /dev/null +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -0,0 +1,166 @@ +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, + }, + }; + } + + function getBidRequest() { + return { + ...getBidConfig(), + bidId: 'bidid-1', + adUnitCode: 'adUnit-123', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + }, + }, + }; + } + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + const bid = getBidConfig(); + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when required params not found', function () { + expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('should return false when required params.pageId are not passed', function () { + const bid = getBidConfig(); + delete bid.params.pageId; + + expect(spec.isBidRequestValid(bid)).to.be.false + }); + + it('should return false when required params.impId are not passed', function () { + const bid = getBidConfig(); + delete bid.params.impId; + + expect(spec.isBidRequestValid(bid)).to.be.false + }); + }); + + describe('buildRequests', function () { + const refererUrl = 'https://yandex.ru/secure-ads'; + + const gdprConsent = { + gdprApplies: 1, + consentString: 'concent-string', + apiVersion: 1, + }; + + const bidderRequest = { + refererInfo: { + referer: refererUrl + }, + gdprConsent + }; + + it('creates a valid banner request', function () { + const bannerRequest = getBidRequest(); + bannerRequest.getFloor = () => ({ + currency: 'USD', + // floor: 0.5 + }); + + const requests = spec.buildRequests([bannerRequest], bidderRequest); + + expect(requests).to.have.lengthOf(1); + const request = requests[0]; + + expect(request).to.exist; + const { method, url, data } = request; + + expect(method).to.equal('POST'); + + const parsedRequestUrl = parseUrl(url); + const { search: query } = parsedRequestUrl + + expect(parsedRequestUrl.hostname).to.equal('bs-metadsp.yandex.ru'); + expect(parsedRequestUrl.pathname).to.equal('/metadsp/123'); + + expect(query['imp-id']).to.equal('1'); + expect(query['target-ref']).to.equal('yandex.ru'); + expect(query['ssp-id']).to.equal('10500'); + + expect(query['gdpr']).to.equal('1'); + expect(query['tcf-consent']).to.equal('concent-string'); + + expect(request.data).to.exist; + expect(data.site).to.not.equal(null); + expect(data.site.page).to.equal('yandex.ru'); + + // expect(data.device).to.not.equal(null); + // expect(data.device.w).to.equal(window.innerWidth); + // expect(data.device.h).to.equal(window.innerHeight); + + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.equal(300); + expect(data.imp[0].banner.h).to.equal(250); + }); + }); + + describe('response handler', function () { + const bannerRequest = getBidRequest(); + + const bannerResponse = { + body: { + seatbid: [{ + bid: [ + { + impid: '1', + price: 0.3, + crid: 321, + adm: '', + w: 300, + h: 250, + adomain: [ + 'example.com' + ], + adid: 'yabs.123=', + } + ] + }], + cur: 'USD', + }, + }; + + it('handles banner responses', function () { + bannerRequest.bidRequest = { + mediaType: BANNER, + bidId: 'bidid-1', + }; + const result = spec.interpretResponse(bannerResponse, bannerRequest); + + expect(result).to.have.lengthOf(1); + expect(result[0]).to.exist; + + const rtbBid = result[0]; + expect(rtbBid.width).to.equal(300); + expect(rtbBid.height).to.equal(250); + expect(rtbBid.cpm).to.be.within(0.1, 0.5); + expect(rtbBid.ad).to.equal(''); + expect(rtbBid.currency).to.equal('USD'); + expect(rtbBid.netRevenue).to.equal(true); + expect(rtbBid.ttl).to.equal(180); + + expect(rtbBid.meta.advertiserDomains).to.deep.equal(['example.com']); + }); + }); +}); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 698bfb92888..e4d258ecdea 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -73,6 +73,12 @@ const VIDEO_REQUEST = Object.assign({}, REQUEST, { } }) +const NATIVE_REQUEST = Object.assign({}, REQUEST, { + 'mediaTypes': { + 'native': { } + } +}) + const RESPONSE = { advertiser: 'yieldlab', curl: 'https://www.yieldlab.de', @@ -84,6 +90,42 @@ const RESPONSE = { adtype: 'BANNER' } +const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { + 'adtype': 'NATIVE', + 'native': { + 'link': { + 'url': 'https://www.yieldlab.de' + }, + 'assets': [ + { + 'id': 1, + 'title': { + 'text': 'This is a great headline' + } + }, + { + 'id': 2, + 'img': { + 'url': 'https://localhost:8080/yl-logo100x100.jpg', + 'w': 100, + 'h': 100 + } + }, + { + 'id': 3, + 'data': { + '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' + ] + } +}) + const VIDEO_RESPONSE = Object.assign({}, RESPONSE, { 'adtype': 'VIDEO' }) @@ -297,6 +339,45 @@ describe('yieldlabBidAdapter', function () { expect(result[0].vastUrl).to.include('&id=abc') }) + it('should add adUrl and native assets when type is Native', function () { + const result = spec.interpretResponse({body: [NATIVE_RESPONSE]}, {validBidRequests: [NATIVE_REQUEST], queryParams: REQPARAMS}) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].mediaType).to.equal('native') + expect(result[0].adUrl).to.include('https://ad.yieldlab.net/d/1111/2222/?ts=') + expect(result[0].native.title).to.equal('This is a great headline') + expect(result[0].native.body).to.equal('Native body value') + expect(result[0].native.image.url).to.equal('https://localhost:8080/yl-logo100x100.jpg') + expect(result[0].native.image.width).to.equal(100) + expect(result[0].native.image.height).to.equal(100) + expect(result[0].native.clickUrl).to.equal('https://www.yieldlab.de') + expect(result[0].native.impressionTrackers.length).to.equal(3) + }) + + it('should add adUrl and default native assets when type is Native', function () { + const NATIVE_RESPONSE_2 = Object.assign({}, NATIVE_RESPONSE, { + 'native': { + 'link': { + 'url': 'https://www.yieldlab.de' + }, + 'assets': [], + 'imptrackers': [] + } + }) + const result = spec.interpretResponse({body: [NATIVE_RESPONSE_2]}, {validBidRequests: [NATIVE_REQUEST], queryParams: REQPARAMS}) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].mediaType).to.equal('native') + expect(result[0].adUrl).to.include('https://ad.yieldlab.net/d/1111/2222/?ts=') + expect(result[0].native.title).to.equal('') + expect(result[0].native.body).to.equal('') + expect(result[0].native.image.url).to.equal('') + expect(result[0].native.image.width).to.equal(0) + expect(result[0].native.image.height).to.equal(0) + }) + it('should append gdpr parameters to vastUrl', function () { const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST], queryParams: REQPARAMS_GDPR}) @@ -333,4 +414,40 @@ describe('yieldlabBidAdapter', function () { expect(result[0].vastUrl).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') }) }) + + describe('getUserSyncs', function () { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: false + }; + const expectedUrlSnippets = ['https://ad.yieldlab.net/d/6846326/766/2x2?', 'ts=', 'type=h']; + + it('should return user sync as expected', function () { + const bidRequest = { + gdprConsent: { + consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', + gdprApplies: true + }, + uspConsent: '1YYY' + }; + const sync = spec.getUserSyncs(syncOptions, [], bidRequest.gdprConsent, bidRequest.uspConsent); + expect(expectedUrlSnippets.every(urlSnippet => sync[0].url.includes(urlSnippet))); + expect(sync[0].url).to.have.string('gdpr=' + Number(bidRequest.gdprConsent.gdprApplies)); + expect(sync[0].url).to.have.string('gdpr_consent=' + bidRequest.gdprConsent.consentString); + // USP consent should be ignored + expect(sync[0].url).not.have.string('usp_consent='); + expect(sync[0].type).to.have.string('iframe'); + }); + + it('should return user sync even without gdprApplies in gdprConsent', function () { + const gdprConsent = { + consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA' + } + const sync = spec.getUserSyncs(syncOptions, [], gdprConsent, undefined); + expect(expectedUrlSnippets.every(urlSnippet => sync[0].url.includes(urlSnippet))); + expect(sync[0].url).to.have.string('gdpr_consent=' + gdprConsent.consentString); + expect(sync[0].url).not.have.string('gdpr='); + expect(sync[0].type).to.have.string('iframe'); + }); + }); }) diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 3eee9e44453..f72705a79ac 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -511,6 +511,7 @@ describe('YieldmoAdapter', function () { body: [{ callback_id: '21989fdbef550a', cpm: 3.45455, + publisherDealId: 'YMO_123', width: 300, height: 250, ad: '' + @@ -525,6 +526,7 @@ describe('YieldmoAdapter', function () { const newResponse = spec.interpretResponse(mockServerResponse()); expect(newResponse.length).to.be.equal(1); expect(newResponse[0]).to.deep.equal({ + dealId: 'YMO_123', requestId: '21989fdbef550a', cpm: 3.45455, width: 300, @@ -552,6 +554,7 @@ describe('YieldmoAdapter', function () { crid: 'dd65c0a7536aff', impid: '91ea8bba1', price: 1.5, + dealid: 'YMO_456' }, }, ]; @@ -574,6 +577,7 @@ describe('YieldmoAdapter', function () { const newResponse = spec.interpretResponse(response, bidRequest); expect(newResponse.length).to.be.equal(2); expect(newResponse[1]).to.deep.equal({ + dealId: 'YMO_456', cpm: 1.5, creativeId: 'dd65c0a7536aff', currency: 'USD', diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index 4186c5da41a..58939ac0c66 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -7,6 +7,8 @@ const ENDPOINT = 'https://y.one.impact-ad.jp/h_bid'; const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/dac-video-prebid.min.js'; +const DEFAULT_VIDEO_SIZE = {w: 640, h: 360}; + describe('yieldoneBidAdapter', function() { const adapter = newBidder(spec); @@ -40,32 +42,7 @@ describe('yieldoneBidAdapter', function() { }); describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': 'yieldone', - 'params': { - placementId: '36891' - }, - 'adUnitCode': 'adunit-code1', - 'sizes': [[300, 250], [336, 280]], - 'bidId': '23beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - }, - { - 'bidder': 'yieldone', - 'params': { - placementId: '47919' - }, - 'adUnitCode': 'adunit-code2', - 'sizes': [[300, 250]], - 'bidId': '382091349b149f"', - 'bidderRequestId': '"1f9c98192de251"', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - } - ]; - - let bidderRequest = { + const bidderRequest = { refererInfo: { numIframes: 0, reachedTop: true, @@ -74,35 +51,353 @@ describe('yieldoneBidAdapter', function() { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); + describe('Basic', function () { + const bidRequests = [ + { + 'bidder': 'yieldone', + 'params': {placementId: '36891'}, + 'adUnitCode': 'adunit-code1', + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, + { + 'bidder': 'yieldone', + 'params': {placementId: '47919'}, + 'adUnitCode': 'adunit-code2', + 'bidId': '382091349b149f"', + 'bidderRequestId': '"1f9c98192de251"', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); - it('sends bid request to our endpoint via GET', function () { - expect(request[0].method).to.equal('GET'); - expect(request[1].method).to.equal('GET'); + it('sends bid request to our endpoint via GET', function () { + expect(request[0].method).to.equal('GET'); + expect(request[1].method).to.equal('GET'); + }); + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT); + expect(request[1].url).to.equal(ENDPOINT); + }); + it('adUnitCode should be sent as uc parameters on any requests', function () { + expect(request[0].data.uc).to.equal('adunit-code1'); + expect(request[1].data.uc).to.equal('adunit-code2'); + }); }); - it('attaches source and version to endpoint URL as query params', function () { - expect(request[0].url).to.equal(ENDPOINT); - expect(request[1].url).to.equal(ENDPOINT); + describe('Old Format', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + mediaType: 'banner', + sizes: [[300, 250], [336, 280]], + }, + { + params: {placementId: '1'}, + mediaType: 'banner', + sizes: [[336, 280]], + }, + { + // It doesn't actually exist. + params: {placementId: '2'}, + }, + { + params: {placementId: '3'}, + mediaType: 'video', + sizes: [[1280, 720], [1920, 1080]], + }, + { + params: {placementId: '4'}, + mediaType: 'video', + sizes: [[1920, 1080]], + }, + { + params: {placementId: '5'}, + mediaType: 'video', + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data.sz).to.equal('336x280'); + expect(request[2].data.sz).to.equal(''); + expect(request[3].data).to.not.have.property('sz'); + expect(request[4].data).to.not.have.property('sz'); + expect(request[5].data).to.not.have.property('sz'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + expect(request[0].data).to.not.have.property('w'); + expect(request[1].data).to.not.have.property('w'); + expect(request[2].data).to.not.have.property('w'); + expect(request[3].data.w).to.equal(1280); + expect(request[3].data.h).to.equal(720); + expect(request[4].data.w).to.equal(1920); + expect(request[4].data.h).to.equal(1080); + expect(request[5].data.w).to.equal(DEFAULT_VIDEO_SIZE.w); + expect(request[5].data.h).to.equal(DEFAULT_VIDEO_SIZE.h); + }); }); - it('parameter sz has more than one size on banner requests', function () { - expect(request[0].data.sz).to.equal('300x250,336x280'); - expect(request[1].data.sz).to.equal('300x250'); + describe('New Format', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + }, + }, + { + params: {placementId: '1'}, + mediaTypes: { + banner: { + sizes: [[336, 280]], + }, + }, + }, + { + // It doesn't actually exist. + params: {placementId: '2'}, + mediaTypes: { + banner: { + }, + }, + }, + { + params: {placementId: '3'}, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[1280, 720], [1920, 1080]], + }, + }, + }, + { + params: {placementId: '4'}, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [1920, 1080], + }, + }, + }, + { + params: {placementId: '5'}, + mediaTypes: { + video: { + context: 'outstream', + }, + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data.sz).to.equal('336x280'); + expect(request[2].data.sz).to.equal(''); + expect(request[3].data).to.not.have.property('sz'); + expect(request[4].data).to.not.have.property('sz'); + expect(request[5].data).to.not.have.property('sz'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + expect(request[0].data).to.not.have.property('w'); + expect(request[1].data).to.not.have.property('w'); + expect(request[2].data).to.not.have.property('w'); + expect(request[3].data.w).to.equal(1280); + expect(request[3].data.h).to.equal(720); + expect(request[4].data.w).to.equal(1920); + expect(request[4].data.h).to.equal(1080); + expect(request[5].data.w).to.equal(DEFAULT_VIDEO_SIZE.w); + expect(request[5].data.h).to.equal(DEFAULT_VIDEO_SIZE.h); + }); + }); + + describe('Multiple Format', function () { + const bidRequests = [ + { + // It will be treated as a banner. + params: { + placementId: '0', + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [1920, 1080], + }, + }, + }, + { + // It will be treated as a video. + params: { + placementId: '1', + playerParams: {}, + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [1920, 1080], + }, + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data).to.not.have.property('sz'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + expect(request[0].data).to.not.have.property('w'); + expect(request[1].data.w).to.equal(1920); + expect(request[1].data.h).to.equal(1080); + }); + }); + + describe('FLUX Format', function () { + const bidRequests = [ + { + // It will be treated as a banner. + params: { + placementId: '0', + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [[1, 1]], + }, + }, + }, + { + // It will be treated as a video. + params: { + placementId: '1', + playerParams: {}, + playerSize: [1920, 1080], + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [[1, 1]], + }, + }, + }, + { + // It will be treated as a video. + params: { + placementId: '2', + playerParams: {}, + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [[1, 1]], + }, + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data).to.not.have.property('sz'); + expect(request[2].data).to.not.have.property('sz'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + expect(request[0].data).to.not.have.property('w'); + expect(request[1].data.w).to.equal(1920); + expect(request[1].data.h).to.equal(1080); + expect(request[2].data.w).to.equal(DEFAULT_VIDEO_SIZE.w); + expect(request[2].data.h).to.equal(DEFAULT_VIDEO_SIZE.h); + }); }); - it('width and height should be set as separate parameters on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = {context: 'outstream'}; - const request = spec.buildRequests([bidRequest], bidderRequest); - expect(request[0].data.w).to.equal('300'); - expect(request[0].data.h).to.equal('250'); + describe('LiveRampID', function () { + it('dont send LiveRampID if undefined', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + }, + { + params: {placementId: '1'}, + userId: {}, + }, + { + params: {placementId: '2'}, + userId: undefined, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data).to.not.have.property('lr_env'); + expect(request[1].data).to.not.have.property('lr_env'); + expect(request[2].data).to.not.have.property('lr_env'); + }); + + it('should send LiveRampID if available', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + userId: {idl_env: 'idl_env_sample'}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.lr_env).to.equal('idl_env_sample'); + }); }); - it('adUnitCode should be sent as uc parameters on any requests', function () { - expect(request[0].data.uc).to.equal('adunit-code1'); - expect(request[1].data.uc).to.equal('adunit-code2'); + describe('IMID', function () { + it('dont send IMID if undefined', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + }, + { + params: {placementId: '1'}, + userId: {}, + }, + { + params: {placementId: '2'}, + userId: undefined, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data).to.not.have.property('imuid'); + expect(request[1].data).to.not.have.property('imuid'); + expect(request[2].data).to.not.have.property('imuid'); + }); + + it('should send IMID if available', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + userId: {imuid: 'imuid_sample'}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.imuid).to.equal('imuid_sample'); + }); }); describe('userid idl_env should be passed to querystring', function () { diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 9de6fa843bc..98459a36d45 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; import { config } from 'src/config.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, getStorage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; @@ -54,7 +54,7 @@ describe('Zeotap ID System', function() { it('when a stored Zeotap ID exists it is added to bids', function() { let store = getStorage(); expect(getStorageManagerSpy.calledOnce).to.be.true; - sinon.assert.calledWith(getStorageManagerSpy, 301, 'zeotapIdPlus'); + sinon.assert.calledWith(getStorageManagerSpy, {gvlid: 301, moduleName: 'zeotapIdPlus'}); }); }); diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..15a1155f378 --- /dev/null +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -0,0 +1,427 @@ +import zetaAnalyticsAdapter from 'modules/zeta_global_sspAnalyticsAdapter.js'; +import {config} from 'src/config'; +import CONSTANTS from 'src/constants.json'; +import {logError} from '../../../src/utils'; + +let utils = require('src/utils'); +let events = require('src/events'); + +const MOCK = { + STUB: { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + }, + AUCTION_END: { + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'timestamp': 1638441234544, + 'auctionEnd': 1638441234784, + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': '/19968336/header-bid-tag-0', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'zeta_global_ssp', + 'params': { + 'sid': 111, + 'tags': { + 'shortname': 'prebid_analytics_event_test_shortname', + 'position': 'test_position' + } + } + }, + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13232385 + } + } + ], + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83' + } + ], + 'adUnitCodes': [ + '/19968336/header-bid-tag-0' + ], + 'bidderRequests': [ + { + 'bidderCode': 'zeta_global_ssp', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'bidderRequestId': '1207cb49191887', + 'bids': [ + { + 'bidder': 'zeta_global_ssp', + 'params': { + 'sid': 111, + 'tags': { + 'shortname': 'prebid_analytics_event_test_shortname', + 'position': 'test_position' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '206be9a13236af', + 'bidderRequestId': '1207cb49191887', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1638441234544, + 'timeout': 400, + 'refererInfo': { + 'referer': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html' + ], + 'canonicalUrl': null + }, + 'start': 1638441234547 + }, + { + 'bidderCode': 'appnexus', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'bidderRequestId': '32b97f0a935422', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13232385 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '41badc0e164c758', + 'bidderRequestId': '32b97f0a935422', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1638441234544, + 'timeout': 400, + 'refererInfo': { + 'referer': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html' + ], + 'canonicalUrl': null + }, + 'start': 1638441234550 + } + ], + 'noBids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13232385 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '41badc0e164c758', + 'bidderRequestId': '32b97f0a935422', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'bidsReceived': [ + { + 'bidderCode': 'zeta_global_ssp', + 'width': 480, + 'height': 320, + 'statusMessage': 'Bid available', + 'adId': '5759bb3ef7be1e8', + 'requestId': '206be9a13236af', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 2.258302852806723, + 'currency': 'USD', + 'ad': 'test_ad', + 'ttl': 200, + 'creativeId': '456456456', + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'viaplay.fi' + ] + }, + 'originalCpm': 2.258302852806723, + 'originalCurrency': 'USD', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'responseTimestamp': 1638441234670, + 'requestTimestamp': 1638441234547, + 'bidder': 'zeta_global_ssp', + 'adUnitCode': '/19968336/header-bid-tag-0', + 'timeToRespond': 123, + 'pbLg': '2.00', + 'pbMg': '2.20', + 'pbHg': '2.25', + 'pbAg': '2.25', + 'pbDg': '2.25', + 'pbCg': '', + 'size': '480x320', + 'adserverTargeting': { + 'hb_bidder': 'zeta_global_ssp', + 'hb_adid': '5759bb3ef7be1e8', + 'hb_pb': '2.20', + 'hb_size': '480x320', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'viaplay.fi' + } + } + ], + 'winningBids': [], + 'timeout': 400 + }, + AD_RENDER_SUCCEEDED: { + 'doc': { + 'location': { + 'href': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'protocol': 'http:', + 'host': 'localhost:63342', + 'hostname': 'localhost', + 'port': '63342', + 'pathname': '/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'hash': '', + 'origin': 'http://test-zeta-ssp.net:63342', + 'ancestorOrigins': { + '0': 'http://test-zeta-ssp.net:63342' + } + } + }, + 'bid': { + 'bidderCode': 'zeta_global_ssp', + 'width': 480, + 'height': 320, + 'statusMessage': 'Bid available', + 'adId': '5759bb3ef7be1e8', + 'requestId': '206be9a13236af', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 2.258302852806723, + 'currency': 'USD', + 'ad': 'test_ad', + 'ttl': 200, + 'creativeId': '456456456', + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'viaplay.fi' + ] + }, + 'originalCpm': 2.258302852806723, + 'originalCurrency': 'USD', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'responseTimestamp': 1638441234670, + 'requestTimestamp': 1638441234547, + 'bidder': 'zeta_global_ssp', + 'adUnitCode': '/19968336/header-bid-tag-0', + 'timeToRespond': 123, + 'pbLg': '2.00', + 'pbMg': '2.20', + 'pbHg': '2.25', + 'pbAg': '2.25', + 'pbDg': '2.25', + 'pbCg': '', + 'size': '480x320', + 'adserverTargeting': { + 'hb_bidder': 'zeta_global_ssp', + 'hb_adid': '5759bb3ef7be1e8', + 'hb_pb': '2.20', + 'hb_size': '480x320', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'viaplay.fi' + }, + 'status': 'rendered', + 'params': [ + { + 'sid': 111, + 'tags': { + 'shortname': 'prebid_analytics_event_test_shortname', + 'position': 'test_position' + } + } + ] + }, + 'adId': '5759bb3ef7be1e8' + } +} + +describe('Zeta Global SSP Analytics Adapter', function() { + let sandbox; + let xhr; + let requests; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + requests = []; + xhr = sandbox.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + sandbox.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + sandbox.restore(); + config.resetConfig(); + }); + + it('should require publisherId', function () { + sandbox.stub(utils, 'logError'); + zetaAnalyticsAdapter.enableAnalytics({ + options: {} + }); + expect(utils.logError.called).to.equal(true); + }); + + describe('handle events', function() { + beforeEach(function() { + zetaAnalyticsAdapter.enableAnalytics({ + options: { + sid: 111 + } + }); + }); + + afterEach(function () { + zetaAnalyticsAdapter.disableAnalytics(); + }); + + it('events are sent', function() { + this.timeout(5000); + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.NO_BID, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_WON, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BIDDER_DONE, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BIDDER_ERROR, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.SET_TARGETING, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BEFORE_BIDDER_HTTP, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.REQUEST_BIDS, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.ADD_AD_UNITS, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.AD_RENDER_FAILED, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED); + events.emit(CONSTANTS.EVENTS.TCF2_ENFORCEMENT, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.STALE_RENDER, MOCK.STUB); + + expect(requests.length).to.equal(2); + expect(JSON.parse(requests[0].requestBody)).to.deep.equal(MOCK.AUCTION_END); + expect(JSON.parse(requests[1].requestBody)).to.deep.equal(MOCK.AD_RENDER_SUCCEEDED); + }); + }); +}); diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index dcb0183fb4c..20113a63994 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -1,4 +1,5 @@ import {spec} from '../../../modules/zeta_global_sspBidAdapter.js' +import {BANNER, VIDEO} from '../../../src/mediaTypes'; describe('Zeta Ssp Bid Adapter', function () { const eids = [ @@ -99,6 +100,13 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.user.ext.eids).to.eql(eids); }); + it('Test contains ua and language', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + expect(payload.device.ua).to.not.be.empty; + expect(payload.device.language).to.not.be.empty; + }); + it('Test page and domain in site', function () { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); const payload = JSON.parse(request.data); @@ -143,7 +151,22 @@ describe('Zeta Ssp Bid Adapter', function () { 'https://example2.com' ], h: 150, - w: 200 + w: 200, + ext: { + bidtype: 'video' + } + }, + { + id: 'auctionId3', + impid: 'impId3', + price: 0.2, + adm: '', + crid: 'creativeId3', + adomain: [ + 'https://example3.com' + ], + h: 400, + w: 300 } ] } @@ -159,6 +182,8 @@ describe('Zeta Ssp Bid Adapter', function () { const receivedBid1 = response.body.seatbid[0].bid[0]; expect(bid1).to.not.be.empty; expect(bid1.ad).to.equal(receivedBid1.adm); + expect(bid1.vastXml).to.be.undefined; + expect(bid1.mediaType).to.equal(BANNER); expect(bid1.cpm).to.equal(receivedBid1.price); expect(bid1.height).to.equal(receivedBid1.h); expect(bid1.width).to.equal(receivedBid1.w); @@ -169,11 +194,25 @@ describe('Zeta Ssp Bid Adapter', function () { const receivedBid2 = response.body.seatbid[0].bid[1]; expect(bid2).to.not.be.empty; expect(bid2.ad).to.equal(receivedBid2.adm); + expect(bid2.vastXml).to.equal(receivedBid2.adm); + expect(bid2.mediaType).to.equal(VIDEO); expect(bid2.cpm).to.equal(receivedBid2.price); expect(bid2.height).to.equal(receivedBid2.h); expect(bid2.width).to.equal(receivedBid2.w); expect(bid2.requestId).to.equal(receivedBid2.impid); expect(bid2.meta.advertiserDomains).to.equal(receivedBid2.adomain); + + const bid3 = bidResponse[2]; + const receivedBid3 = response.body.seatbid[0].bid[2]; + expect(bid3).to.not.be.empty; + expect(bid3.ad).to.equal(receivedBid3.adm); + expect(bid3.vastXml).to.equal(receivedBid3.adm); + expect(bid3.mediaType).to.equal(VIDEO); + expect(bid3.cpm).to.equal(receivedBid3.price); + expect(bid3.height).to.equal(receivedBid3.h); + expect(bid3.width).to.equal(receivedBid3.w); + expect(bid3.requestId).to.equal(receivedBid3.impid); + expect(bid3.meta.advertiserDomains).to.equal(receivedBid3.adomain); }); it('Different cases for user syncs', function () { diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 0ffef30965b..66e11b9a472 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -1,10 +1,19 @@ import { expect } from 'chai'; -import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid, getAssetMessage, getAllAssetsMessage } from 'src/native.js'; +import { + fireNativeTrackers, + getNativeTargeting, + nativeBidIsValid, + getAssetMessage, + getAllAssetsMessage, + decorateAdUnitsWithNativeParams +} from 'src/native.js'; import CONSTANTS from 'src/constants.json'; +import {stubAuctionIndex} from '../helpers/indexStub.js'; const utils = require('src/utils'); const bid = { adId: '123', + transactionId: 'au', native: { title: 'Native Creative', body: 'Cool description great stuff', @@ -32,6 +41,7 @@ const bid = { }; const bidWithUndefinedFields = { + transactionId: 'au', native: { title: 'Native Creative', body: undefined, @@ -52,6 +62,10 @@ describe('native.js', function () { let triggerPixelStub; let insertHtmlIntoIframeStub; + function deps(adUnit) { + return { index: stubAuctionIndex({ adUnits: [adUnit] }) }; + } + beforeEach(function () { triggerPixelStub = sinon.stub(utils, 'triggerPixel'); insertHtmlIntoIframeStub = sinon.stub(utils, 'insertHtmlIntoIframe'); @@ -71,7 +85,8 @@ describe('native.js', function () { }); it('sends placeholders for configured assets', function () { - const bidRequest = { + const adUnit = { + transactionId: 'au', nativeParams: { body: { sendId: true }, clickUrl: { sendId: true }, @@ -85,7 +100,7 @@ describe('native.js', function () { } } }; - const targeting = getNativeTargeting(bid, bidRequest); + const targeting = getNativeTargeting(bid, deps(adUnit)); expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal('hb_native_body:123'); @@ -95,7 +110,8 @@ describe('native.js', function () { }); it('should only include native targeting keys with values', function () { - const bidRequest = { + const adUnit = { + transactionId: 'au', nativeParams: { body: { sendId: true }, clickUrl: { sendId: true }, @@ -110,7 +126,7 @@ describe('native.js', function () { } }; - const targeting = getNativeTargeting(bidWithUndefinedFields, bidRequest); + const targeting = getNativeTargeting(bidWithUndefinedFields, deps(adUnit)); expect(Object.keys(targeting)).to.deep.equal([ CONSTANTS.NATIVE_KEYS.title, @@ -121,7 +137,8 @@ describe('native.js', function () { }); it('should only include targeting that has sendTargetingKeys set to true', function () { - const bidRequest = { + const adUnit = { + transactionId: 'au', nativeParams: { image: { required: true, @@ -136,7 +153,7 @@ describe('native.js', function () { } }; - const targeting = getNativeTargeting(bid, bidRequest); + const targeting = getNativeTargeting(bid, deps(adUnit)); expect(Object.keys(targeting)).to.deep.equal([ CONSTANTS.NATIVE_KEYS.title @@ -144,7 +161,8 @@ describe('native.js', function () { }); it('should only include targeting if sendTargetingKeys not set to false', function () { - const bidRequest = { + const adUnit = { + transactionId: 'au', nativeParams: { image: { required: true, @@ -181,7 +199,7 @@ describe('native.js', function () { } }; - const targeting = getNativeTargeting(bid, bidRequest); + const targeting = getNativeTargeting(bid, deps(adUnit)); expect(Object.keys(targeting)).to.deep.equal([ CONSTANTS.NATIVE_KEYS.title, @@ -193,7 +211,8 @@ describe('native.js', function () { }); it('should copy over rendererUrl to bid object and include it in targeting', function () { - const bidRequest = { + const adUnit = { + transactionId: 'au', nativeParams: { image: { required: true, @@ -209,7 +228,7 @@ describe('native.js', function () { } }; - const targeting = getNativeTargeting(bid, bidRequest); + const targeting = getNativeTargeting(bid, deps(adUnit)); expect(Object.keys(targeting)).to.deep.equal([ CONSTANTS.NATIVE_KEYS.title, @@ -227,7 +246,8 @@ describe('native.js', function () { }); it('should copy over adTemplate to bid object and include it in targeting', function () { - const bidRequest = { + const adUnit = { + transactionId: 'au', nativeParams: { image: { required: true, @@ -241,7 +261,7 @@ describe('native.js', function () { } }; - const targeting = getNativeTargeting(bid, bidRequest); + const targeting = getNativeTargeting(bid, deps(adUnit)); expect(Object.keys(targeting)).to.deep.equal([ CONSTANTS.NATIVE_KEYS.title, @@ -374,35 +394,33 @@ describe('native.js', function () { }); describe('validate native', function () { - let bidReq = [{ - bids: [{ - bidderCode: 'test_bidder', - bidId: 'test_bid_id', - mediaTypes: { - native: { - title: { - required: true, - }, - body: { - required: true, - }, - image: { - required: true, - sizes: [150, 50], - aspect_ratios: [150, 50] - }, - icon: { - required: true, - sizes: [50, 50] - }, - } + const adUnit = { + transactionId: 'test_adunit', + mediaTypes: { + native: { + title: { + required: true, + }, + body: { + required: true, + }, + image: { + required: true, + sizes: [150, 50], + aspect_ratios: [150, 50] + }, + icon: { + required: true, + sizes: [50, 50] + }, } - }] - }]; + } + } let validBid = { adId: 'abc123', requestId: 'test_bid_id', + transactionId: 'test_adunit', adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { @@ -428,6 +446,7 @@ describe('validate native', function () { let noIconDimBid = { adId: 'abc234', requestId: 'test_bid_id', + transactionId: 'test_adunit', adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { @@ -449,6 +468,7 @@ describe('validate native', function () { let noImgDimBid = { adId: 'abc345', requestId: 'test_bid_id', + transactionId: 'test_adunit', adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { @@ -472,11 +492,13 @@ describe('validate native', function () { afterEach(function () {}); it('should accept bid if no image sizes are defined', function () { - let result = nativeBidIsValid(validBid, bidReq); + decorateAdUnitsWithNativeParams([adUnit]); + const index = stubAuctionIndex({adUnits: [adUnit]}) + let result = nativeBidIsValid(validBid, {index}); expect(result).to.be.true; - result = nativeBidIsValid(noIconDimBid, bidReq); + result = nativeBidIsValid(noIconDimBid, {index}); expect(result).to.be.true; - result = nativeBidIsValid(noImgDimBid, bidReq); + result = nativeBidIsValid(noImgDimBid, {index}); expect(result).to.be.true; }); }); diff --git a/test/spec/sizeMapping_spec.js b/test/spec/sizeMapping_spec.js index a3c39a52441..c4efbddad6d 100644 --- a/test/spec/sizeMapping_spec.js +++ b/test/spec/sizeMapping_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { resolveStatus, setSizeConfig, sizeSupported } from 'src/sizeMapping.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {includes} from 'src/polyfill.js' let utils = require('src/utils'); let deepClone = utils.deepClone; diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index a01c6ad3c2e..88beaa88a67 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1,5 +1,11 @@ import { expect } from 'chai'; -import adapterManager, { allS2SBidders, clientTestAdapters, gdprDataHandler, coppaDataHandler } from 'src/adapterManager.js'; +import adapterManager, { + gdprDataHandler, + coppaDataHandler, + _partitionBidders, + PARTITIONS, + getS2SBidderSet, _filterBidsForAdUnit +} from 'src/adapterManager.js'; import { getAdUnits, getServerTestingConfig, @@ -11,9 +17,9 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; import { setSizeConfig } from 'src/sizeMapping.js'; -import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import {find, includes} from 'src/polyfill.js'; import s2sTesting from 'modules/s2sTesting.js'; +import {hook} from '../../../../src/hook.js'; var events = require('../../../../src/events'); const CONFIG = { @@ -87,9 +93,14 @@ describe('adapterManager tests', function () { config.setConfig({s2sConfig: { enabled: false }}); }); + afterEach(() => { + s2sTesting.clientTestBidders.clear(); + }); + describe('callBids', function () { before(function () { config.setConfig({s2sConfig: { enabled: false }}); + hook.ready(); }); beforeEach(function () { @@ -162,7 +173,7 @@ describe('adapterManager tests', function () { 'bidderCode': 'appnexus', 'auctionId': '1863e370099523', 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', + 'uniquePbsTid': '34566b569352ef2', 'bids': [ { 'bidder': 'appnexus', @@ -479,7 +490,7 @@ describe('adapterManager tests', function () { 'bidderCode': 'appnexus', 'auctionId': '1863e370099523', 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', + 'uniquePbsTid': '34566b569352ef2', 'timeout': 1000, 'src': 's2s', 'adUnitsS2SCopy': [ @@ -700,15 +711,11 @@ describe('adapterManager tests', function () { prebidServerAdapterMock.callBids.reset(); }); - afterEach(function () { - allS2SBidders.length = 0; - }); - const bidRequests = [{ 'bidderCode': 'appnexus', 'auctionId': '1863e370099523', 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', + 'uniquePbsTid': '34566b569352ef2', 'timeout': 1000, 'src': 's2s', 'adUnitsS2SCopy': [ @@ -844,7 +851,7 @@ describe('adapterManager tests', function () { 'bidderCode': 'pubmatic', 'auctionId': '1863e370099523', 'bidderRequestId': '2946b569352ef2', - 'tid': '2342342342lfi23', + 'uniquePbsTid': '2342342342lfi23', 'timeout': 1000, 'src': 's2s', 'adUnitsS2SCopy': [ @@ -1041,6 +1048,21 @@ describe('adapterManager tests', function () { sinon.assert.calledTwice(prebidServerAdapterMock.callBids); }); + it('should have one tid for ALL s2s bidRequests', function () { + let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'pubmatic'], bid.bidder)); + return adUnit; + }) + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + const firstBid = prebidServerAdapterMock.callBids.firstCall.args[0]; + const secondBid = prebidServerAdapterMock.callBids.secondCall.args[0]; + + // TIDS should be the same + expect(firstBid.tid).to.equal(secondBid.tid); + }); + it('should fire for simultaneous s2s and client requests', function () { adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { @@ -1289,9 +1311,6 @@ describe('adapterManager tests', function () { } beforeEach(function () { - allS2SBidders.length = 0; - clientTestAdapters.length = 0 - adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; @@ -1646,13 +1665,32 @@ describe('adapterManager tests', function () { describe('makeBidRequests', function () { let adUnits; beforeEach(function () { - allS2SBidders.length = 0 adUnits = utils.deepClone(getAdUnits()).map(adUnit => { adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'rubicon'], bid.bidder)); return adUnit; }) }); + it('should add nativeParams to adUnits after BEFORE_REQUEST_BIDS', () => { + function beforeReqBids(adUnits) { + adUnits.forEach(adUnit => { + adUnit.mediaTypes.native = { + type: 'image', + } + }) + } + events.on(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); + adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + events.off(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); + expect(adUnits.map((u) => u.nativeParams).some(i => i == null)).to.be.false; + }); + it('should make separate bidder request objects for each bidder', () => { adUnits = [utils.deepClone(getAdUnits()[0])]; @@ -1700,8 +1738,6 @@ describe('adapterManager tests', function () { let sandbox; beforeEach(function () { sandbox = sinon.sandbox.create(); - allS2SBidders.length = 0; - clientTestAdapters.length = 0; // always have matchMedia return true for us sandbox.stub(utils.getWindowTop(), 'matchMedia').callsFake(() => ({matches: true})); }); @@ -1877,7 +1913,7 @@ describe('adapterManager tests', function () { ['visitor-uk', 'desktop'] ); - // only one adUnit and one bid from that adUnit should make it through the applied labels above + // only one adUnit and one bid from that adUnit should make it through the applied labels above expect(bidRequests.length).to.equal(1); expect(bidRequests[0].bidderCode).to.equal('rubicon'); expect(bidRequests[0].bids.length).to.equal(1); @@ -1961,7 +1997,6 @@ describe('adapterManager tests', function () { describe('s2sTesting - testServerOnly', () => { beforeEach(() => { config.setConfig({ s2sConfig: getServerTestingConfig(CONFIG) }); - allS2SBidders.length = 0 s2sTesting.bidSource = {}; }); @@ -2092,7 +2127,6 @@ describe('adapterManager tests', function () { afterEach(() => { config.resetConfig() - allS2SBidders.length = 0; s2sTesting.bidSource = {}; }); @@ -2272,4 +2306,102 @@ describe('adapterManager tests', function () { ); }); }); + + describe('getS2SBidderSet', () => { + it('should always return the "null" bidder', () => { + expect([...getS2SBidderSet({bidders: []})]).to.eql([null]); + }); + + it('should not consider disabled s2s adapters', () => { + const actual = getS2SBidderSet([{enabled: false, bidders: ['A', 'B']}, {enabled: true, bidders: ['C']}]); + expect([...actual]).to.include.members(['C']); + expect([...actual]).not.to.include.members(['A', 'B']); + }); + + it('should accept both single config objects and an array of them', () => { + const conf = {enabled: true, bidders: ['A', 'B']}; + expect(getS2SBidderSet(conf)).to.eql(getS2SBidderSet([conf])); + }); + }); + + describe('separation of client and server bidders', () => { + let s2sBidders, getS2SBidders; + beforeEach(() => { + s2sBidders = null; + getS2SBidders = sinon.stub(); + getS2SBidders.callsFake(() => s2sBidders); + }) + + describe('partitionBidders', () => { + let adUnits; + + beforeEach(() => { + adUnits = [{ + bids: [{ + bidder: 'A' + }, { + bidder: 'B' + }] + }, { + bids: [{ + bidder: 'A', + }, { + bidder: 'C' + }] + }]; + }); + + function partition(adUnits, s2sConfigs) { + return _partitionBidders(adUnits, s2sConfigs, {getS2SBidders}) + } + + Object.entries({ + 'all client': { + s2s: [], + expected: { + [PARTITIONS.CLIENT]: ['A', 'B', 'C'], + [PARTITIONS.SERVER]: [] + } + }, + 'all server': { + s2s: ['A', 'B', 'C'], + expected: { + [PARTITIONS.CLIENT]: [], + [PARTITIONS.SERVER]: ['A', 'B', 'C'] + } + }, + 'mixed': { + s2s: ['B', 'C'], + expected: { + [PARTITIONS.CLIENT]: ['A'], + [PARTITIONS.SERVER]: ['B', 'C'] + } + } + }).forEach(([test, {s2s, expected}]) => { + it(`should partition ${test} requests`, () => { + s2sBidders = new Set(s2s); + const s2sConfig = {}; + expect(partition(adUnits, s2sConfig)).to.eql(expected); + sinon.assert.calledWith(getS2SBidders, sinon.match.same(s2sConfig)); + }); + }); + }); + + describe('filterBidsForAdUnit', () => { + function filterBids(bids, s2sConfig) { + return _filterBidsForAdUnit(bids, s2sConfig, {getS2SBidders}); + } + it('should not filter any bids when s2sConfig == null', () => { + const bids = ['untouched', 'data']; + expect(filterBids(bids)).to.eql(bids); + }); + + it('should remove bids that have bidder not present in s2sConfig', () => { + s2sBidders = new Set('A', 'B'); + const s2sConfig = {}; + expect(filterBids(['A', 'C', 'D'].map((code) => ({bidder: code})), s2sConfig)).to.eql([{bidder: 'A'}]); + sinon.assert.calledWith(getS2SBidders, sinon.match.same(s2sConfig)); + }) + }); + }); }); diff --git a/test/spec/unit/core/auctionIndex_spec.js b/test/spec/unit/core/auctionIndex_spec.js new file mode 100644 index 00000000000..f00e2cd281f --- /dev/null +++ b/test/spec/unit/core/auctionIndex_spec.js @@ -0,0 +1,129 @@ +import {AuctionIndex} from '../../../../src/auctionIndex.js'; + +describe('auction index', () => { + let index, auctions; + + function mockAuction(id, adUnits, bidderRequests) { + return { + getAuctionId() { return id }, + getAdUnits() { return adUnits; }, + getBidRequests() { return bidderRequests; } + } + } + + beforeEach(() => { + auctions = []; + index = new AuctionIndex(() => auctions); + }) + + describe('getAuction', () => { + beforeEach(() => { + auctions = [mockAuction('a1'), mockAuction('a2')]; + }); + + it('should find auctions by auctionId', () => { + expect(index.getAuction({auctionId: 'a1'})).to.equal(auctions[0]); + }); + + it('should return undef if auction is missing', () => { + expect(index.getAuction({auctionId: 'missing'})).to.be.undefined; + }); + + it('should return undef if no auctionId is provided', () => { + expect(index.getAuction({})).to.be.undefined; + }); + }); + + describe('getAdUnit', () => { + let adUnits; + + beforeEach(() => { + adUnits = [{transactionId: 'au1'}, {transactionId: 'au2'}]; + auctions = [ + mockAuction('a1', [adUnits[0], {}]), + mockAuction('a2', [adUnits[1]]) + ]; + }); + + it('should find adUnits by transactionId', () => { + expect(index.getAdUnit({transactionId: 'au2'})).to.equal(adUnits[1]); + }); + + it('should return undefined if adunit is missing', () => { + expect(index.getAdUnit({transactionId: 'missing'})).to.be.undefined; + }); + + it('should return undefined if no transactionId is provided', () => { + expect(index.getAdUnit({})).to.be.undefined; + }); + }); + + describe('getBidRequest', () => { + let bidRequests; + beforeEach(() => { + bidRequests = [{bidId: 'b1'}, {bidId: 'b2'}]; + auctions = [ + mockAuction('a1', [], [{bids: [bidRequests[0], {}]}]), + mockAuction('a2', [], [{bids: [bidRequests[1]]}]) + ] + }); + + it('should find bidRequests by requestId', () => { + expect(index.getBidRequest({requestId: 'b2'})).to.equal(bidRequests[1]); + }); + + it('should return undef if bidRequest is missing', () => { + expect(index.getBidRequest({requestId: 'missing'})).to.be.undefined; + }); + + it('should return undef if no requestId is provided', () => { + expect(index.getBidRequest({})).to.be.undefined; + }); + }); + + describe('getMediaTypes', () => { + let bidderRequests, mediaTypes, adUnits; + + beforeEach(() => { + mediaTypes = [{mockMT: '1'}, {mockMT: '2'}, {mockMT: '3'}, {mockMT: '4'}] + adUnits = [ + {transactionId: 'au1', mediaTypes: mediaTypes[0]}, + {transactionId: 'au2', mediaTypes: mediaTypes[1]} + ] + bidderRequests = [ + {bidderRequestId: 'ber1', bids: [{bidId: 'b1', mediaTypes: mediaTypes[2], transactionId: 'au1'}, {}]}, + {bidderRequestId: 'ber2', bids: [{bidId: 'b2', mediaTypes: mediaTypes[3], transactionId: 'au2'}]} + ] + auctions = [ + mockAuction('a1', [adUnits[0]], [bidderRequests[0], {}]), + mockAuction('a2', [adUnits[1]], [bidderRequests[1]]) + ] + }); + + it('should find mediaTypes by transactionId', () => { + expect(index.getMediaTypes({transactionId: 'au2'})).to.equal(mediaTypes[1]); + }); + + it('should find mediaTypes by requestId', () => { + expect(index.getMediaTypes({requestId: 'b1'})).to.equal(mediaTypes[2]); + }); + + it('should give precedence to request.mediaTypes over adUnit.mediaTypes', () => { + expect(index.getMediaTypes({requestId: 'b2', transactionId: 'au2'})).to.equal(mediaTypes[3]); + }); + + it('should return undef if requestId and transactionId do not match', () => { + expect(index.getMediaTypes({requestId: 'b1', transactionId: 'au2'})).to.be.undefined; + }); + + it('should return undef if no params are provided', () => { + expect(index.getMediaTypes({})).to.be.undefined; + }); + + ['requestId', 'transactionId'].forEach(param => { + it(`should return undef if ${param} is missing`, () => { + expect(index.getMediaTypes({[param]: 'missing'})).to.be.undefined; + }); + }) + }); +}); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 4dc79deaf85..067f9abe424 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -7,7 +7,10 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; import CONSTANTS from 'src/constants.json'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; +import {hook} from '../../../../src/hook.js'; +import {auctionManager} from '../../../../src/auctionManager.js'; +import {stubAuctionIndex} from '../../../helpers/indexStub.js'; const CODE = 'sampleBidder'; const MOCK_BIDS_REQUEST = { @@ -43,6 +46,10 @@ describe('bidders created by newBidder', function () { let addBidResponseStub; let doneStub; + before(() => { + hook.ready(); + }); + beforeEach(function () { spec = { code: CODE, @@ -59,17 +66,22 @@ describe('bidders created by newBidder', function () { describe('when the ajax response is irrelevant', function () { let ajaxStub; let getConfigSpy; + let aliasRegistryStub, aliasRegistry; beforeEach(function () { ajaxStub = sinon.stub(ajax, 'ajax'); addBidResponseStub.reset(); getConfigSpy = sinon.spy(config, 'getConfig'); doneStub.reset(); + aliasRegistry = {}; + aliasRegistryStub = sinon.stub(adapterManager, 'aliasRegistry'); + aliasRegistryStub.get(() => aliasRegistry); }); afterEach(function () { ajaxStub.restore(); getConfigSpy.restore(); + aliasRegistryStub.restore(); }); it('should let registerSyncs run with invalid alias and aliasSync enabled', function () { @@ -116,6 +128,7 @@ describe('bidders created by newBidder', function () { }); spec.code = 'aliasBidder'; const bidder = newBidder(spec); + aliasRegistry = {[spec.code]: CODE}; bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(false); }); @@ -548,6 +561,30 @@ describe('bidders created by newBidder', function () { expect(logErrorSpy.calledOnce).to.equal(true); }); + + it('should require requestId from interpretResponse', () => { + const bidder = newBidder(spec); + const bid = { + 'ad': 'creative', + 'cpm': '1.99', + 'creativeId': 'some-id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); + spec.interpretResponse.returns(bid); + + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(addBidResponseStub.called).to.be.false; + }); }); describe('when the ajax call fails', function () { @@ -783,7 +820,7 @@ describe('registerBidder', function () { describe('validate bid response: ', function () { let spec; - let bidder; + let indexStub, adUnits, bidderRequests; let addBidResponseStub; let doneStub; let ajaxStub; @@ -824,25 +861,34 @@ describe('validate bid response: ', function () { callbacks.success('response body', { getResponseHeader: fakeResponse }); }); logErrorSpy = sinon.spy(utils, 'logError'); + indexStub = sinon.stub(auctionManager, 'index'); + adUnits = []; + bidderRequests = []; + indexStub.get(() => stubAuctionIndex({adUnits: adUnits, bidderRequests: bidderRequests})) }); afterEach(function () { ajaxStub.restore(); logErrorSpy.restore(); + indexStub.restore; }); it('should add native bids that do have required assets', function () { + adUnits = [{ + transactionId: 'au', + nativeParams: { + title: {'required': true}, + } + }] let bidRequest = { bids: [{ bidId: '1', auctionId: 'first-bid-id', adUnitCode: 'mock/placement', + transactionId: 'au', params: { param: 5 }, - nativeParams: { - title: {'required': true}, - }, mediaType: 'native', }] }; @@ -869,21 +915,24 @@ describe('validate bid response: ', function () { }); it('should not add native bids that do not have required assets', function () { + adUnits = [{ + transactionId: 'au', + nativeParams: { + title: {'required': true}, + }, + }]; let bidRequest = { bids: [{ bidId: '1', auctionId: 'first-bid-id', adUnitCode: 'mock/placement', + transactionId: 'au', params: { param: 5 }, - nativeParams: { - title: {'required': true}, - }, mediaType: 'native', }] }; - let bids1 = Object.assign({}, bids[0], { @@ -905,17 +954,21 @@ describe('validate bid response: ', function () { }); it('should add bid when renderer is present on outstream bids', function () { + adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'outstream'} + } + }] let bidRequest = { bids: [{ bidId: '1', auctionId: 'first-bid-id', + transactionId: 'au', adUnitCode: 'mock/placement', params: { param: 5 }, - mediaTypes: { - video: {context: 'outstream'} - } }] }; @@ -951,7 +1004,7 @@ describe('validate bid response: ', function () { sizes: [[300, 250]], }] }; - + bidderRequests = [bidRequest]; let bids1 = Object.assign({}, bids[0], { diff --git a/test/spec/unit/core/bidderSettings_spec.js b/test/spec/unit/core/bidderSettings_spec.js new file mode 100644 index 00000000000..ece18040d1e --- /dev/null +++ b/test/spec/unit/core/bidderSettings_spec.js @@ -0,0 +1,123 @@ +import {bidderSettings, ScopedSettings} from '../../../../src/bidderSettings.js'; +import {expect} from 'chai'; +import * as prebidGlobal from '../../../../src/prebidGlobal'; +import sinon from 'sinon'; + +describe('ScopedSettings', () => { + let data; + let settings; + + beforeEach(() => { + settings = new ScopedSettings(() => data, 'fallback'); + }); + + describe('get', () => { + it('should retrieve setting from scope', () => { + data = { + scope: {key: 'value'} + }; + expect(settings.get('scope', 'key')).to.equal('value'); + }); + + it('should fallback to fallback scope', () => { + data = { + fallback: { + key: 'value' + } + }; + expect(settings.get('scope', 'key')).to.equal('value'); + }); + + it('should retrieve from default scope if scope is null', () => { + data = { + fallback: { + key: 'value' + } + }; + + expect(settings.get(null, 'key')).to.equal('value'); + }); + + it('should not fall back if own setting has a falsy value', () => { + data = { + scope: { + key: false, + }, + fallback: { + key: true + } + } + expect(settings.get('scope', 'key')).to.equal(false); + }) + }); + + describe('getOwn', () => { + it('should not fall back to default scope', () => { + data = { + fallback: { + key: 'value' + } + }; + expect(settings.getOwn('missing', 'key')).to.be.undefined; + }); + + it('should use default if scope is null', () => { + data = { + fallback: { + key: 'value' + } + }; + expect(settings.getOwn(null, 'key')).to.equal('value'); + }); + }); + + describe('getScopes', () => { + it('should return all top-level keys except the default scope', () => { + data = { + fallback: {}, + scope1: {}, + scope2: {}, + }; + expect(settings.getScopes()).to.have.members(['scope1', 'scope2']); + }); + }); + + describe('settingsFor', () => { + it('should merge with default scope', () => { + data = { + fallback: { + dkey: 'value' + }, + scope: { + skey: 'value' + } + } + expect(settings.settingsFor('scope')).to.eql({ + dkey: 'value', + skey: 'value' + }) + }) + }); +}); + +describe('bidderSettings', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + bidderSettings: { + scope: { + key: 'value' + } + } + }); + }) + + afterEach(() => { + sandbox.restore(); + }) + + it('should fetch data from getGlobal().bidderSettings', () => { + expect(bidderSettings.get('scope', 'key')).to.equal('value'); + }) +}); diff --git a/test/spec/unit/core/consentHandler_spec.js b/test/spec/unit/core/consentHandler_spec.js new file mode 100644 index 00000000000..082ff34f90c --- /dev/null +++ b/test/spec/unit/core/consentHandler_spec.js @@ -0,0 +1,59 @@ +import {ConsentHandler} from '../../../../src/consentHandler.js'; + +describe('Consent data handler', () => { + let handler; + beforeEach(() => { + handler = new ConsentHandler(); + }) + + it('should be disabled, return null data on init', () => { + expect(handler.enabled).to.be.false; + expect(handler.getConsentData()).to.equal(null); + }) + + it('should resolve promise to null when disabled', () => { + return handler.promise.then((data) => { + expect(data).to.equal(null); + }); + }); + + it('should return data after setConsentData', () => { + const data = {consent: 'string'}; + handler.enable(); + handler.setConsentData(data); + expect(handler.getConsentData()).to.equal(data); + }); + + it('should resolve .promise to data after setConsentData', (done) => { + let actual = null; + const data = {consent: 'string'}; + handler.enable(); + handler.promise.then((d) => actual = d); + setTimeout(() => { + expect(actual).to.equal(null); + handler.setConsentData(data); + setTimeout(() => { + expect(actual).to.equal(data); + done(); + }) + }) + }); + + it('should resolve .promise to new data if setConsentData is called a second time', (done) => { + let actual = null; + const d1 = {data: '1'}; + const d2 = {data: '2'}; + handler.enable(); + handler.promise.then((d) => actual = d); + handler.setConsentData(d1); + setTimeout(() => { + expect(actual).to.equal(d1); + handler.setConsentData(d2); + handler.promise.then((d) => actual = d); + setTimeout(() => { + expect(actual).to.equal(d2); + done(); + }) + }) + }); +}) diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index 5bb766217f5..74a3b3b023f 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -1,8 +1,19 @@ -import { resetData, getCoreStorageManager, storageCallbacks, getStorageManager } from 'src/storageManager.js'; +import { + resetData, + getCoreStorageManager, + storageCallbacks, + getStorageManager, + newStorageManager +} from 'src/storageManager.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; +import {hook} from '../../../../src/hook.js'; describe('storage manager', function() { + before(() => { + hook.ready(); + }); + beforeEach(function() { resetData(); }); @@ -95,4 +106,62 @@ describe('storage manager', function() { expect(localStorage.getItem('unrelated')).to.be.eq('dummy'); }); }); + + describe('when bidderSettings.allowStorage is defined', () => { + const DENIED_BIDDER = 'denied-bidder'; + const DENY_KEY = 'storageAllowed'; + + const COOKIE = 'test-cookie'; + const LS_KEY = 'test-localstorage'; + + function mockBidderSettings() { + return { + get(bidder, key) { + if (bidder === DENIED_BIDDER && key === DENY_KEY) { + return false; + } else { + return undefined; + } + } + } + } + + Object.entries({ + disallowed: [DENIED_BIDDER, false], + allowed: ['allowed-bidder', true] + }).forEach(([test, [bidderCode, shouldWork]]) => { + describe(`for ${test} bidders`, () => { + let mgr; + + beforeEach(() => { + mgr = newStorageManager({bidderCode: bidderCode}, {bidderSettings: mockBidderSettings()}); + }) + + afterEach(() => { + mgr.setCookie(COOKIE, 'delete', new Date().toUTCString()); + mgr.removeDataFromLocalStorage(LS_KEY); + }) + + const testDesc = (desc) => `should ${shouldWork ? '' : 'not'} ${desc}`; + + it(testDesc('allow cookies'), () => { + mgr.setCookie(COOKIE, 'value'); + expect(mgr.getCookie(COOKIE)).to.equal(shouldWork ? 'value' : null); + }); + + it(testDesc('allow localStorage'), () => { + mgr.setDataInLocalStorage(LS_KEY, 'value'); + expect(mgr.getDataFromLocalStorage(LS_KEY)).to.equal(shouldWork ? 'value' : null); + }); + + it(testDesc('report localStorage as available'), () => { + expect(mgr.hasLocalStorage()).to.equal(shouldWork); + }); + + it(testDesc('report cookies as available'), () => { + expect(mgr.cookiesAreEnabled()).to.equal(shouldWork); + }); + }); + }); + }) }); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 27bb456905d..53aa3b90fa8 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -5,6 +5,7 @@ import { createBidReceived } from 'test/fixtures/fixtures.js'; 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'; const bid1 = { 'bidderCode': 'rubicon', @@ -467,6 +468,50 @@ describe('targeting tests', function () { }); }); + describe('targetingControls.allowZeroCpmBids', function () { + let bid4; + let bidderSettingsStorage; + + before(function() { + bidderSettingsStorage = $$PREBID_GLOBAL$$.bidderSettings; + }); + + beforeEach(function () { + bid4 = utils.deepClone(bid1); + bid4.adserverTargeting = { + hb_pb: '0.0', + hb_adid: '567891011', + hb_bidder: 'appnexus', + }; + bid4.bidder = bid4.bidderCode = 'appnexus'; + bid4.cpm = 0; + bidsReceived = [bid4]; + }); + + after(function() { + bidsReceived = [bid1, bid2, bid3]; + $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsStorage; + }) + + it('targeting should not include a 0 cpm by default', function() { + bid4.adserverTargeting = {}; + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.deep.equal({}); + }); + + it('targeting should allow a 0 cpm with targetingControls.allowZeroCpmBids set to true', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + standard: { + allowZeroCpmBids: true + } + }; + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('hb_pb', 'hb_bidder', 'hb_adid', 'hb_bidder_appnexus', 'hb_adid_appnexus', 'hb_pb_appnexus'); + expect(targeting['/123456/header-bid-tag-0']['hb_pb']).to.equal('0.0') + }); + }); + describe('targetingControls.allowTargetingKeys', function () { let bid4; @@ -507,6 +552,77 @@ describe('targeting tests', function () { }); }); + describe('targetingControls.addTargetingKeys', function () { + let winningBid = null; + + beforeEach(function () { + bidsReceived = [bid1, bid2, nativeBid1, nativeBid2].map(deepClone); + bidsReceived.forEach((bid) => { + bid.adserverTargeting[CONSTANTS.TARGETING_KEYS.SOURCE] = 'test-source'; + bid.adUnitCode = 'adunit'; + if (winningBid == null || bid.cpm > winningBid.cpm) { + winningBid = bid; + } + }); + enableSendAllBids = true; + }); + + const expandKey = function (key) { + const keys = new Set(); + if (winningBid.adserverTargeting[key] != null) { + keys.add(key); + } + bidsReceived + .filter((bid) => bid.adserverTargeting[key] != null) + .map((bid) => bid.bidderCode) + .forEach((code) => keys.add(`${key}_${code}`.substr(0, 20))); + return new Array(...keys); + } + + const targetingResult = function () { + return targetingInstance.getAllTargeting(['adunit'])['adunit']; + } + + it('should include added keys', function () { + config.setConfig({ + targetingControls: { + addTargetingKeys: ['SOURCE'] + } + }); + expect(targetingResult()).to.include.all.keys(...expandKey(CONSTANTS.TARGETING_KEYS.SOURCE)); + }); + + it('should keep default and native keys', function() { + config.setConfig({ + targetingControls: { + addTargetingKeys: ['SOURCE'] + } + }); + const defaultKeys = new Set(Object.values(CONSTANTS.DEFAULT_TARGETING_KEYS)); + Object.values(CONSTANTS.NATIVE_KEYS).forEach((k) => defaultKeys.add(k)); + + const expectedKeys = new Set(); + bidsReceived + .map((bid) => Object.keys(bid.adserverTargeting)) + .reduce((left, right) => left.concat(right), []) + .filter((key) => defaultKeys.has(key)) + .map(expandKey) + .reduce((left, right) => left.concat(right), []) + .forEach((k) => expectedKeys.add(k)); + expect(targetingResult()).to.include.all.keys(...expectedKeys); + }); + + it('should not be allowed together with allowTargetingKeys', function () { + config.setConfig({ + targetingControls: { + allowTargetingKeys: [CONSTANTS.TARGETING_KEYS.BIDDER], + addTargetingKeys: [CONSTANTS.TARGETING_KEYS.SOURCE] + } + }); + expect(targetingResult).to.throw(); + }); + }); + describe('targetingControls.allowSendAllBidsTargetingKeys', function () { let bid4; diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 2522887bb98..5302b3b8c74 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -15,7 +15,10 @@ import * as ajaxLib from 'src/ajax.js'; import * as auctionModule from 'src/auction.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; import { _sendAdToCreative } from 'src/secureCreatives.js'; -import find from 'core-js-pure/features/array/find.js'; +import {find} from 'src/polyfill.js'; +import {synchronizePromise} from '../../helpers/syncPromise.js'; +import 'src/prebid.js'; +import {hook} from '../../../src/hook.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -190,13 +193,21 @@ window.apntag = { } describe('Unit: Prebid Module', function () { - let bidExpiryStub; + let bidExpiryStub, promiseSandbox; + + before(() => { + hook.ready(); + }); + beforeEach(function () { + promiseSandbox = sinon.createSandbox(); + synchronizePromise(promiseSandbox); bidExpiryStub = sinon.stub(filters, 'isBidNotExpired').callsFake(() => true); configObj.setConfig({ useBidCache: true }); }); afterEach(function() { + promiseSandbox.restore(); $$PREBID_GLOBAL$$.adUnits = []; bidExpiryStub.restore(); configObj.setConfig({ useBidCache: false }); @@ -422,6 +433,7 @@ describe('Unit: Prebid Module', function () { let bid; let auction; let ajaxStub; + let indexStub; let cbTimeout = 3000; let targeting; @@ -487,7 +499,8 @@ describe('Unit: Prebid Module', function () { ], 'bidId': '4d0a6829338a07', 'bidderRequestId': '331f3cf3f1d9c8', - 'auctionId': '20882439e3238c' + 'auctionId': '20882439e3238c', + 'transactionId': 'trdiv-gpt-ad-1460505748561-0', } ], 'auctionStart': 1505250713622, @@ -505,6 +518,7 @@ describe('Unit: Prebid Module', function () { let auctionManagerInstance = newAuctionManager(); targeting = newTargeting(auctionManagerInstance); let adUnits = [{ + transactionId: 'trdiv-gpt-ad-1460505748561-0', code: 'div-gpt-ad-1460505748561-0', sizes: [[300, 250], [300, 600]], bids: [{ @@ -516,6 +530,8 @@ describe('Unit: Prebid Module', function () { }]; let adUnitCodes = ['div-gpt-ad-1460505748561-0']; auction = auctionManagerInstance.createAuction({adUnits, adUnitCodes}); + indexStub = sinon.stub(auctionManager, 'index'); + indexStub.get(() => auctionManagerInstance.index); ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(function() { return function(url, callback) { const fakeResponse = sinon.stub(); @@ -527,6 +543,7 @@ describe('Unit: Prebid Module', function () { afterEach(function () { ajaxStub.restore(); + indexStub.restore(); }); it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 0 to 5', function() { @@ -566,6 +583,7 @@ describe('Unit: Prebid Module', function () { let cbTimeout = 3000; let auctionManagerInstance; let targeting; + let indexStub; const bannerResponse = { 'version': '0.0.1', @@ -644,6 +662,7 @@ describe('Unit: Prebid Module', function () { } const adUnit = { + transactionId: `tr${code}`, code: code, sizes: [[300, 250], [300, 600]], bids: [{ @@ -656,22 +675,22 @@ describe('Unit: Prebid Module', function () { let _mediaTypes = {}; if (mediaTypes.indexOf('banner') !== -1) { - _mediaTypes['banner'] = { + Object.assign(_mediaTypes, { 'banner': {} - }; + }); } if (mediaTypes.indexOf('video') !== -1) { - _mediaTypes['video'] = { + Object.assign(_mediaTypes, { 'video': { context: 'instream', playerSize: [300, 250] } - }; + }); } if (mediaTypes.indexOf('native') !== -1) { - _mediaTypes['native'] = { + Object.assign(_mediaTypes, { 'native': {} - }; + }); } if (Object.keys(_mediaTypes).length > 0) { @@ -733,35 +752,41 @@ describe('Unit: Prebid Module', function () { before(function () { currentPriceBucket = configObj.getConfig('priceGranularity'); - sinon.stub(adapterManager, 'makeBidRequests').callsFake(() => ([{ - 'bidderCode': 'appnexus', - 'auctionId': '20882439e3238c', - 'bidderRequestId': '331f3cf3f1d9c8', - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '10433394' - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'sizes': [ - [ - 300, - 250 + sinon.stub(adapterManager, 'makeBidRequests').callsFake(() => { + const br = { + 'bidderCode': 'appnexus', + 'auctionId': '20882439e3238c', + 'bidderRequestId': '331f3cf3f1d9c8', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'trdiv-gpt-ad-1460505748561-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] ], - [ - 300, - 600 - ] - ], - 'bidId': '4d0a6829338a07', - 'bidderRequestId': '331f3cf3f1d9c8', - 'auctionId': '20882439e3238c' - } - ], - 'auctionStart': 1505250713622, - 'timeout': 3000 - }])); + 'bidId': '4d0a6829338a07', + 'bidderRequestId': '331f3cf3f1d9c8', + 'auctionId': '20882439e3238c' + } + ], + 'auctionStart': 1505250713622, + 'timeout': 3000 + }; + const au = auction.getAdUnits().find((au) => au.transactionId === br.bids[0].transactionId); + br.bids[0].mediaTypes = Object.assign({}, au.mediaTypes); + return [br]; + }); }); after(function () { @@ -769,8 +794,14 @@ describe('Unit: Prebid Module', function () { adapterManager.makeBidRequests.restore(); }) + beforeEach(() => { + indexStub = sinon.stub(auctionManager, 'index'); + indexStub.get(() => auctionManagerInstance.index); + }); + afterEach(function () { ajaxStub.restore(); + indexStub.restore(); }); it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 0 - 5', function() { @@ -1002,12 +1033,7 @@ describe('Unit: Prebid Module', function () { adUnitCode: config.adUnitCodes[0], }; - const event = { - source: { postMessage: sinon.stub() }, - origin: 'origin.sf.com' - }; - - _sendAdToCreative(mockAdObject, event); + _sendAdToCreative(mockAdObject, sinon.stub()); expect(slots[0].spyGetSlotElementId.called).to.equal(false); expect(slots[1].spyGetSlotElementId.called).to.equal(true); @@ -1600,6 +1626,7 @@ describe('Unit: Prebid Module', function () { let auctionArgs; beforeEach(function () { + auctionArgs = null; adUnitsBackup = auction.getAdUnits auctionManagerStub = sinon.stub(auctionManager, 'createAuction').callsFake(function() { auctionArgs = arguments[0]; @@ -1854,11 +1881,39 @@ describe('Unit: Prebid Module', function () { pos: 2 } } + }, + { + code: 'test6', + bids: [], + sizes: [300, 250], + mediaTypes: { + banner: { + sizes: [300, 250], + pos: 0 + } + } }]; $$PREBID_GLOBAL$$.requestBids({ adUnits: adUnit }); expect(auctionArgs.adUnits[0].mediaTypes.banner.pos).to.equal(2); + expect(auctionArgs.adUnits[1].mediaTypes.banner.pos).to.equal(0); + }); + + it(`should allow no bids if 'ortb2Imp' is specified`, () => { + const adUnit = { + code: 'test', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + ortb2Imp: {} + }; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [adUnit] + }); + sinon.assert.match(auctionArgs.adUnits[0], adUnit); }); }); @@ -1995,7 +2050,7 @@ describe('Unit: Prebid Module', function () { }); expect(auctionArgs.adUnits.length).to.equal(1); expect(auctionArgs.adUnits[1]).to.not.exist; - assert.ok(logErrorSpy.calledWith("Detected adUnit.code 'bad-ad-unit-2' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.")); + assert.ok(logErrorSpy.calledWith("adUnit.code 'bad-ad-unit-2' has no 'adUnit.bids' and no 'adUnit.ortb2Imp'. Removing adUnit from auction")); }); }); }); diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index cee416bd1be..e3dc21ffd92 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -1,20 +1,60 @@ import { - _sendAdToCreative, receiveMessage + _sendAdToCreative, getReplier, receiveMessage } from 'src/secureCreatives.js'; +import * as secureCreatives from 'src/secureCreatives.js'; import * as utils from 'src/utils.js'; import {getAdUnits, getBidRequests, getBidResponses} from 'test/fixtures/fixtures.js'; import {auctionManager} from 'src/auctionManager.js'; import * as auctionModule from 'src/auction.js'; import * as native from 'src/native.js'; import {fireNativeTrackers, getAllAssetsMessage} from 'src/native.js'; -import events from 'src/events.js'; +import * as events from 'src/events.js'; import { config as configObj } from 'src/config.js'; +import 'src/prebid.js'; import { expect } from 'chai'; var CONSTANTS = require('src/constants.json'); describe('secureCreatives', () => { + function makeEvent(ev) { + return Object.assign({origin: 'mock-origin', ports: []}, ev) + } + + describe('getReplier', () => { + it('should use source.postMessage if no MessagePort is available', () => { + const ev = { + ports: [], + source: { + postMessage: sinon.spy() + }, + origin: 'mock-origin' + }; + getReplier(ev)('test'); + sinon.assert.calledWith(ev.source.postMessage, JSON.stringify('test')); + }); + + it('should use MesagePort.postMessage if available', () => { + const ev = { + ports: [{ + postMessage: sinon.spy() + }] + } + getReplier(ev)('test'); + sinon.assert.calledWith(ev.ports[0].postMessage, JSON.stringify('test')); + }); + + it('should throw if origin is null and no MessagePort is available', () => { + const ev = { + origin: null, + ports: [], + postMessage: sinon.spy() + } + const reply = getReplier(ev); + expect(() => reply('test')).to.throw(); + }); + }); + describe('_sendAdToCreative', () => { beforeEach(function () { sinon.stub(utils, 'logError'); @@ -40,14 +80,10 @@ describe('secureCreatives', () => { cpm: '1.00', adUnitCode: 'some_dom_id' }; - const event = { - source: { postMessage: sinon.stub() }, - origin: 'origin.sf.com' - }; - - _sendAdToCreative(mockAdObject, event); - expect(JSON.parse(event.source.postMessage.args[0][0]).ad).to.equal(''); - expect(JSON.parse(event.source.postMessage.args[0][0]).adUrl).to.equal('http://creative.prebid.org/1.00'); + const reply = sinon.spy(); + _sendAdToCreative(mockAdObject, reply); + expect(reply.args[0][0].ad).to.equal(''); + expect(reply.args[0][0].adUrl).to.equal('http://creative.prebid.org/1.00'); window.googletag = oldVal; window.apntag = oldapntag; }); @@ -141,9 +177,9 @@ describe('secureCreatives', () => { message: 'Prebid Request' }; - const ev = { - data: JSON.stringify(data) - }; + const ev = makeEvent({ + data: JSON.stringify(data), + }); receiveMessage(ev); @@ -168,9 +204,9 @@ describe('secureCreatives', () => { message: 'Prebid Request' }; - const ev = { + const ev = makeEvent({ data: JSON.stringify(data) - }; + }); receiveMessage(ev); @@ -209,9 +245,9 @@ describe('secureCreatives', () => { message: 'Prebid Request' }; - const ev = { + const ev = makeEvent({ data: JSON.stringify(data) - }; + }); receiveMessage(ev); @@ -237,6 +273,38 @@ describe('secureCreatives', () => { configObj.setConfig({'auctionOptions': {}}); }); + + it('should emit AD_RENDER_FAILED if requested missing adId', () => { + const ev = makeEvent({ + data: JSON.stringify({ + message: 'Prebid Request', + adId: 'missing' + }) + }); + receiveMessage(ev); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: CONSTANTS.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + adId: 'missing' + })); + }); + + it('should emit AD_RENDER_FAILED if creative can\'t be sent to rendering frame', () => { + pushBidResponseToAuction({}); + const ev = makeEvent({ + source: { + postMessage: sinon.stub().callsFake(() => { throw new Error(); }) + }, + data: JSON.stringify({ + message: 'Prebid Request', + adId: bidId + }) + }); + receiveMessage(ev) + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION, + adId: bidId + })); + }); }); describe('Prebid Native', function() { @@ -249,13 +317,13 @@ describe('secureCreatives', () => { action: 'allAssetRequest' }; - const ev = { + const ev = makeEvent({ data: JSON.stringify(data), source: { postMessage: sinon.stub() }, origin: 'any origin' - }; + }); receiveMessage(ev); @@ -278,13 +346,13 @@ describe('secureCreatives', () => { action: 'allAssetRequest' }; - const ev = { + const ev = makeEvent({ data: JSON.stringify(data), source: { postMessage: sinon.stub() }, origin: 'any origin' - }; + }); receiveMessage(ev); @@ -322,13 +390,13 @@ describe('secureCreatives', () => { action: 'allAssetRequest' }; - const ev = { + const ev = makeEvent({ data: JSON.stringify(data), source: { postMessage: sinon.stub() }, origin: 'any origin' - }; + }); receiveMessage(ev); @@ -366,13 +434,13 @@ describe('secureCreatives', () => { action: 'click', }; - const ev = { + const ev = makeEvent({ data: JSON.stringify(data), source: { postMessage: sinon.stub() }, origin: 'any origin' - }; + }); receiveMessage(ev); @@ -395,5 +463,56 @@ describe('secureCreatives', () => { expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); }); }); + + describe('Prebid Event', () => { + Object.entries({ + 'unrendered': [false, (bid) => { delete bid.status; }], + 'rendered': [true, (bid) => { bid.status = CONSTANTS.BID_STATUS.RENDERED }] + }).forEach(([test, [shouldEmit, prepBid]]) => { + describe(`for ${test} bids`, () => { + beforeEach(() => { + prepBid(adResponse); + pushBidResponseToAuction(adResponse); + }); + + it(`should${shouldEmit ? ' ' : ' not '}emit AD_RENDER_FAILED`, () => { + const event = makeEvent({ + data: JSON.stringify({ + message: 'Prebid Event', + event: CONSTANTS.EVENTS.AD_RENDER_FAILED, + adId: bidId, + info: { + reason: 'Fail reason', + message: 'Fail message', + }, + }) + }); + receiveMessage(event); + expect(stubEmit.calledWith(CONSTANTS.EVENTS.AD_RENDER_FAILED, { + adId: bidId, + bid: adResponse, + reason: 'Fail reason', + message: 'Fail message' + })).to.equal(shouldEmit); + }); + + it(`should${shouldEmit ? ' ' : ' not '}emit AD_RENDER_SUCCEEDED`, () => { + const event = makeEvent({ + data: JSON.stringify({ + message: 'Prebid Event', + event: CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, + adId: bidId, + }) + }); + receiveMessage(event); + expect(stubEmit.calledWith(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, { + adId: bidId, + bid: adResponse, + doc: null + })).to.equal(shouldEmit); + }); + }); + }); + }); }); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 6494ead78e7..50a95b079c9 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -2,6 +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'; var assert = require('assert'); @@ -1177,6 +1178,13 @@ describe('Utils', function () { } expect(utils.deepEqual(obj1, obj2)).to.equal(false); }); + it('should check types if {matchTypes: true}', () => { + function Typed(obj) { + Object.assign(this, obj); + } + const obj = {key: 'value'}; + expect(deepEqual({outer: obj}, {outer: new Typed(obj)}, {checkTypes: true})).to.be.false; + }); describe('cyrb53Hash', function() { it('should return the same hash for the same string', function() { @@ -1198,4 +1206,41 @@ describe('Utils', function () { }); }); }); + + describe('waitForElementToLoad', () => { + let element; + let callbacks; + + function callback() { + callbacks++; + } + + function delay(delay = 0) { + return new Promise((resolve) => { + window.setTimeout(resolve, delay); + }) + } + + beforeEach(() => { + callbacks = 0; + element = window.document.createElement('div'); + }); + + it('should respect timeout if set', () => { + waitForElementToLoad(element, 50).then(callback); + return delay(60).then(() => { + expect(callbacks).to.equal(1); + }); + }); + + ['load', 'error'].forEach((event) => { + it(`should complete on '${event} event'`, () => { + waitForElementToLoad(element).then(callback); + element.dispatchEvent(new Event(event)); + return delay().then(() => { + expect(callbacks).to.equal(1); + }) + }); + }); + }); }); diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 554db3ebe4e..34e9bed04b6 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -2,6 +2,8 @@ import chai from 'chai'; import { getCacheUrl, store } from 'src/videoCache.js'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; +import {auctionManager} from '../../src/auctionManager.js'; +import {AuctionIndex} from '../../src/auctionIndex.js'; const should = chai.should(); @@ -32,23 +34,6 @@ function getMockBid(bidder, auctionId, bidderRequestId) { }; } -function getMockBidRequest(bidder = 'appnexus', auctionId = '173afb6d132ba3', bidderRequestId = '3d1063078dfcc8') { - return { - 'bidderCode': bidder, - 'auctionId': auctionId, - 'bidderRequestId': bidderRequestId, - 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', - 'bids': [getMockBid(bidder, auctionId, bidderRequestId)], - 'auctionStart': 1510852447530, - 'timeout': 5000, - 'src': 's2s', - 'doneCbCallCount': 0, - 'refererInfo': { - 'referer': 'http://mytestpage.com' - } - } -} - describe('The video cache', function () { function assertError(callbackSpy) { callbackSpy.calledOnce.should.equal(true); @@ -240,7 +225,7 @@ describe('The video cache', function () { JSON.parse(request.requestBody).should.deep.equal(payload); }); - it('should include additional params in request payload should config.cache.vasttrack be true and bidderRequest argument was defined', () => { + it('should include additional params in request payload should config.cache.vasttrack be true - with timestamp', () => { config.setConfig({ cache: { url: 'https://prebid.adnxs.com/pbc/v1/cache', @@ -269,7 +254,21 @@ describe('The video cache', function () { auctionId: '1234-56789-abcde' }]; - store(bids, function () { }, getMockBidRequest()); + const stub = sinon.stub(auctionManager, 'index'); + stub.get(() => new AuctionIndex(() => [{ + getAuctionId() { + return '1234-56789-abcde'; + }, + getAuctionStart() { + return 1510852447530; + } + }])) + try { + store(bids, function () { }); + } finally { + stub.restore(); + } + const request = server.requests[0]; request.method.should.equal('POST'); request.url.should.equal('https://prebid.adnxs.com/pbc/v1/cache'); diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 3ce8ba081da..61621c7ec42 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,113 +1,103 @@ import { isValidVideoBid } from 'src/video.js'; +import {hook} from '../../src/hook.js'; +import {stubAuctionIndex} from '../helpers/indexStub.js'; describe('video.js', function () { + before(() => { + hook.ready(); + }); + it('validates valid instream bids', function () { const bid = { adId: '456xyz', vastUrl: 'http://www.example.com/vastUrl', - requestId: '123abc' + transactionId: 'au' }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { context: 'instream' } - } - }] + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'instream'} + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(true); }); it('catches invalid instream bids', function () { const bid = { - requestId: '123abc' + transactionId: 'au' }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { context: 'instream' } - } - }] + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'instream'} + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(false); }); it('catches invalid bids when prebid-cache is disabled', function () { - const bidRequests = [{ - bids: [{ - bidder: 'vastOnlyVideoBidder', - mediaTypes: { video: {} }, - }] + const adUnits = [{ + transactionId: 'au', + bidder: 'vastOnlyVideoBidder', + mediaTypes: {video: {}}, }]; - const valid = isValidVideoBid({ vastXml: 'vast' }, bidRequests); + const valid = isValidVideoBid({ transactionId: 'au', vastXml: 'vast' }, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(false); }); it('validates valid outstream bids', function () { const bid = { - requestId: '123abc', + transactionId: 'au', renderer: { url: 'render.url', render: () => true, } }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { context: 'outstream' } - } - }] + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'outstream'} + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(true); }); it('validates valid outstream bids with a publisher defined renderer', function () { const bid = { - requestId: '123abc', + transactionId: 'au', }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { - context: 'outstream', - renderer: { - url: 'render.url', - render: () => true, - } - } + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: { + context: 'outstream', } - }] + }, + renderer: { + url: 'render.url', + render: () => true, + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(true); }); it('catches invalid outstream bids', function () { const bid = { - requestId: '123abc' + transactionId: 'au', }; - const bidRequests = [{ - bids: [{ - bidId: '123abc', - bidder: 'appnexus', - mediaTypes: { - video: { context: 'outstream' } - } - }] + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'outstream'} + } }]; - const valid = isValidVideoBid(bid, bidRequests); + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(false); }); }); diff --git a/test/test_deps.js b/test/test_deps.js new file mode 100644 index 00000000000..8b8c9fd3a0f --- /dev/null +++ b/test/test_deps.js @@ -0,0 +1,9 @@ +window.process = { + env: { + NODE_ENV: 'production' + } +}; + +require('test/helpers/prebidGlobal.js'); +require('test/mocks/adloaderStub.js'); +require('test/mocks/xhr.js'); diff --git a/test/test_index.js b/test/test_index.js index 53d75e36176..883f4d0590c 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,6 +1,4 @@ -require('test/helpers/prebidGlobal.js'); -require('test/mocks/adloaderStub.js'); -require('test/mocks/xhr.js'); +require('./test_deps.js'); var testsContext = require.context('.', true, /_spec$/); testsContext.keys().forEach(testsContext); diff --git a/wdio.conf.js b/wdio.conf.js index c9aaa3f160d..4865c03f339 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -9,7 +9,7 @@ function getCapabilities() { return platformMap[os]; } - // only IE 11, Chrome 80 & Firefox 73 run as part of functional tests + // only Chrome 80 & Firefox 73 run as part of functional tests // rest of the browsers are discarded. delete browsers['bs_chrome_79_windows_10']; delete browsers['bs_firefox_72_windows_10']; diff --git a/webpack.conf.js b/webpack.conf.js index a738a2a0868..5269f5300f5 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -2,19 +2,11 @@ var prebid = require('./package.json'); var path = require('path'); var webpack = require('webpack'); var helpers = require('./gulpHelpers.js'); -var RequireEnsureWithoutJsonp = require('./plugins/RequireEnsureWithoutJsonp.js'); var { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); var argv = require('yargs').argv; -var allowedModules = require('./allowedModules.js'); - -// list of module names to never include in the common bundle chunk -var neverBundle = [ - 'AnalyticsAdapter.js' -]; var plugins = [ - new RequireEnsureWithoutJsonp(), - new webpack.EnvironmentPlugin(['LiveConnectMode']) + new webpack.EnvironmentPlugin({'LiveConnectMode': null}) ]; if (argv.analyze) { @@ -23,27 +15,8 @@ if (argv.analyze) { ) } -plugins.push( // this plugin must be last so it can be easily removed for karma unit tests - new webpack.optimize.CommonsChunkPlugin({ - name: 'prebid', - filename: 'prebid-core.js', - minChunks: function(module) { - return ( - ( - module.context && module.context.startsWith(path.resolve('./src')) && - !(module.resource && neverBundle.some(name => module.resource.includes(name))) - ) || - ( - module.resource && (allowedModules.src.concat(['core-js'])).some( - name => module.resource.includes(path.resolve('./node_modules/' + name)) - ) - ) - ); - } - }) -); - module.exports = { + mode: 'production', devtool: 'source-map', resolve: { modules: [ @@ -51,8 +24,26 @@ module.exports = { 'node_modules' ], }, + entry: (() => { + const entry = { + 'prebid-core': { + import: './src/prebid.js' + } + }; + const selectedModules = new Set(helpers.getArgModules()); + Object.entries(helpers.getModules()).forEach(([fn, mod]) => { + if (selectedModules.size === 0 || selectedModules.has(mod)) { + entry[mod] = { + import: fn, + dependOn: 'prebid-core' + } + } + }); + return entry; + })(), output: { - jsonpFunction: prebid.globalVarName + 'Chunk' + chunkLoadingGlobal: prebid.globalVarName + 'Chunk', + chunkLoading: 'jsonp', }, module: { rules: [ @@ -77,5 +68,9 @@ module.exports = { } ] }, + optimization: { + usedExports: true, + sideEffects: true, + }, plugins };