From 4131e67c469ac74d4db0e30b5af97eb484209200 Mon Sep 17 00:00:00 2001 From: aecook <36861760+aecook@users.noreply.github.com> Date: Tue, 28 Mar 2023 09:42:25 -0700 Subject: [PATCH] PFG-1962: Updating to 7.42 (#155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rise Bid Adapter: added isWrapper parameter to adapter request (#9329) * add Rise adapter * fixes * change param isOrg to org * Rise adapter * change email for rise * fix circle failed * bump * bump * bump * remove space * Upgrade Rise adapter to 5.0 * added isWrapper param * addes is_wrapper parameter to documentation * added is_wrapper to test * removed isWrapper Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur Co-authored-by: OronW <41260031+OronW@users.noreply.github.com> Co-authored-by: lasloche <62240785+lasloche@users.noreply.github.com> * Added video media type support (#9326) * Aso Bid Adapter: add bcmint alias (#9387) * Add bcmint alias * kick off tests Co-authored-by: dev Co-authored-by: Chris Huie * AIDEM Bid Adapter: added wpar and placementId param (#9377) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object * Added wpar to environment * Added placementId parameter * added unit tests for the wpar environment object Co-authored-by: darkstar Co-authored-by: AndreaC <67786179+darkstarac@users.noreply.github.com> * Taboola Bid Adapter: onBidWon, userSyncs, gpp support and FPD (#9376) * on-bid-won * support-fpd * support-fpd * support-fpd * support-fpd * support-fpd * support-fpd * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * position-pagetype * Yieldlab Bid Adapter: read and pass UserIdsAsEids atype information (#9370) * YieldlabBidAdapter read atype information from UserIdsAsEids and pass it as query parameter (atypes={idprovider}:{atype},{idprovider2}:{atype2},...) * Update type hint and add semi colons Co-authored-by: Christoph Kipping <29540638+kippsterr@users.noreply.github.com> * Medianet RTD module: fix `getTargetingData` to retrieve correct adUnits (#9392) * Holid Bid Adapter: initial release (#9371) * Holid bid adapter * Adjust test to various device sizes * Include first party data from ortb2 object * Remove trailing spaces in test * Appnexus Bid Adapter : add video data from the request to the bid response (#9396) * Appnexus adapter: add video data from the request to the bid response * kick off tests * remove change Co-authored-by: Chris Huie * AdagioBidAdapter: Remove some params (#9398) * Feedad Bid Adapter: added new bid request parameters (#9397) * added file scaffold * added isBidRequestValid implementation * added local prototype of ad integration * added implementation for placement ID validation * fixed video context filter * applied lint to feedad bid adapter * added unit test for bid request validation * added buildRequest unit test * added unit tests for timeout and bid won callbacks * updated bid request to FeedAd API * added parsing of feedad api bid response * added transmisison of tracking events to FeedAd Api * code cleanup * updated feedad unit tests for buildRequest method * added unit tests for event tracking implementation * added unit test for interpretResponse method * added adapter documentation * added dedicated feedad example page * updated feedad adapter to use live system * updated FeedAd adapter placement ID regex * removed groups from FeedAd adapter placement ID regex * removed dedicated feedad example page * updated imports in FeedAd adapter file to use relative paths * updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest() * added GDPR fields to the FeedAd bid request * removed video from supported media types of the FeedAd adapter * increased version code of FeedAd adapter to 1.0.2 * removed unnecessary check of bidder request * fixed unit test testing for old FeedAd version * removed video media type example from documentation file * added gvlid to FeedAd adapter * added decoration parameter to adapter documentation * added pass through of additional bid parameters * added user syncs to FeedAd bid adapter * increased FeedAd bid adapter version * lint pass over FeedAd bid adapter * fixed parsing of user syncs from server response * increased FeedAd bid adapter version * fixed version code in test file * added adapter and prebid version to bid request parameters * removed TODO item * added missing test case for user syncs * increased adapter version to 1.0.5 * Yieldlab Bid Adapter: code style updates (#9386) * Consistently add trailing comma and semicolons everywhere * Use shorthand object property function definition * Fix typo and update type hint * GPP support for the yahoo connect id module. (#9399) Co-authored-by: dumitrubarbos * yahoospp bidder& aol bidder: GPP Support in bid requests (#9345) * GPP support for the yahoospp bidder and legacy aol bidder. * GPP support for the yahoospp bidder and legacy aol bidder. * GPP support for the yahoo connect id module - review comments. * GPP support for the yahoo connect id module - review comments. * GPP support for the yahoo connect id module - review comments. Co-authored-by: dumitrubarbos * Orbitsoft Bid Adapter : add adapter back to current version (#9288) * Adding Orbitsoft module * Adding Orbitsoft module (corrected) * Adding Orbitsoft module (correction of remarks) * Adding Orbitsoft module (correction of remarks) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to new constructor) * Adding Orbitsoft module (delete unnecessary aliases) * Adding Orbitsoft module (delete unnecessary aliases) * fixed orbitsoftAdapter * fixed orbitsoftAdapter processing undefined request referrer * fixed orbitsoftAdapter processing undefined request referrer * fix-orbitsoftAdaper: codereview fixes * added changes for new spec * added changes for new spec * added changes for new spec Co-authored-by: Dmitriy Shimko Co-authored-by: Хатламаджиян Виталий * extract-gpid (#9401) * Yieldmo Adapter: Add support for structured user agent (#9380) * Adding sua to device object * Update * import pick * ESLint fixes * Adding unit test * Copying entire device object * appnnexus bid adapter - support for adomain (#9403) * smartx Bid Adapter: add support for sitekey (#9408) * Add smartclipBidAdapter * smartxBidAdapter.js - removed unused variables, removed debug, added window before the outstream related functions * - made outstream player configurable * remove wrong named files * camelcase * fix * Out-Stream render update to SmartPlay 5.2 * ESlint fix * ESlint fix * ESlint fix * adjust tests, fixes * ESlint * adjusted desired bitrate examples * added bid.meta.advertiserDomains support * bug fix for numeric elementID outstream render * fix renderer url * support for floors module * bugfixes to be openRTB 2.5 compliant * update internal renderer usage * remove unused outstream_function logic * bugfix outstream options for default outstream renderer configuration * [PREB-10] fix empty title not configurable * add pbjs version * testing with outstream 5.3.0 * pbjs version into content.ext * made visibilityThreshold configurable * adjust position of pbjs version * Merge branch 'master' of https://github.com/prebid/Prebid.js into HEAD * update smartclip outstream player version to support outstream 6 release along with necessary config changes * Add support for schain * vacuuming * update custom header x-openrtb-version to 2.5 * add support for sitekey to smartxBidAdapter Co-authored-by: smartclip AdTechnology Co-authored-by: Gino Cirlini Co-authored-by: smartclip-adtech <65160328+smartclip-adtech@users.noreply.github.com> * Adloox RTD Module: fix breakage since 7.x release (#9383) getTargetingData resulted in a crash as 'auction' is no longer passed in and we needed access to the global ORTB2 targetings. Reworked to be a lot simplier, and removed the ATF viewability segment and use the results of intersectionRtdProvider if present * JW Player Video Module: trigger error when missing div id (#9407) * checks for divId and get state existence * adds test for missing divId * add gppConsent (#9415) * ssp added to meta.demandSource (#9409) * adds a safety check (#9420) * Alkimi Bid Adapter: using the floors convention (#9368) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes * change maintainer info * Updated the ad unit params * features support added * transfer adUnitCode * transfer adUnitCode: test * AlkimiBidAdapter getFloor() using Co-authored-by: Alexander Bogdanov Co-authored-by: Kalidas Engaiahraj Co-authored-by: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Co-authored-by: Nikulin Mikhail * init new Appush adapter (#9346) * Prebid 7.32.0 release * Increment version to 7.33.0-pre * TargetVideo Bid Adapter: Updating margin rule (#9428) * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo Bid Adapter: Add GDPR/USP support * TargetVideo Bid Adapter: Add GDPR/USP support tests * TargetVideo Bid Adapter: Updating margin rule * PBjs Core (Price Floors) : Support inverseBidAdjustment function (#9395) * support inverseBidAdjustment function * pass in bidRequest object to adjustments * dont do fake bids bobby duh * Criteo Bid Adapter : Bump Publisher Tag version (#9429) Update reference to version 133 (latest) * IX Bid Adapter: retrieve user/agent hints and fix tmax issue (#9394) * feat: passthrough gpp information when it is provided [PB-1395] * chore: passthrough using module [PB-1395] * IX Bid Adapter Changes: change mtype logic, useragent client hints, change tmax logic * remove fallback for tmax timeout Co-authored-by: Chris Corbo * PBjs Core (Promises): fix static method GreedyPromise.resolve not working with Angular + Zone.js (#9426) * fix: Webpack v5 complain about named export from JSON modules * Index Exchange Adapter: fix "Should not import the named export 'EVENTS'.'AUCTION_DEBUG' (imported as 'EVENTS') from default-exporting module (only default export is available soon)"" * fix: Uncaught TypeError: Cannot read properties of undefined (reading 'getSlotElementId') * fix: Uncaught TypeError: Cannot read properties of undefined (reading 'getSlotElementId') * fix #9422 * refactor: fix linting error Co-authored-by: Javier Marín * USP consent management: handle errors from CMPs that cannot deal with `registerDeletion` (#9434) * nexx360 Bid Adapter: aliases list update (#9439) * ssp added to meta.demandSource * aliases update * Update live-connect-js version (#9438) * update live-connect-js * fix * fix package-lock.json * enable video/banner mediatypes for inImage/inBanner/inArticle/inScreen (#9417) * The payload extended with document.referer and canonicalUrl (#9416) * Prebid 7.33.0 release * Increment version to 7.34.0-pre * Admixer Bid Adapter : adding floor module support and new alias (#9427) * add floor module support * bidFloor update * Update admixerBidAdapter.md * Update admixerBidAdapter.js * remove tests * tests * floor test * Update admixerBidAdapter_spec.js * Update admixerBidAdapter_spec.js * Update admixerBidAdapter.js * https endpoint * lint bugs fix * Admatic Bid Adapter : bugfix with AdserverCurrency param (#9451) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * added support for user agent client hints (#9445) * nextMillenniumBidAdapter: fix replaceGetUserMacro function (#9442) * add video support * fix replaceUserMacro func * Add tests Co-authored-by: Mikhail Ivanchenko * kargo - adding support for vast url in bid response (#9447) * openxOrtbBidAdapter: fix device.sua test (#9452) * Criteo Bid Adapter : Bump Publisher Tag version (#9450) Co-authored-by: v.raybaud * BLIINK Bid Adapter: fix ttl (#9443) * fix(bliink): bid ttl * fix(bliink): ttl unit tests Co-authored-by: Samous * Bump ua-parser-js from 0.7.32 to 0.7.33 (#9456) Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.32 to 0.7.33. - [Release notes](https://github.com/faisalman/ua-parser-js/releases) - [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md) - [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.32...0.7.33) --- updated-dependencies: - dependency-name: ua-parser-js dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Triplelift Bid Adapter: Support for GPP in bid requests (#9455) * prioritize topmostlocation * adds test for topmostlocation / referrer * cleanup * delete param after test * TL-32803: Update referrer logic * TL-32803: Update referrer logic * TL-34204: Add support for GPP Co-authored-by: Nick Llerandi Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> * Lotame Panorama ID Module : add safari handling (#9418) * GRUE-176 work in progress, trying to make the id URL dynamic. * GRUE-176 After some attempts with leveraging environment variables, decided to look for an environment variable passed on configuration. * GRUE-177 WIP added a fix for handling timestamps, plus there's some temporary debugging that I was using to understand flow. * GRUE-177 Fixed bug with localStorage checking for empty, included null as a possible return value. * GRUE-177 updated the environment handling for dev, qa, and prod. I'm still on the fence on whether we need this, but it's allowing the tests to pass currently, so leaving it in for now. * GRUE-178 removed the dynamic URL handling for the ID endpoint. We will manage that change with the build process for testing. * GRUE-339 changes to check for browser, and accomodate Safari with a different URL. * GRUE-339 changes to check for browser, and accomodate Safari with a different URL. * GRUE-339 Removed the obfuscation from the Safari URL, as it was deemed unnecessary. * GRUE-339 corrected the safari id endpoint - I had forgotten that it was different than the usual one. * GRUE-339 Updated test to cover Safari handling. * GRUE-340 Updated the variable name for the cookieless domain, to remove the emphasis on Safari and better illustrate that this is a general approach. Co-authored-by: Mark Conrad Co-authored-by: Mark * Adnuntius Bid Adapter: Bug fix for multiple mime types. (#9458) * Holid bid adapter: skip user syncs when no bidders in bid response (#9462) * Seeding Alliance Bid Adapter: add banner support and get endpoint-url from config (#9404) * add seedingAlliance Adapter * add two native default params * ... * ... * seedingAlliance Adapter: add two more default native params * updating seedingAlliance Adapter * seedingAlliance Adapter * quickfix no bids + net revenue * bugfix replace auction price * change URL and add versioning * add vendorId to seedingAllianceAdapter * optimize code + banner support * add newline at the end of file * fix ci/circleci error * add new specs Co-authored-by: SeedingAllianceTech <55976067+SeedingAllianceTech@users.noreply.github.com> Co-authored-by: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Co-authored-by: Hendrick Musche * Emx Digital Bid Adapter : adding US Privacy string support (#9461) * adding ccpa support for emx_digital adapter * emx_digital ccpa compliance: lint fix * emx 3.0 compliance update * fix outstream renderer issue, update test spec * refactor formatVideoResponse function to use core-js/find * Add support for schain forwarding * Resolved issue with Schain object location * prebid 5.0 floor module and advertiserDomain support * liveramp idl and uid2.0 support for prebid * gpid support * remove utils ext * remove empty line * remove trailing spaces * move gpid test module * move gpid test module * removing trailing spaces from unit test * remove comments from unit test * Include us_privacy string in redirects (#8) * include us_privacy string in redirects * added test cases for us_privacy and gdpr * added test cases for gdpr without usp * updated test case when no privacy strings and fixed package-lock.json * revert package-lock.json Co-authored-by: EMXDigital * kick off ci tests Co-authored-by: Nick Colletti Co-authored-by: Nick Colletti Co-authored-by: Kiyoshi Hara Co-authored-by: Dan Bogdan Co-authored-by: Jherez Taylor Co-authored-by: EMXDigital Co-authored-by: Rakesh Balakrishnan Co-authored-by: Kevin Co-authored-by: Chris Huie * consumableBidAdapter: add gdpr and usp sync params (#9463) * PBS Bid Adapter : site should not exist when app is present (#9258) * Update prebidServerBidAdapter_spec.js * Update prebidServerBidAdapter_spec.js * fix test * remove app from site test * add site/app/dooh function * fix config * remove deepSetValue * add to ortb converter * add check * add back publisher.id * fix linting * ortb conversion lib: leave only one of dooh, app, or site in the request Co-authored-by: Demetrio Girardi * updated pbs filterSettings to sync with pbjs config filterSettings (#9423) * ArcSpan RTD Module: Initial Release (#9459) * Create arcspanRtdProvider.md * Added ArcSpan RTD Provider * Implemented alter bid request function in ArcSpan RTD Provider * Added unit tests for ArcSpan RTD Provider * Added more unit tests for ArcSpan RTD Provider * Load ArcSpan scripts using Prebid script loader * Fixed ArcSpan RTD module unit tests * Adding ArcSpan to submodules.json * Load ArcSpan script only if not already on the page * Load ArcSpan script only if not already on the page * Update issue tracker action to use new gh api (#9466) * Prebid 7.34.0 release * Increment version to 7.35.0-pre * PulsePoint Bid Adapter: support timeout/tmax (#9465) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * ET-12672 - passing tmax value to PulsePoint bidder * ET-12672 - using 500ms as a default and adding formatting Co-authored-by: anand-venkatraman * hadronId user id submodule: force localStorage (#9432) * Storing hadronId in localStorage after getting it from server * reverting hadronId documentation * Outbrain Bid Adapter: added video support (#9405) * add video support * add more video props * Smartadserver Bid Adapter: support floors per media type (#9437) * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Rework payloads enriching Co-authored-by: Meven Courouble * Smartytech Bid Adapter: Add video format (#9388) * Add new bid adapter for company smartytech * change domain to prod * update unit tests * remove unused code * remove unused code * add video type * update documentation * Core & priceFloors: pass bid request to `bidCpmAdjustment`; warn about invalid `adUnit.floors` definitions (#9441) * Core & priceFloors: pass `bidRequest` as third arg to `bidCpmAdjustment` * Floors: warn when adUnit.floors is not valid * Fixes potential error when reading _pssps localStorage key (#9474) * SmartyadsBidAdapter: update request params (#9472) Co-authored-by: Vasyl Rishko * Bump tibdex/github-app-token from 1.7.0 to 1.8.0 (#9479) Bumps [tibdex/github-app-token](https://github.com/tibdex/github-app-token) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/tibdex/github-app-token/releases) - [Commits](https://github.com/tibdex/github-app-token/compare/021a2405c7f990db57f5eae5397423dcc554159c...b62528385c34dbc9f38e5f4225ac829252d1ea92) --- updated-dependencies: - dependency-name: tibdex/github-app-token dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * C-Wire Bid Adapter: Code refactorings (#9446) * Reduce c-wire adapter to basic functionality (c-wire/creatives#3) * Minimize processing of bids * Add basic tests for adapter * Read cw_creative parameter from url and pass it as creativeId to the request (c-wire/creative#9) * Attach slot dimensions and maxWidth to request (c-wire/creatives#22) * Add feature flag support (c-wire/creatives#22) * Propagate cw_debug flag to ad server payload * Implement CWID (c-wire/prebid#3) * Add maxHeight CSS attribute (c-wire/creatives#22) * Update Prebid endpoint (c-wire/prebid#3) * Rename referrer to old property name * Map pageViewId to auctionId (c-wire/prebid#3) * Re-introduce pageId as required parameter (c-wire/prebid#3) * Map response body's bid.html property to bid.ad (c-wire/prebid#3) * Rename creativeId property to cwcreative in the payload (c-wire/prebid#3) * Flatten pageId and placementId into bid object (c-wire/prebid#3) * Send bid won and error events (c-wire/prebid#3) * Align cw* parameters with documentation and PBS adapter (c-wire/prebid#3) * QA Fix featureFlag check * Add refgroups from URL parameters (c-wire/prebid#3) * Inline adapter specific payload (c-wire/prebid#3) * Make pageViewId per prebid instance (c-wire/prebid#3) * Extract cwire extensions into own method (c-wire/prebid#3) * Update documentation (c-wire/prebid#3) * Add validations for placementId and pageId (c-wire/prebid#3) * QA Fix linting error * Add prebid version to payload (c-wire/prebid#3) * bidderCode fix (#9485) * Taboola Bid Adapter: pass nurl to bidResponse (#9482) * nurl-bugfix * nurl-bugfix * Vidazoo Bid Adapter: support for gpp consent and bid data (#9480) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * feat(module): pass gpp consent and bid data to server. * fix(module): change spec bidder timeout to 3000. --------- Co-authored-by: Udi Talias Co-authored-by: roman * Smartadserver Bid Adapter: support GPP consent (#9489) * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Add GPP support * Rework payloads enriching --------- Co-authored-by: Meven Courouble * Prebid Core: Added aliasRegistry to the Public API (#9467) * added aliasRegistry to the public api * reverted a few changes made for local dev * addressed feedback * AdYouLike Bid Adapter : add pbjs version information (#9476) * add required clickurl in every native adrequest * allows the native response to be given as is to prebid if possible * add unit tests on new Native case * Handle meta object in bid response with default addomains array * fix icon retrieval in Native case * Update priorities in case of multiple mediatypes given * improve robustness and fix associated unit test on picture urls * add support for params.size parameter * add unit test on new size format * Makes sure the playerSize format is consistent * enable Vast response on bidder adapter * fix lint errors * add test on Vast format case * add userId to bidrequest * revert package-lock.json changes * improve multiple mediatype handling * Expose adyoulike GVL id * fix icurl issue when retreiving icon for Native mediatype * update unit tests on icon url in native mediatype * target video endpoint when video mediatype is present * add unit test on video endpoint * detect if bid request has video * remove console log * Add size information in Video bid + unit tests * Remove unused method (old video retrieval) * update pagereferrer and pageUrl values * improve null robustness in native getAssetValue * change function body and add unit test * fix pageUrl in case not given i ortb2 * adjust pageUrl and referrer values * add unit tests on new priority behaviour * add pbjsversion in bid request * add unit test --------- Co-authored-by: GuillaumeA * Sonobi Bid Adapter: add additional sizes to bid request (#9413) * Added mediaTypes.video playerSize and sizes. * Save. * Order from least to most important. 1. Deprecated bid.size 2. bid.params.sizes. 3. mediaTypes.video.playerSize 4. mediaTypes.video.sizes 5. mediaTypes.banner.sizes * check for null. * Accepting multiple uniques sizes from different props. * Comments. * Added mediaTypes.video playerSize and sizes. * Save. * Order from least to most important. 1. Deprecated bid.size 2. bid.params.sizes. 3. mediaTypes.video.playerSize 4. mediaTypes.video.sizes 5. mediaTypes.banner.sizes * check for null. * Accepting multiple uniques sizes from different props. * Comments. * Circle CI error. Circle CI failing on line 298 due to trailing space, but there isnt one. * Circle CI error. Circle CI failing on line 298 due to trailing space, but there isnt one. * Readded hello_world.html --------- Co-authored-by: Zac Carlin * Add ESLint Plugin Recommendation for VSCODE (#9498) Just a quality of life improvement for any VSCode users. * Deprecate zeusPrimeRtdProvider submodule (#9358) Deprecates the zeusPrimeRtdProvider submodule and updates the associated documentation and tests. * Consent Management: Added config option for user action timeout (#9365) * progress * fixed tests and refactored * reverted some changes made while devloping on my local * in progress * updated action timeout logic * removed comment * reverted a few changes * reverted another change * addressed feedback * refactored actionTimeout logic * ZetaGlobalSsp bid adapter: bidfloor module (#9490) * ZetaGlobalSspBidAdapter: support bidfloors module * remove added space for linting * fix test --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Chris Huie * OneTag Bid Adapter: add gppConsent fetch (#9487) Co-authored-by: federico * Prebid 7.35.0 release * Increment version to 7.36.0-pre * Bump http-cache-semantics from 4.1.0 to 4.1.1 (#9502) Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/kornelski/http-cache-semantics/releases) - [Commits](https://github.com/kornelski/http-cache-semantics/commits) --- updated-dependencies: - dependency-name: http-cache-semantics dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix gpg Key Expiration for Debian Containers (#9497) Debian containers for yarn are having issues with key expirations. This change resolves that. Eventually the base images should be updated, but that timeline is unknown. There are a number of proposed solutions for the issue, but this one fixes ours. References to the issue: https://github.com/yarnpkg/yarn/issues/7866 Similar in AWS Builds: https://github.com/yarnpkg/yarn/issues/7866 * Topics Module: Mark Down file added (#9484) * Topics MD file added * Topics MD file changes * BrightcomSSP bid adapter: add new adapter (#9411) * BrightcomSSP: add new adapter * BrightcomSSP: add glvid * BrightcomSSP: add missing gvlid; update isBidRequestValidC * AIDEM Bidder Adapter: changed required params and win notice payload (#9457) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object * Added wpar to environment * Added placementId parameter * added unit tests for the wpar environment object * PlacementId is now a required parameter Added optional rateLimit parameter Added publisherId, siteId, placementId in win notice payload Added unit tests * Revert to optional placementId parameter Added missing semicolons --------- Co-authored-by: Giovanni Sollazzo Co-authored-by: darkstar * LiveIntent Id module: Update live-connect-js version (#9505) * Update live-connect-js version * fix eslint comment * Vidazoo Bid Adapter - webSessionId request param (#9504) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * Vidazoo Bid Adapter - added webSessionId to request --------- Co-authored-by: roman Co-authored-by: Saar Amrani <89377180+saar120@users.noreply.github.com> Co-authored-by: Saar Amrani * pass referer to ortb request (#9475) Co-authored-by: Anthony Lin * Freewheel SSP Bid Adapter: bugfix for schain (#9492) * freewheel-sspBidAdapter: Bug Fix for schain (#9471) * Fixed schain logic to parse schain as string * Updated schain test to check schain string * Update freewheel-sspBidAdapter.js * kickoff tests --------- Co-authored-by: Scott Floam Co-authored-by: Patrick McCann Co-authored-by: Chris Huie * Add GVLID to smartx (#9512) * Scattered Bid Adapter: New Adapter (#9295) * Scattered module skeleton * Review fixes * Remove netRevenue check * Fix test * Review fixes * update amxIdSystem (#9508) * Scattered bid adapter: fix tests (#9514) * Neuwo RTD Module : initial release (#9385) * added neuwoRtdProvider.js (implementation), integration test spec, initial description document (neuwoRtdProvider.md) * neuwoRtdProvider finish: added _example.html using https://docs.prebid.org/dev-docs/examples/basic-example.html improved neuwoRtdProvider logging and error handling improved neuwoRtdProvider.md testing steps * neuwoRtdProvider.js -> updated to convert response using IAB to Tax ID conversion dictionary +tests, finished "no response" test * neuwoRtdProvider.js: added BILLABLE_EVENTs: 'auction' on getBidRequestData, 'request' on successful parse of neuwo request, 'general' on if global data actually extended using topics * review reflections: added injection into site.cattax, site.pagecat for "IAB 2.2", clarified conversion code conversion dictionary comment to include IAB 2+ - added tests to further ensure endpoint response malformation handling robustness * neuwoRtdProvider.js -> updated segtax code to compatible non-deprecated version (2.0 -> to 2.2, used conversion table is the same) * neuwoRtdProvider.js -> reduced: - amount and mutability of variables (const) - responsibility of injectTopics, callback lifted into callbacks of the ajax call - amount of billable events, on api call success only (expects marketing_categories) * SirData RTD Module: Change (add Seller Defined Audience (SDA) support) (#9448) * sirdataRtdProvider.js update (SDA support) Add support for Seller Defined Audience. Various improvement and support for new bidders. * update Sirdata RTD module linting issues fixed * New tests Add new tests to reach 80%+ test coverage * Update sirdataRtdProvider_spec.js * Update sirdataRtdProvider_spec.js * Nexx360 Bid Adapter: League-m alias added (#9518) * bidderCode fix * League-m added as alias * Feedad Bid Adapter: updated user-sync handling (#9519) * added file scaffold * added isBidRequestValid implementation * added local prototype of ad integration * added implementation for placement ID validation * fixed video context filter * applied lint to feedad bid adapter * added unit test for bid request validation * added buildRequest unit test * added unit tests for timeout and bid won callbacks * updated bid request to FeedAd API * added parsing of feedad api bid response * added transmisison of tracking events to FeedAd Api * code cleanup * updated feedad unit tests for buildRequest method * added unit tests for event tracking implementation * added unit test for interpretResponse method * added adapter documentation * added dedicated feedad example page * updated feedad adapter to use live system * updated FeedAd adapter placement ID regex * removed groups from FeedAd adapter placement ID regex * removed dedicated feedad example page * updated imports in FeedAd adapter file to use relative paths * updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest() * added GDPR fields to the FeedAd bid request * removed video from supported media types of the FeedAd adapter * increased version code of FeedAd adapter to 1.0.2 * removed unnecessary check of bidder request * fixed unit test testing for old FeedAd version * removed video media type example from documentation file * added gvlid to FeedAd adapter * added decoration parameter to adapter documentation * added pass through of additional bid parameters * added user syncs to FeedAd bid adapter * increased FeedAd bid adapter version * lint pass over FeedAd bid adapter * fixed parsing of user syncs from server response * increased FeedAd bid adapter version * fixed version code in test file * added adapter and prebid version to bid request parameters * removed TODO item * added missing test case for user syncs * increased adapter version to 1.0.5 * updated from upstream * updated from upstream * updated user sync to accept multiple server responses * increased FeedAd bid adapter version code * fixed test case * fixed lint errors * Adding support of eids for smilewanted (#9440) * IX Bid Adapter - Add support for IMUID (#9500) * feat: add imuid to pbjs adapter [PB-1434] * chore: add unit test [PB-1434] --------- Co-authored-by: Chris Corbo * Admaru Bid Adapter: Add user sync (#9444) * AdmaruBidAdapter: add user sync * AdmaruBidAdapter: Use https in user sync * Limelight Digital Bid Adapter: added new custom fields for targeting (#9436) * Added new custom fields (#2) * Fixed integration * Fixes after review * Fixes after review --------- Co-authored-by: RuzannaAvetisyan <44729750+RuzannaAvetisyan@users.noreply.github.com> Co-authored-by: apykhteyev * Appnexus Bid Adapter : support alternate format for bid.params properties (#9503) * appnexus bid adapter - support alternate format for params * fix lint error * appnexus bid adapter - userid support update (#9507) * IX Bid Adapter: refactor buildRequest method (#9495) * feat: remove request splitting logic [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] --------- Co-authored-by: shahin.rahbariasl * bug fix for grid adapter not applying jw segment data when bidderRequest ortb2.user data doe not exist (#9521) * add sua support (#9523) * Read and pass device.sua object to translator payload (#9526) Co-authored-by: Kapil Tuptewar * Prebid 7.36.0 release * Increment version to 7.37.0-pre * Sonobi Bid Adapter: remove userid query param (#9496) * Removed the userid param This was causing a 414 error when userid and eids was duplicated * Update Sonobi Unit test for userid param * Remove deepClone import * Restored a userid unit test to ensure that the buildRequests function still works even if the publisher specifies a userid * Reworded the userid unit test and asserted that userid is not being set * Fixed undefined check in unit test * Update adapter docs (c-wire/prebid#3) (#9528) * MinuteMediaPlus Bid Adapter: New Bid Adapter (#9430) * MinuteMediaPlus bid adapter * bidder code changed to * incrx Bid Adapter : add support for adtype and settings (#9477) * Update incrxBidAdapter.js We have added new keys in the endpoint response (Line 71, 72) due to which we need to update our Adapter with the latest file * fix linting * Update incrxBidAdapter_spec.js We have added new keys in the response * Update incrxBidAdapter.js removed consolelog lines --------- Co-authored-by: Chris Huie * KueezRtbBidAdapter - pass gpp and bid data to server. (#9491) * Criteo Bid Adapter: Map full user & site objects (#9534) DPP-4310 * Update: Replaced adUnitCount with adUnits array (#9510) * Livewrapped Bid Adapter: added support for Price Floors Module (#9540) * Added support for the Price Floors Module * Use the ad server's currency when getting prices from the floors module * Default to USD if the ad server's currency can't be retrieved * Set the default currency at the right place * Added tests and made a minor change in how device width and height are calculated * Only include flrCur when ad requests contain floors * Proxistore Bid Adapter: migrate to new subdomain (#9537) * Migrate to new subdomain * Upgrade domain in tests as well * Mediago / Discovery Bid Adapters : update reporting of eids to server (#9539) * Mediago Bid Adapter:new adapter * remove console * change spec file to fix CircleCI * change spec file to fix CircleCI * change spec file * Update mediagoBidAdapter.js * Update mediagoBidAdapter.js * rerun CurcleCi * update mediagoBidAdapter * update discoveryBidAdapter * Discovery Bid Adapter : parameter updates * Mediago Bid Adapter : parameter updates * Mediago Bid Adapter : code style format * rerun circleci * rerun circleci * rerun circleci * rerun circleci * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server --------- Co-authored-by: BaronYu * Core & PBS adapter: introduce bidder-level `ortb2Imp`; s2s-only `module` bids; PBS bidder-level `imp` params (#9470) * adUnit.bid.ortb2Imp support * Add "module" bids and PBS bidder-level imp params * Merge branch 'master' into bid-ortb2Imp * Update tests * nextMillennium Bid Adapter: cookie sync URL (#9522) * if no response, use hardcoded URL * lint added a space * net rev true * add test and fix queries (&) * Adagio Bid Adapter: update video params validation (#9542) * Adagio Bid Adapter: update video params validation * Adagio: update adapter doc * Triplelift Bid Adapter: set networkId in response (#9545) * prioritize topmostlocation * adds test for topmostlocation / referrer * cleanup * delete param after test * TL-32803: Update referrer logic * TL-32803: Update referrer logic * TL-34204: Add support for GPP * TL-34944: Add logic to pass networkId back in the bid response * TL-34944: Add logic to pass networkId back in the bid response * TL-34944: Add logic to pass networkId back in the bid response * few more tests --------- Co-authored-by: Nick Llerandi Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> * Oxxion Analytics Adapter : initial adapter release (#9449) * oxxion Analytics Adapter * debug(oxxionRtdProvider): onAuctionInit() * Revert "debug(oxxionRtdProvider): onAuctionInit()" This reverts commit d0894e34119fdbc9a075e35ea3f309774ca6bbbd. --------- Co-authored-by: Anthony Guyot * Revert "Oxxion Analytics Adapter : initial adapter release (#9449)" (#9549) This reverts commit 23fe08397668f252c774213a3be14617264a4e8d. * Adding tmax to bid request. (#9548) Defaulting to 400 if none is supplied. * Criteo Bid Adapter: Bumping PublisherTag version & Adapter version (#9554) * Prebid 7.37.0 release * Increment version to 7.38.0-pre * Add new size 480x480 (ID: 261) in Rubicon Adapter (#9557) Co-authored-by: Andrea Cannuni * Fix empty-string sessionId when using unexpired sessions (#9559) * Freewheel SSP Bid Adapter: Add Dynamic Floor Price Feature (#9511) * FreeWheel add floor price * FreeWheel code update * FreeWheel-SSP-Adapter: Update to use Vast 4.2 by default * PubMatic Bid Adapter : removed check on title asset of Native (#9563) * Removed length constraint on title for native * updated the test cases --------- Co-authored-by: pm-azhar-mulla * Optidigital Bid Adapter: initial adapter release (#9501) * add new adapter * update adapter * update unit tests * update adapter --------- Co-authored-by: Dawid W * Sspbc Bid Adapter : add native asset mapping and floor updates (#9389) * 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 * [ssbc-adapter] bid cache for video ads * [sspbc-adapter] add PageView.id to banner ad; update tests * [sspbc-adapter] fix window.gam not being added to banner html * [sspbc-adapter] send device / content language * [sspbc-adapter] send pageview and site ids to user sync frame * [sspbc-adapter] add ES6 version of common ad library (for banner creatives) * [sspbc-adapter] move content property * [sspbc-adapter] reorganize notification payload creator * [sspbc-adapter] store PLN price in meta; send in bidWon notification * [sspbc-adapter] add playbackmethod to supporten video params; allow overridinbg video settngs via bid.params.video * [sspbc-adapter] update md * [sspbc-adapter] fix error in mapVideo method (Object assign merror when mediaTypes do not contain video) * [sspbc-5.7] remove storage/cookie detection * [sspbc-5.7] add screen size to request * [sspbc-adapter] add adapter update from sspbc-5.8 branch --------- Co-authored-by: Wojciech Biały * Admedia Bid Adapter : initial adapter release (#9525) * Admedia Bid Adapter Code * Test case changed * Removed unnecessary code + fixed test case --------- Co-authored-by: kapil_arora * lazy parsing of browser (#9571) * Add GPP support in the bid request (#9572) * Smaato: add gpp and gpp_sid support (#9552) * Adagio Bid Adapter: add new param to bidRequest payload (#9564) * AdagioBidAdapter: add `usIfr` to bidRequest payload * AdagioBidAdapter: improve .md file --------- Co-authored-by: G15N * appnexus bid adapter - update contact email (#9577) * Zeta Ssp Bid Adapter: timeout func (#9544) * ZetaGlobalSspBidAdapter: apply onTimeout func * test * fix style --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko * Epsilon Analytics Adapter: Adding error logging (#9538) * eps_error_tracking - adding error tracking for epsilon analytics, simplified errors for recording on back end * eps_error_tracking - adding error tracking for epsilon analytics - fixing default value - fixing style error * Cointraffic Bid Adapter: domain change (#9580) * domain change * added types in tests * Neuwo RTD Provider : api url required in configuration (#9565) * added neuwoRtdProvider.js (implementation), integration test spec, initial description document (neuwoRtdProvider.md) * neuwoRtdProvider finish: added _example.html using https://docs.prebid.org/dev-docs/examples/basic-example.html improved neuwoRtdProvider logging and error handling improved neuwoRtdProvider.md testing steps * neuwoRtdProvider.js -> updated to convert response using IAB to Tax ID conversion dictionary +tests, finished "no response" test * neuwoRtdProvider.js: added BILLABLE_EVENTs: 'auction' on getBidRequestData, 'request' on successful parse of neuwo request, 'general' on if global data actually extended using topics * review reflections: added injection into site.cattax, site.pagecat for "IAB 2.2", clarified conversion code conversion dictionary comment to include IAB 2+ - added tests to further ensure endpoint response malformation handling robustness * neuwoRtdProvider.js -> updated segtax code to compatible non-deprecated version (2.0 -> to 2.2, used conversion table is the same) * neuwoRtdProvider.js -> reduced: - amount and mutability of variables (const) - responsibility of injectTopics, callback lifted into callbacks of the ajax call - amount of billable events, on api call success only (expects marketing_categories) * configurable API endpoint, documentation update * Pubmatic Bid Adapter: Removed logic to append JW player data to the "dctr" param (#9169) * removed logic to append jw data to dctr param * removed unneeded code * removed extra html page for testing * Prebidmanager Analytics Adapter: reduce requests size (#9566) * Prebidmanager analytics adapter: reduce requests size * Update prebidmanagerAnalyticsAdapter.js * Update prebidmanagerAnalyticsAdapter.js --------- Co-authored-by: Patrick McCann * Increment version to 7.39.0-pre * Prebid 7.38.0 release * Resetdigital bid Adapter: add fields and suport for coppa (#9400) * Resetdigital Bid Adapter: Add custom fields to bid request and GDRP support * fixing comments * fix bidfloor settings * get kewords from request and site meta tag" * fix bild errors --------- Co-authored-by: nicolas taglienti * Renamed cpexId to czechAdId (#9433) * remove outdated lgtm shield (#9535) * Rubicon Bid Adapter: add native support (#9574) * add support for native * wrap native tests around FEATURES.NATIVE * remove commented out code * HB-16092 support multiformat parameter * do not generate imp if has only banner media type * check banner bid type only if mediaTypes.banner * new multiformat logic * bidonmultiformat * fixes: do not set empty keywords; better behavior for floors. * currency is always added * remove prorperties that are already set by ortb --------- Co-authored-by: Michele Nasti * Missena: add format params and onBidWon pixel (#9517) * Alkimi Bid Adapter : add multisize multiformat (#9575) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes * change maintainer info * Updated the ad unit params * features support added * transfer adUnitCode * transfer adUnitCode: test * AlkimiBidAdapter getFloor() using * ALK-504 Multi size ad slot support * ALK-504 Multi size ad slot support --------- Co-authored-by: Alexander <32703851+pro-nsk@users.noreply.github.com> Co-authored-by: Alexander Bogdanov Co-authored-by: Alexander Bogdanov Co-authored-by: motors Co-authored-by: Kalidas Engaiahraj Co-authored-by: mik * Revert "Rubicon Bid Adapter: add native support (#9574)" (#9599) This reverts commit 99ffff27e11b2c984dad2dc8256e930d548f5535. * Prebid Core: emit seatnonbid from prebid server (#9453) * Parse and emit seatnonbid from server * Fix testing adjustments * Use onResponse for seatNonBids * Fix linting error * Emit to auction and add unit tests * Use optional property chaining * returnallbidstatus * fix varname in spec * Tappx Bid Adapter: Fix/os and vendor (#9468) * Fix: creating host correctly when http or https are added from the beginning * Fix :: Changed double quotes for single quotes * Fix :: Getting the full page URL * Fix :: Changed order params * Fix :: Replaced quotes from double to simple * Fix :: Adapting format to lint * Remove TODO comment * Added more controls * camelcase fix * Changed test * Remove "inIframe" util * Fix: solved error getOs and created getVendor functions * Fix: Solved CircelCI format problems --------- Co-authored-by: Jordi Arnau Co-authored-by: ruben_tappx * NovatiqidSystem UserId Module: Fix async json response (#9601) * 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 * include the AIB Vendor Id * fix async response --------- Co-authored-by: novatiq <79258366+novatiq@users.noreply.github.com> * ID5 Adapter: protect against local storage writing without consent (#9587) * id-6129: don't write to local storage without consent * id-6129: clean up * id-6129: clean up * id-6129: refactor * id-6129: use deepAccess * id-6129: unit tests * id-6129: logging * id-6129: improve log * GDPR (consentManagement): fix `actionTimeout` behavior (#9600) * GDPR (consentManagement): fix `actionTimeout` behavior * Add test case for actionTimeout = 0 * VisibleMeasures Bid Adapter : initial adapter release (#9488) * add VisibleMeasures adapter * fix test * PubMatic Bid Adapter and Analytics Adapter : Added support for dchain and metadata (#9579) * only send hb_acat if acat is present * removed a couple unneeded files * reverted a few more unneeded changes * Changes to support dchain object. And passing metadata in logger log including dchain * Added missing code * Added the missing metadata in PubaticBidAdapter * Revrting the changes from jlquaccia/UOE-8728 * Test cases added for metadata and dchain --------- Co-authored-by: jlquaccia * Prebid Core: Only Send Optional Category Targeting Key if ACAT is Present on Page (#9530) * only send hb_acat if acat is present * removed a couple unneeded files * reverted a few more unneeded changes * updated acat test * Eplanning Bid Adapter : cut url when url size greater than 255 (#9606) * cut url whit url length is greater than 255 * Fix lint * TL-35335: Cast playbackmethod as array (#9610) * Relevad RTD Module : initial release (#9454) * Added implementation of the Relevad Real-Time Data Provider * removed bidder from the testing HTML file * Addressed reviewer's request w.r.t. removing bidder-specific handling' * set page url * Addressed code review comments: fixed email address, added description of ORTB attributes we pass to the bidders * Addressed code review comments --------- Co-authored-by: Relevad <> * AMX Bid Adapter: add gpp support (#9556) * AMX bid adapter: add gpp support, cookie sync improvements * fix unit tests * Rubicon bid adapter: native support (#9605) * add support for native * wrap native tests around FEATURES.NATIVE * remove commented out code * HB-16092 support multiformat parameter * do not generate imp if has only banner media type * check banner bid type only if mediaTypes.banner * new multiformat logic * bidonmultiformat * fixes: do not set empty keywords; better behavior for floors. * currency is always added * remove prorperties that are already set by ortb * fix tests --------- Co-authored-by: Michele Nasti * [Video Module] Bugfix: Allow publishers to override video params (#9611) * allows publisher to override video params * passes proper config to ima * TTD BId Adapter: Support bidfloor bidding parameter (#9607) * rbc-OPATH-367: support bidfloor bidding parameter * rbc-OPATH-367-added-tests --------- Co-authored-by: robert.charlton Co-authored-by: robert-charlton-ttd <124640243+robert-charlton-ttd@users.noreply.github.com> * Add: banner requests for ViouslyBidAdapter (#9513) * Prebid 7.39.0 release * Increment version to 7.40.0-pre * Relevad RTD adapter: updated contact email (#9618) * Added implementation of the Relevad Real-Time Data Provider * removed bidder from the testing HTML file * Addressed reviewer's request w.r.t. removing bidder-specific handling' * set page url * Addressed code review comments: fixed email address, added description of ORTB attributes we pass to the bidders * Addressed code review comments * Updated contact email address --------- Co-authored-by: Relevad <> * Remove references to deprecated cookies (#9619) * ImproveDigital Bid Adapter: minor bug fixes (#9614) * Improve Digital adapter: publisher endpoint, addtl consent, syncs (#14) - add bidders to sync url when extend mode enabled - set ConsentedProvidersSettings when extend mode enabled - dynamically generated AD_SERVER_URL when publisherId available * Code refactored * Minor changes * Fix an issue where uppercase tags broke the JS on page, as they were not properly escaped * fixed tests --------- Co-authored-by: Faisal Islam <93644923+faisalvs@users.noreply.github.com> Co-authored-by: Faisal Islam Co-authored-by: Jozef Bartek Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> * Rubicon bid adapter: remove pchain support (#9621) * Rubicon adapter - remove pchain support * removing pchain from unit tests * kargo adapter - adding prebid version to requests (#9620) * Updated adf adapter to support native with type; use ortb request for natives (#9616) Co-authored-by: Justas Pupelis * smallfix on response validation (#9623) Co-authored-by: Vasyl Rishko * Lemma Digital Bid Adapter : initial adapter release (#9532) * Added lemmadigital bid adapter code * update documentation for banner ad request * Made review changes for video params reads first * read video size from playerSize (#9625) Co-authored-by: Love Sharma * TheMediaGrid Bid Adapters : do not use jwp segments from bid.rtd field (#9627) * TheMediaGrid: do not use jwp segments from bid.rtd field * TheMediaGridNM: do not use jwp segments from bid.rtd field * PBjs Core: do not rely on an extendable `window.Promise` (#9558) * Core: do not rely on an extendable `window.Promise` * Add test cases for empty-input all / allSettled * Nexx360 Bid Adapter: native support added and ortbConverter usage (#9626) * native added and ortb converter usage * Update nexx360BidAdapter.js * ADJS-1271-send-envelope-param-for-lexicon (#9634) Co-authored-by: John Bauzon * Vidazoo Bid Adapter: pass sua params. (#9636) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * feat: pass sua params to bid request. --------- Co-authored-by: Udi Talias Co-authored-by: roman * Permutive RTD Module: migrate magnite to ortb2 (#9555) * feat(permutiveRtd): migrate rubicon targeting to ortb2 * perf(permutiveRtd): prevent redundant cohort reads and updates * fix(permutiveRtd): enable debugger logs for ortb2 updates * fix(permutiveRtd): provide identity bidder fn fallback * test(permutiveRtd): update params to follow refactor * fix(permutiveRtd): prevent multiple targeting updates once in realtime * fix(permutiveRtd): require `waitForIt` and permutive to be false to complete immediately * fix(permutiveRtd): remove bidder specific logic * update Mediago & Discovery BidAdapter:remove size filter (#9585) * Mediago Bid Adapter:new adapter * remove console * change spec file to fix CircleCI * change spec file to fix CircleCI * change spec file * Update mediagoBidAdapter.js * Update mediagoBidAdapter.js * rerun CurcleCi * update mediagoBidAdapter * update discoveryBidAdapter * Discovery Bid Adapter : parameter updates * Mediago Bid Adapter : parameter updates * Mediago Bid Adapter : code style format * rerun circleci * rerun circleci * rerun circleci * rerun circleci * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * update Mediago & Discovery BidAdapter:remove size filter * update Mediago & Discovery BidAdapter:code format * update Mediago & Discovery BidAdapter:code format * update Mediago & Discovery BidAdapter:add param in banner format * update mediago & discovery:first party data * update mediago & discovery:first party data * update mediago & discovery:first party data --------- Co-authored-by: BaronYu * kueezRtb Bid Adapter: pass sua data to server. (#9643) * Core: fix native render when adUnits defines `mediaTypes.native.ortb` but adapter replies with "legacy" native bid (#9638) * Fix conversion to ortb native * Add natvie ortb response to message payload when the adUnit uses native ortb * main>modules\neuwoRtdProvider.js > apiUrl format handling improved, removed unnecessary parameter integrationExamples\gpt\neuwoRtdProvider_example.html > fixed render-step handling on warning (#9646) * Rubicon Bid Adapter: add size 1x2 (#9644) * Added support for the Price Floors Module * Use the ad server's currency when getting prices from the floors module * Default to USD if the ad server's currency can't be retrieved * Set the default currency at the right place * Added tests and made a minor change in how device width and height are calculated * Only include flrCur when ad requests contain floors * Added 1x2 (524) size to Rubicon adapter --------- Co-authored-by: msmeza * Prebid 7.40.0 release * Increment version to 7.41.0-pre * PubWise Bid Adapter: support video and improve tests (#9576) * updates to PubWise bidder * updates for video outstream processing * update pubwise bidder to 0.3.0 * add testing updates * update device detection * Update creative.html (#9648) * CleanMediaNet Bid Adapter : add userid support and update testing (#9608) * Update cleanmedianetBidAdapter.js * Update cleanmedianetBidAdapter.md * Revert "Update cleanmedianetBidAdapter.md" This reverts commit f7d3e50ccc4eb8c846ab92ab5bf8704351a11c2b. * Revert "Update cleanmedianetBidAdapter.js" This reverts commit 861230608f744e3b35a8bc939f947a43afe05e8c. * Update cleanmedianetBidAdapter_spec.js * Update cleanmedianetBidAdapter.md * Update cleanmedianetBidAdapter.js * Updated config.yml * Updated config.yml * kick off tests * Update cleanmedianetBidAdapter.md * Update cleanmedianetBidAdapter.js EndPoint Update * Update cleanmedianetBidAdapter.js --------- Co-authored-by: Moshe Moses Co-authored-by: moses Co-authored-by: Chris Huie * Stv Bid Adapter : initial adapter release (#9533) * initial commit * adapted buildRequests function * refinement pfilter and bcat * refinement * adapted tests for isBidRequestValid,buildRequests * adaptations for test * finished building stvBidAdapter.js * finished: ran tests, coverage 99% * update: rename w->srw, h->srh * adapt stvBidAdapter.md * remove dspx from stv adapters * some changes (missing: getUserSyncs, but is the same as in radsBidAdapter) * added checks in getUserSyncs; ran tests --------- Co-authored-by: theo_ * TheMediaGrid Bid Adapter : support gpp (#9629) * TheMediaGrid: support gpp * TheMediaGrid: review fixes * TheMediaGrid: fix merge typo * TheMediaGrid: minor fix --------- Co-authored-by: Stanyslav Glubyshev * Delete flocIdSystem.js (#9652) * Core: make video cache timeout configurable (#9578) * Aduptech Bid Adapter : add GVLID (#9658) * Add GVLID to adup tech Axel Springer Teaser Ad GmbH (647) is the vendor running this bid adatper * fix lint error --------- Co-authored-by: Chris Huie * IX Bid Adapter: update additional consent field (#9650) * feat: update additional consent location [PB-351] * feat: update additional consent field [PB-351] --------- Co-authored-by: shahin.rahbariasl * Missena: add userEids, add network and cpm to tracking (#9645) * Freewheel SSP Bid Adapter : support userIdAsEids (#9655) * FreeWheel add floor price * FreeWheel code update * FreeWheel-SSP-Adapter: Update to use Vast 4.2 by default * FreeWheel-SSP-Adapter add userIdAsEids support * Freewheel-SSP-Adapter add test for eids * NoBid Bid Adapter: support for Floors (#9635) * Enable supplyChain support * Added support for COPPA * rebuilt * Added support for Extended User IDs. * Added support for the "meta" attribute in bid response. * Added support for Floors to the NoBid Prebid Adapter. * Delete .project Removed IDE file. * Delete .jsdtscope Removed IDE file. * Delete org.eclipse.wst.jsdt.ui.superType.container Removed IDE file. * Delete org.eclipse.wst.jsdt.ui.superType.name Removed IDE file. * Delete a Delete strange file. * Delete nobidBidAdapter.js.orig Delete file not needed. --------- Co-authored-by: Reda Guermas * AdUp Technology bid adapter: avoid modification of bid request (#9656) * Deleted the default empty string from userConsent argument in the module's init-function. (#9663) * Bump webpack from 5.74.0 to 5.76.0 (#9668) Bumps [webpack](https://github.com/webpack/webpack) from 5.74.0 to 5.76.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.74.0...v5.76.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Yandex Bid Adapter: (#9604) * added support media-type native * fixed gdpr parameters * changed endpoint url Co-authored-by: Taras Saveliev * Freewheel SSP Adapter: add prebid version in ad request (#9667) * FreeWheel add floor price * FreeWheel code update * FreeWheel-SSP-Adapter: Update to use Vast 4.2 by default * FreeWheel-SSP-Adapter add userIdAsEids support * Freewheel-SSP-Adapter add test for eids * Freewheel SSP Adapter: add prebid version in request * code cleanup * feat(permutiveRtd): add `ix` and custom cohort `ortb2.user.data` (#9631) * Browsi Bid Adapter: initial release (#9562) * browsiBidAdapter * browsiBidAdapter-pr-check-ids-are-truthy-and-use-video-sizes * browsiBidAdapter-pr-spec-fix * support the timeout parameter in the conversant adapter (#9673) Co-authored-by: johwier * DFP video adserver module: set `description_url` to pub's URL by default; do not skip setting it if `cache.url` is set (#9665) * Increment version to 7.42.0-pre * Prebid 7.41.0 release * Next Millennium Bid Adapter : added `imp[].id` required parameter for openrtb 2.5 request. (#9675) * changed name company * changed name company in test * Added processing of a new group_id parameter * Added processing of a new group_id parameter * changed check parameters * fixed lint remarks * added test * fixed bug - lint * changed test * changed test - 2 * fixed bug - adapter * added logic for getting ad impressions * Collecting timeouts data * Collecting resaponses and no_bids data * changed a name function * added event bidRequested * added event bidRequested * added function initialization events * fixed bug * save * added tests * Added processing of the disabledSendingStatisticData parameter, which disables sending statistics data * changed the name of the variables * fixed bug - added required parameter for openrtb request * ZetaGlobalSsp: provide schain (#9678) Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko * Underdog Media Bid Adapter: Switch request to method to POST (#9547) * switch UDM adapter request to post * add siteId to url * add support for 33 across id * Nativo Bid Adapter: adding UserId support (#9583) * Initial nativoBidAdapter document creation (js, md and spec) * Fulling working prebid using nativoBidAdapter. Support for GDPR and CCPA in user syncs. * Added defult size settings based on the largest ad unit. Added response body validation. Added consent to request url qs params. * Changed bidder endpoint url * Changed double quotes to single quotes. * Reverted package-json.lock to remove modifications from PR * Added optional bidder param 'url' so the ad server can force- match an existing placement * Lint fix. Added space after if. * Added new QS param to send various adUnit data to adapter endpopint * Updated unit test for new QS param * Added qs param to keep track of ad unit refreshes * Updated bidMap key default value * Updated refresh increment logic * Refactored spread operator for IE11 support * Updated isBidRequestValid check * Refactored Object.enties to use Object.keys to fix CircleCI testing errors * Updated bid mapping key creation to prioritize ad unit code over placementId * Added filtering by ad, advertiser and campaign. * Merged master * Added more robust bidDataMap with multiple key access * Deduped filer values * Rolled back package.json * Duped upstream/master's package.lock file ... not sure how it got changed in the first place * Small refactor of filterData length check. Removed comparison with 0 since a length value of 0 is already falsy. * Added bid sizes to request * Fixed function name in spec. Added unit tests. * Added priceFloor module support * Added protection agains empty url parameter * Changed ntv_url QS param to use referrer.location instead of referrer.page * Removed testing 'only' flag * Added ntv_url QS param value validation * Added userId support * Added unit tests, refactored for bugs * Wrapped ajax in try/catch * Added more unit testing * Updated eid check for duplicate values. Removed error logging as we no longer need it. * Kulturemedia Bid Adapter: New Adapter (#9613) * Initial implementation of kulturemedia bid adapter * Changing outstream to instream * Enriching md file with test examples * Changing nId to networkId * Cleaning up md file --------- Co-authored-by: Danijel Predarski * GDPR consent management: accept static config without `getTCData` (#9664) * Revert "Nativo Bid Adapter: adding UserId support (#9583)" (#9691) This reverts commit aa100bc72df52fe2e6c9bb7bcf41be9867c65be5. * SmartyTech Bid Adapter : add size parameters (#9692) * Add new bid adapter for company smartytech * change domain to prod * update unit tests * remove unused code * remove unused code * add video type * update documentation * Bump tibdex/github-app-token from 1.7.0 to 1.8.0 Bumps [tibdex/github-app-token](https://github.com/tibdex/github-app-token) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/tibdex/github-app-token/releases) - [Commits](https://github.com/tibdex/github-app-token/compare/021a2405c7f990db57f5eae5397423dcc554159c...b62528385c34dbc9f38e5f4225ac829252d1ea92) --- updated-dependencies: - dependency-name: tibdex/github-app-token dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * add ability to force set banner size --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Criteo Bid Adapter: reinforce adomain type in case of missmatch (#9687) * Update criteoBidAdapter.js * Update auction.js * LiveIntent UserId module: update LiveConnect dependency (#9672) * Update LiveConnect dependency * Shorter fire timeout longer wait timeout * Check for liveConnect presence before attempting to fire * Rename defaultEventDelay to fireEventDelay * Clean-up package-lock.json * Do not fire event in userId spec * Remove duplicate function --------- Co-authored-by: Viktor Dreiling Co-authored-by: Viktor Dreiling * minutemediaplus Bid Adapter - pass gpp, sua and bid data to server. (#9637) * DistroScale bidder enhancement (#9641) * Disable describe.only and it.only (#9693) * Permutive RTD Module: migrate appnexus to ortb2 (#9630) * fix(permutiveRtd): migrate appnexus to ortb2 keywords * test(permutiveRtd): remove outdated appnexus test * fix(permutiveRtd): remove bidder specific logic for keywords * fix(permutiveRtd): remove alias map in legacy segment setting * Revert "fix(permutiveRtd): remove alias map in legacy segment setting" This reverts commit e1fa6347ad29ba3c14e76fffad46a081d9d6da95. * changed the URL (#9698) * Core: improve FPD enrichment (#9659) * Move sua logic to core * Improve FPD enrichment: merge in `setConfig({app, device, site})`; leave only one of `dooh`, `app`, `site`; enrich the one left with `domain` / `publisher` * ShowHeroes Bid Adapter: added support for USP (#9681) * 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 * ShowHeroes Adapter - naming convention issue * Mixed AdUnits declaration support * ITDEV-4723 PrebidJS adapter support with SupplyChain module object * ITDEV-4723 Fix tests * ITDEV-4723 New entry point * showheroes-bsBidAdapter: Add support for advertiserDomains * showheroes-bsBidAdapter: hotfix for outstream render * showheroes-bsBidAdapter: update renderer url * showheroes-bsBidAdapter: use only the necessary fields from the gdprConsent * ShowHeroes adapter - added a new endpoint * ShowHeroes adapter - unit tests * Update showheroes-bsBidAdapter.md * ShowHeroes adapter - added support for USP --------- Co-authored-by: veranevera Co-authored-by: Elizaveta Voziyanova <44549195+h2p4x8@users.noreply.github.com> * VIS.X fix onTimeout data (#9657) * Fluct Bid Adapter: add user sync support (#9651) * feat: implementation getUserSyncs for image * user syncs * fix * return all syncs * correct referer -> page * add regs.{gdpr,us_privacy,coppa} to reqs --------- Co-authored-by: Ryo Sakuma * Prebid 7.42.0 release --------- Signed-off-by: dependabot[bot] Co-authored-by: inna Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur Co-authored-by: OronW <41260031+OronW@users.noreply.github.com> Co-authored-by: lasloche <62240785+lasloche@users.noreply.github.com> Co-authored-by: Udi Talias ⚛️ Co-authored-by: Adserver.Online <61009237+adserver-online@users.noreply.github.com> Co-authored-by: dev Co-authored-by: Chris Huie Co-authored-by: Giovanni Sollazzo Co-authored-by: darkstar Co-authored-by: AndreaC <67786179+darkstarac@users.noreply.github.com> Co-authored-by: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Co-authored-by: nkloeber <100145701+nkloeber@users.noreply.github.com> Co-authored-by: Christoph Kipping <29540638+kippsterr@users.noreply.github.com> Co-authored-by: Demetrio Girardi Co-authored-by: Krzysztof Desput Co-authored-by: Torsten Co-authored-by: Olivier Co-authored-by: couchcrew-thomas Co-authored-by: radubarbos Co-authored-by: dumitrubarbos Co-authored-by: VitalyOrbit Co-authored-by: Dmitriy Shimko Co-authored-by: Хатламаджиян Виталий Co-authored-by: Nayan Savla Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: Gino <53079123+Skylinar@users.noreply.github.com> Co-authored-by: smartclip AdTechnology Co-authored-by: Gino Cirlini Co-authored-by: smartclip-adtech <65160328+smartclip-adtech@users.noreply.github.com> Co-authored-by: Alexander Clouter Co-authored-by: Karim Mourra Co-authored-by: dalmenarDevST <116064809+dalmenarDevST@users.noreply.github.com> Co-authored-by: Gabriel Chicoye Co-authored-by: Zeeshan Rasool Co-authored-by: Alexander <32703851+pro-nsk@users.noreply.github.com> Co-authored-by: Alexander Bogdanov Co-authored-by: Kalidas Engaiahraj Co-authored-by: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Co-authored-by: Nikulin Mikhail Co-authored-by: nitzanappush <116559356+nitzanappush@users.noreply.github.com> Co-authored-by: Prebid.js automated release Co-authored-by: Dejan Grbavcic Co-authored-by: Robert Ray Martinez III Co-authored-by: Tachfine Co-authored-by: ccorbo Co-authored-by: Chris Corbo Co-authored-by: Javier Marín Co-authored-by: Javier Marín Co-authored-by: Wiem Zine El Abidine Co-authored-by: Yohan Boutin Co-authored-by: pkwisniowski-id5 <121963897+pkwisniowski-id5@users.noreply.github.com> Co-authored-by: Daria Boyko Co-authored-by: Fatih Kaya Co-authored-by: Jarosław Stropa <51368253+jaropenx@users.noreply.github.com> Co-authored-by: Mikhail Ivanchenko Co-authored-by: Mikhail Ivanchenko Co-authored-by: Andy Rusiecki Co-authored-by: Vincent Co-authored-by: v.raybaud Co-authored-by: samous Co-authored-by: Samous Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Patrick Loughrey Co-authored-by: Nick Llerandi Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> Co-authored-by: Mike Marcus Co-authored-by: Mark Conrad Co-authored-by: Mark Co-authored-by: Mikael Lundin Co-authored-by: Krzysztof Desput Co-authored-by: sag-jonhil <78849369+sag-jonhil@users.noreply.github.com> Co-authored-by: SeedingAllianceTech <55976067+SeedingAllianceTech@users.noreply.github.com> Co-authored-by: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Co-authored-by: Hendrick Musche Co-authored-by: EMX Digital <43830380+EMXDigital@users.noreply.github.com> Co-authored-by: Nick Colletti Co-authored-by: Nick Colletti Co-authored-by: Kiyoshi Hara Co-authored-by: Dan Bogdan Co-authored-by: Jherez Taylor Co-authored-by: EMXDigital Co-authored-by: Rakesh Balakrishnan Co-authored-by: Kevin Co-authored-by: Jason Piros Co-authored-by: Jason Quaccia Co-authored-by: Jose Cabal-Ugaz <6942011+josecu@users.noreply.github.com> Co-authored-by: Eugene Rachitskiy Co-authored-by: anand-venkatraman Co-authored-by: joseluis laso Co-authored-by: Mark Kuhar Co-authored-by: Krzysztof Sokół <88041828+smart-adserver@users.noreply.github.com> Co-authored-by: Meven Courouble Co-authored-by: preved-medved Co-authored-by: Paulius Imbrasas <880130+CremboC@users.noreply.github.com> Co-authored-by: rishko00 <43280707+rishko00@users.noreply.github.com> Co-authored-by: Vasyl Rishko Co-authored-by: Espen <2290914+espen-j@users.noreply.github.com> Co-authored-by: Saar Amrani Co-authored-by: roman Co-authored-by: guiann Co-authored-by: GuillaumeA Co-authored-by: Zachary Carlin Co-authored-by: Zac Carlin Co-authored-by: Stephen Johnston Co-authored-by: Matt Crute <872334+mbcrute@users.noreply.github.com> Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Co-authored-by: federico Co-authored-by: Nitin Nimbalkar <96475150+nitin0610@users.noreply.github.com> Co-authored-by: Alexandru Co-authored-by: Maxim Schuwalow <16665913+mschuwalow@users.noreply.github.com> Co-authored-by: Saar Amrani <89377180+saar120@users.noreply.github.com> Co-authored-by: anthonyjl92 Co-authored-by: Anthony Lin Co-authored-by: Scott Floam Co-authored-by: Scott Floam Co-authored-by: Patrick McCann Co-authored-by: Muki Seiler Co-authored-by: Tomasz Kogut Co-authored-by: Nick Jacob Co-authored-by: moquity <33487117+moquity@users.noreply.github.com> Co-authored-by: nouchy <33549554+nouchy@users.noreply.github.com> Co-authored-by: Maxime DEYMÈS <47388595+MaxSmileWanted@users.noreply.github.com> Co-authored-by: Alexander Pykhteyev Co-authored-by: RuzannaAvetisyan <44729750+RuzannaAvetisyan@users.noreply.github.com> Co-authored-by: apykhteyev Co-authored-by: shahinrahbariasl <56240400+shahinrahbariasl@users.noreply.github.com> Co-authored-by: shahin.rahbariasl Co-authored-by: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Co-authored-by: Taro FURUKAWA <6879289+0tarof@users.noreply.github.com> Co-authored-by: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Co-authored-by: Kapil Tuptewar Co-authored-by: JonGoSonobi Co-authored-by: Prebid-Team Co-authored-by: Léonard Labat Co-authored-by: pnh-pubx <73683023+pnh-pubx@users.noreply.github.com> Co-authored-by: msmeza Co-authored-by: Anthony Co-authored-by: BaronJHYu <254878848@qq.com> Co-authored-by: BaronYu Co-authored-by: JacobKlein26 <42449375+JacobKlein26@users.noreply.github.com> Co-authored-by: Gammelin Guillaume Co-authored-by: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Co-authored-by: Anthony Guyot Co-authored-by: Andrea Cannuni <57228257+ACannuniRP@users.noreply.github.com> Co-authored-by: Andrea Cannuni Co-authored-by: Michael Bishop <41241382+bishpls@users.noreply.github.com> Co-authored-by: xwang202 <57196235+xwang202@users.noreply.github.com> Co-authored-by: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Co-authored-by: pm-azhar-mulla Co-authored-by: optidigital-prebid <124287395+optidigital-prebid@users.noreply.github.com> Co-authored-by: Dawid W Co-authored-by: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Co-authored-by: Wojciech Biały Co-authored-by: admediadev <124867578+admediadev@users.noreply.github.com> Co-authored-by: kapil_arora Co-authored-by: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Co-authored-by: Ruslan S <15067216+Enigo@users.noreply.github.com> Co-authored-by: G15N Co-authored-by: Aaron Price <67345931+AaronColbyPrice@users.noreply.github.com> Co-authored-by: Aleksandr Štšepelin Co-authored-by: Artur Zybin <57211433+arturzybin@users.noreply.github.com> Co-authored-by: nicolasresetdigital <86322485+nicolasresetdigital@users.noreply.github.com> Co-authored-by: nicolas taglienti Co-authored-by: David Spohr Co-authored-by: Michele Nasti Co-authored-by: Michele Nasti Co-authored-by: Petre Damoc Co-authored-by: Alexander Bogdanov Co-authored-by: motors Co-authored-by: mik Co-authored-by: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Co-authored-by: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Co-authored-by: Jordi Arnau Co-authored-by: ruben_tappx Co-authored-by: rajsidhunovatiq <79534312+rajsidhunovatiq@users.noreply.github.com> Co-authored-by: novatiq <79258366+novatiq@users.noreply.github.com> Co-authored-by: kzhang-id5 <126269766+kzhang-id5@users.noreply.github.com> Co-authored-by: VisibleMeasuresHB <123548546+VisibleMeasuresHB@users.noreply.github.com> Co-authored-by: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Co-authored-by: fndigrazia Co-authored-by: aenrel <123280204+aenrel@users.noreply.github.com> Co-authored-by: julian-burger-ttd <105891200+julian-burger-ttd@users.noreply.github.com> Co-authored-by: robert.charlton Co-authored-by: robert-charlton-ttd <124640243+robert-charlton-ttd@users.noreply.github.com> Co-authored-by: geoffray-viously <95097046+geoffray-viously@users.noreply.github.com> Co-authored-by: Catalin Ciocov Co-authored-by: Faisal Islam <93644923+faisalvs@users.noreply.github.com> Co-authored-by: Faisal Islam Co-authored-by: Jozef Bartek Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: bretg Co-authored-by: Justas Pupelis Co-authored-by: Justas Pupelis Co-authored-by: Abhijit Mane <54662130+lm-abhijit@users.noreply.github.com> Co-authored-by: Love Sharma Co-authored-by: Love Sharma Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: John Ivan Bauzon Co-authored-by: John Bauzon Co-authored-by: Antonio Gargaro <38767071+AntonioGargaro@users.noreply.github.com> Co-authored-by: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Co-authored-by: Patrick McCann Co-authored-by: CleanMediaNet <46790659+CleanMediaNet@users.noreply.github.com> Co-authored-by: Moshe Moses Co-authored-by: moses Co-authored-by: theo-stv <120092078+theo-stv@users.noreply.github.com> Co-authored-by: theo_ Co-authored-by: Stanyslav Glubyshev Co-authored-by: redaguermas Co-authored-by: Reda Guermas Co-authored-by: Steffen Anders Co-authored-by: Antti Ylitepsa <89379237+AYlitepsa@users.noreply.github.com> Co-authored-by: Saveliev Taras Co-authored-by: Taras Saveliev Co-authored-by: itamaravady-browsi <123303182+itamaravady-browsi@users.noreply.github.com> Co-authored-by: johnwier <49074029+johnwier@users.noreply.github.com> Co-authored-by: johwier Co-authored-by: Malkov Mikhail Co-authored-by: Jake Miller Co-authored-by: jsfledd Co-authored-by: dani-nova <73398187+dani-nova@users.noreply.github.com> Co-authored-by: Danijel Predarski Co-authored-by: Viktor Dreiling <34981284+3link@users.noreply.github.com> Co-authored-by: Viktor Dreiling Co-authored-by: Viktor Dreiling Co-authored-by: duancg Co-authored-by: Vadim Mazzherin Co-authored-by: veranevera Co-authored-by: Elizaveta Voziyanova <44549195+h2p4x8@users.noreply.github.com> Co-authored-by: Vladimir Fedoseev Co-authored-by: Yoko OYAMA Co-authored-by: Ryo Sakuma --- README.md | 1 - integrationExamples/gpt/lemma_sample.html | 129 ++ .../gpt/neuwoRtdProvider_example.html | 18 +- .../gpt/relevadRtdProvider_example.html | 138 ++ .../gpt/x-domain/creative.html | 2 +- libraries/ortbConverter/processors/default.js | 38 +- modules/.submodules.json | 2 +- modules/adagioBidAdapter.js | 6 +- modules/adagioBidAdapter.md | 93 +- modules/adfBidAdapter.js | 113 +- modules/admediaBidAdapter.js | 98 ++ modules/admediaBidAdapter.md | 35 + modules/aduptechBidAdapter.js | 80 +- modules/alkimiBidAdapter.js | 59 +- modules/amxBidAdapter.js | 101 +- modules/appnexusBidAdapter.md | 2 +- modules/browsiBidAdapter.js | 171 +++ modules/browsiBidAdapter.md | 38 + modules/cleanmedianetBidAdapter.js | 317 ++-- modules/cleanmedianetBidAdapter.md | 82 +- modules/cointrafficBidAdapter.js | 8 +- modules/consentManagement.js | 94 +- modules/conversantAnalyticsAdapter.js | 214 ++- modules/conversantBidAdapter.js | 4 + modules/cpexIdSystem.md | 27 - modules/criteoBidAdapter.js | 31 +- .../{cpexIdSystem.js => czechAdIdSystem.js} | 14 +- modules/czechAdIdSystem.md | 27 + modules/dfpAdServerVideo.js | 16 +- modules/discoveryBidAdapter.js | 7 +- modules/distroscaleBidAdapter.js | 22 +- modules/eplanningBidAdapter.js | 16 +- modules/flocIdSystem.js | 0 modules/fluctBidAdapter.js | 41 +- modules/freewheel-sspBidAdapter.js | 39 +- modules/gridBidAdapter.js | 68 +- modules/gridNMBidAdapter.js | 28 +- modules/gumgumBidAdapter.js | 15 +- modules/hadronRtdProvider.js | 2 +- modules/id5IdSystem.js | 25 + modules/improvedigitalBidAdapter.js | 2 +- modules/ixBidAdapter.js | 2 +- modules/kargoBidAdapter.js | 4 +- modules/kueezRtbBidAdapter.js | 6 + modules/kulturemediaBidAdapter.js | 472 ++++++ modules/kulturemediaBidAdapter.md | 140 ++ modules/lemmaDigitalBidAdapter.js | 570 +++++++ modules/lemmaDigitalBidAdapter.md | 61 + modules/liveIntentIdSystem.js | 24 +- modules/magniteAnalyticsAdapter.js | 33 + modules/mediagoBidAdapter.js | 39 +- modules/minutemediaplusBidAdapter.js | 80 +- modules/missenaBidAdapter.js | 24 +- modules/neuwoRtdProvider.js | 12 +- modules/neuwoRtdProvider.md | 13 +- modules/nextMillenniumBidAdapter.js | 1 + modules/nexx360BidAdapter.js | 444 +++--- modules/nobidBidAdapter.js | 15 +- modules/nobidBidAdapter.md | 4 +- modules/novatiqIdSystem.js | 12 +- modules/optidigitalBidAdapter.js | 221 +++ modules/optidigitalBidAdapter.md | 46 + modules/permutiveRtdProvider.js | 193 ++- modules/prebidmanagerAnalyticsAdapter.js | 160 +- modules/pubmaticAnalyticsAdapter.js | 35 +- modules/pubmaticBidAdapter.js | 86 +- modules/pubwiseAnalyticsAdapter.js | 5 +- modules/pubwiseBidAdapter.js | 298 +++- modules/relevadRtdProvider.js | 365 +++++ modules/relevadRtdProvider.md | 108 ++ modules/resetdigitalBidAdapter.js | 65 +- modules/resetdigitalBidAdapter.md | 28 +- modules/rubiconBidAdapter.js | 559 +++---- modules/showheroes-bsBidAdapter.js | 7 +- modules/smaatoBidAdapter.js | 7 +- modules/smartyadsBidAdapter.js | 2 +- modules/smartytechBidAdapter.js | 9 + modules/sspBCBidAdapter.js | 421 +++--- modules/stvBidAdapter.js | 330 ++++ modules/stvBidAdapter.md | 62 + modules/tappxBidAdapter.js | 9 +- modules/tripleliftBidAdapter.js | 3 + modules/ttdBidAdapter.js | 10 + modules/ttdBidAdapter.md | 4 +- modules/underdogmediaBidAdapter.js | 354 ++++- modules/userId/eids.js | 11 +- modules/userId/userId.md | 2 +- modules/vidazooBidAdapter.js | 8 +- modules/videoModule/index.js | 8 +- modules/videojsVideoProvider.js | 6 +- modules/viouslyBidAdapter.js | 75 +- modules/visiblemeasuresBidAdapter.js | 212 +++ modules/visiblemeasuresBidAdapter.md | 79 + modules/visxBidAdapter.js | 12 +- modules/yandexBidAdapter.js | 330 +++- modules/yandexBidAdapter.md | 67 +- modules/zeta_global_sspBidAdapter.js | 23 + package-lock.json | 1347 +++++++++++------ package.json | 4 +- src/auction.js | 17 +- src/constants.json | 1 + src/fpd/enrichment.js | 63 +- src/fpd/oneClient.js | 26 + {libraries => src}/fpd/sua.js | 4 +- src/native.js | 11 +- src/utils/promise.js | 99 +- src/videoCache.js | 8 +- test/spec/auctionmanager_spec.js | 23 + test/spec/fpd/enrichment_spec.js | 173 ++- .../oneClient.js} | 7 +- test/spec/fpd/sua_spec.js | 2 +- test/spec/modules/ViouslyBidAdapter_spec.js | 98 +- test/spec/modules/adagioBidAdapter_spec.js | 52 +- test/spec/modules/adfBidAdapter_spec.js | 413 +++-- test/spec/modules/admediaBidAdapter_spec.js | 130 ++ test/spec/modules/alkimiBidAdapter_spec.js | 2 +- test/spec/modules/amxBidAdapter_spec.js | 99 +- test/spec/modules/browsiBidAdapter_spec.js | 209 +++ .../modules/cleanmedianetBidAdapter_spec.js | 869 ++++++----- .../modules/cointrafficBidAdapter_spec.js | 84 +- test/spec/modules/consentManagement_spec.js | 126 +- .../conversantAnalyticsAdapter_spec.js | 264 +++- .../spec/modules/conversantBidAdapter_spec.js | 8 + test/spec/modules/cpexIdSystem_spec.js | 18 +- test/spec/modules/criteoBidAdapter_spec.js | 19 +- test/spec/modules/dfpAdServerVideo_spec.js | 40 +- test/spec/modules/eids_spec.js | 4 +- test/spec/modules/eplanningBidAdapter_spec.js | 35 + test/spec/modules/fluctBidAdapter_spec.js | 137 +- .../modules/freewheel-sspBidAdapter_spec.js | 67 +- test/spec/modules/gridBidAdapter_spec.js | 42 +- test/spec/modules/gridNMBidAdapter_spec.js | 8 - test/spec/modules/gumgumBidAdapter_spec.js | 40 + test/spec/modules/id5IdSystem_spec.js | 49 +- .../modules/improvedigitalBidAdapter_spec.js | 14 +- test/spec/modules/ixBidAdapter_spec.js | 8 +- test/spec/modules/kargoBidAdapter_spec.js | 1 + test/spec/modules/kueezRtbBidAdapter_spec.js | 50 + .../modules/kulturemediaBidAdapter_spec.js | 613 ++++++++ .../modules/lemmaDigitalBidAdapter_spec.js | 623 ++++++++ test/spec/modules/liveIntentIdSystem_spec.js | 73 +- .../modules/magniteAnalyticsAdapter_spec.js | 32 + .../modules/minutemediaplusBidAdapter_spec.js | 137 +- test/spec/modules/missenaBidAdapter_spec.js | 10 + test/spec/modules/neuwoRtdProvider_spec.js | 3 +- test/spec/modules/nexx360BidAdapter_spec.js | 353 +++-- test/spec/modules/nobidBidAdapter_spec.js | 32 + test/spec/modules/novatiqIdSystem_spec.js | 23 +- .../modules/optidigitalBidAdapter_spec.js | 604 ++++++++ .../spec/modules/permutiveRtdProvider_spec.js | 400 ++--- .../modules/prebidServerBidAdapter_spec.js | 31 +- .../prebidmanagerAnalyticsAdapter_spec.js | 2 +- .../modules/pubmaticAnalyticsAdapter_spec.js | 57 +- test/spec/modules/pubmaticBidAdapter_spec.js | 283 ++-- test/spec/modules/pubwiseBidAdapter_spec.js | 316 +++- test/spec/modules/relevadRtdProvider_spec.js | 412 +++++ test/spec/modules/rubiconBidAdapter_spec.js | 387 ++++- .../modules/showheroes-bsBidAdapter_spec.js | 40 +- test/spec/modules/smaatoBidAdapter_spec.js | 15 + .../spec/modules/smartytechBidAdapter_spec.js | 51 +- test/spec/modules/sspBCBidAdapter_spec.js | 11 +- test/spec/modules/stvBidAdapter_spec.js | 402 +++++ .../spec/modules/tripleliftBidAdapter_spec.js | 16 +- test/spec/modules/ttdBidAdapter_spec.js | 23 + .../modules/underdogmediaBidAdapter_spec.js | 1071 ++++++++++--- test/spec/modules/userId_spec.js | 3 +- test/spec/modules/vidazooBidAdapter_spec.js | 50 + test/spec/modules/videoModule/pbVideo_spec.js | 31 + .../modules/visiblemeasuresBidAdapter_spec.js | 400 +++++ test/spec/modules/visxBidAdapter_spec.js | 2 +- test/spec/modules/yandexBidAdapter_spec.js | 360 ++++- .../modules/zeta_global_sspBidAdapter_spec.js | 29 + test/spec/native_spec.js | 316 ++-- test/spec/unit/utils/promise_spec.js | 90 +- test/spec/videoCache_spec.js | 23 + test/test_index.js | 12 + 176 files changed, 16192 insertions(+), 4214 deletions(-) create mode 100755 integrationExamples/gpt/lemma_sample.html create mode 100644 integrationExamples/gpt/relevadRtdProvider_example.html create mode 100644 modules/admediaBidAdapter.js create mode 100644 modules/admediaBidAdapter.md create mode 100644 modules/browsiBidAdapter.js create mode 100644 modules/browsiBidAdapter.md delete mode 100644 modules/cpexIdSystem.md rename modules/{cpexIdSystem.js => czechAdIdSystem.js} (82%) create mode 100644 modules/czechAdIdSystem.md delete mode 100644 modules/flocIdSystem.js create mode 100644 modules/kulturemediaBidAdapter.js create mode 100644 modules/kulturemediaBidAdapter.md create mode 100644 modules/lemmaDigitalBidAdapter.js create mode 100644 modules/lemmaDigitalBidAdapter.md create mode 100755 modules/optidigitalBidAdapter.js create mode 100755 modules/optidigitalBidAdapter.md create mode 100644 modules/relevadRtdProvider.js create mode 100644 modules/relevadRtdProvider.md create mode 100644 modules/stvBidAdapter.js create mode 100644 modules/stvBidAdapter.md create mode 100644 modules/visiblemeasuresBidAdapter.js create mode 100644 modules/visiblemeasuresBidAdapter.md create mode 100644 src/fpd/oneClient.js rename {libraries => src}/fpd/sua.js (96%) rename test/spec/{ortbConverter/default_processors_spec.js => fpd/oneClient.js} (79%) create mode 100644 test/spec/modules/admediaBidAdapter_spec.js create mode 100644 test/spec/modules/browsiBidAdapter_spec.js create mode 100644 test/spec/modules/kulturemediaBidAdapter_spec.js create mode 100644 test/spec/modules/lemmaDigitalBidAdapter_spec.js create mode 100755 test/spec/modules/optidigitalBidAdapter_spec.js create mode 100644 test/spec/modules/relevadRtdProvider_spec.js create mode 100644 test/spec/modules/stvBidAdapter_spec.js create mode 100644 test/spec/modules/visiblemeasuresBidAdapter_spec.js diff --git a/README.md b/README.md index bbc0d79ab41..fdbb5482ebf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Build Status](https://circleci.com/gh/prebid/Prebid.js.svg?style=svg)](https://circleci.com/gh/prebid/Prebid.js) [![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") [![Coverage Status](https://coveralls.io/repos/github/prebid/Prebid.js/badge.svg)](https://coveralls.io/github/prebid/Prebid.js) -[![Total Alerts](https://img.shields.io/lgtm/alerts/g/prebid/Prebid.js.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/prebid/Prebid.js/alerts/) # Prebid.js diff --git a/integrationExamples/gpt/lemma_sample.html b/integrationExamples/gpt/lemma_sample.html new file mode 100755 index 00000000000..bdf72eeb484 --- /dev/null +++ b/integrationExamples/gpt/lemma_sample.html @@ -0,0 +1,129 @@ + + + + + + + + + + + + +
+ + +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/neuwoRtdProvider_example.html b/integrationExamples/gpt/neuwoRtdProvider_example.html index f33b9f94c67..4f26bd79f3e 100644 --- a/integrationExamples/gpt/neuwoRtdProvider_example.html +++ b/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -6,9 +6,6 @@ \ No newline at end of file diff --git a/integrationExamples/gpt/relevadRtdProvider_example.html b/integrationExamples/gpt/relevadRtdProvider_example.html new file mode 100644 index 00000000000..daa6d27cf33 --- /dev/null +++ b/integrationExamples/gpt/relevadRtdProvider_example.html @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+ +
Div-2
+
+ +
+ + \ No newline at end of file diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index bea8b70b4fe..2216d0ed6ae 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -1,6 +1,6 @@ `; bid.ad = bid.ad.replace(/]*>/, match => match + s); diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 360a373ba9a..a00fd90506a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -833,7 +833,7 @@ function applyRegulations(r, bidderRequest) { if (gdprConsent.hasOwnProperty('addtlConsent') && gdprConsent.addtlConsent) { r.user.ext.consented_providers_settings = { - consented_providers: gdprConsent.addtlConsent + addtl_consent: gdprConsent.addtlConsent }; } } diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 6a1c1cf94b0..b3d5bc2af64 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -7,6 +7,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'kargo'; const HOST = 'https://krk.kargo.com'; const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}'; +const PREBID_VERSION = '$prebid.version$'; const SYNC_COUNT = 5; const GVLID = 972; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; @@ -58,7 +59,8 @@ export const spec = { width: window.screen.width, height: window.screen.height }, - prebidRawBidRequests: validBidRequests + prebidRawBidRequests: validBidRequests, + prebidVersion: PREBID_VERSION }, spec._getAllMetadata(bidderRequest, tdid)); // User Agent Client Hints / SUA diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 821b3263597..26c0d871a12 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -116,6 +116,12 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { appendUserIdsToRequestPayload(data, userId); + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { data.gdprConsent = bidderRequest.gdprConsent.consentString; diff --git a/modules/kulturemediaBidAdapter.js b/modules/kulturemediaBidAdapter.js new file mode 100644 index 00000000000..0acdd6406cb --- /dev/null +++ b/modules/kulturemediaBidAdapter.js @@ -0,0 +1,472 @@ +import { + deepSetValue, + logInfo, + deepAccess, + logError, + isFn, + isPlainObject, + isStr, + isNumber, + isArray, logMessage +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'kulturemedia'; +const DEFAULT_BID_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_NETWORK_ID = 1; +const OPENRTB_VIDEO_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity' +]; + +export const spec = { + code: BIDDER_CODE, + VERSION: '1.0.0', + supportedMediaTypes: [BANNER, VIDEO], + ENDPOINT: 'https://ads.kulture.media/pbjs', + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bidRequest The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return ( + _validateParams(bid) && + _validateBanner(bid) && + _validateVideo(bid) + ); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of bid requests which should be sent to the Server. + * @param {BidderRequest} bidderRequest bidder request object. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return; + } + + // We need to refactor this to support mixed content when there are both + // banner and video bid requests + let openrtbRequest; + if (hasBannerMediaType(validBidRequests[0])) { + openrtbRequest = buildBannerRequestData(validBidRequests, bidderRequest); + } else if (hasVideoMediaType(validBidRequests[0])) { + openrtbRequest = buildVideoRequestData(validBidRequests[0], bidderRequest); + } + + // adding schain object + if (validBidRequests[0].schain) { + deepSetValue(openrtbRequest, 'source.ext.schain', validBidRequests[0].schain); + } + + // Attaching GDPR Consent Params + if (bidderRequest.gdprConsent) { + deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest.uspConsent) { + deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // EIDS + const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); + if (Array.isArray(eids) && eids.length > 0) { + deepSetValue(openrtbRequest, 'user.ext.eids', eids); + } + + let publisherId = validBidRequests[0].params.publisherId; + let placementId = validBidRequests[0].params.placementId; + const networkId = validBidRequests[0].params.networkId || DEFAULT_NETWORK_ID; + + if (validBidRequests[0].params.e2etest) { + logMessage('E2E test mode enabled'); + publisherId = 'e2etest' + } + let baseEndpoint = spec.ENDPOINT + '?pid=' + publisherId; + + if (placementId) { + baseEndpoint += '&placementId=' + placementId + } + if (networkId) { + baseEndpoint += '&nId=' + networkId + } + + const payloadString = JSON.stringify(openrtbRequest); + return { + method: 'POST', + url: baseEndpoint, + data: payloadString, + }; + }, + + interpretResponse: function (serverResponse) { + const bidResponses = []; + const response = (serverResponse || {}).body; + // response is always one seat (exchange) with (optional) bids for each impression + if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid[0].bid.forEach(bid => { + if (bid.adm && bid.price) { + bidResponses.push(_createBidResponse(bid)); + } + }) + } else { + logInfo('kulturemedia.interpretResponse :: no valid responses to interpret'); + } + return bidResponses; + }, + + getUserSyncs: function (syncOptions, serverResponses) { + logInfo('kulturemedia.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + let syncs = []; + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return syncs; + } + + serverResponses.forEach(resp => { + const userSync = deepAccess(resp, 'body.ext.usersync'); + if (userSync) { + let syncDetails = []; + Object.keys(userSync).forEach(key => { + const value = userSync[key]; + if (value.syncs && value.syncs.length) { + syncDetails = syncDetails.concat(value.syncs); + } + }); + syncDetails.forEach(syncDetails => { + syncs.push({ + type: syncDetails.type === 'iframe' ? 'iframe' : 'image', + url: syncDetails.url + }); + }); + + if (!syncOptions.iframeEnabled) { + syncs = syncs.filter(s => s.type !== 'iframe') + } + if (!syncOptions.pixelEnabled) { + syncs = syncs.filter(s => s.type !== 'image') + } + } + }); + logInfo('kulturemedia.getUserSyncs result=%o', syncs); + return syncs; + }, + +}; + +/* ======================================= + * Util Functions + *======================================= */ + +/** + * @param {BidRequest} bidRequest bid request + */ +function hasBannerMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} + +/** + * @param {BidRequest} bidRequest bid request + */ +function hasVideoMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +function _validateParams(bidRequest) { + if (!bidRequest.params) { + return false; + } + + if (bidRequest.params.e2etest) { + return true; + } + + if (!bidRequest.params.publisherId) { + logError('Validation failed: publisherId not declared'); + return false; + } + + if (!bidRequest.params.placementId) { + logError('Validation failed: placementId not declared'); + return false; + } + + const mediaTypesExists = hasVideoMediaType(bidRequest) || hasBannerMediaType(bidRequest); + if (!mediaTypesExists) { + return false; + } + + return true; +} + +/** + * Validates banner bid request. If it is not banner media type returns true. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ +function _validateBanner(bidRequest) { + // If there's no banner no need to validate + if (!hasBannerMediaType(bidRequest)) { + return true; + } + const banner = deepAccess(bidRequest, 'mediaTypes.banner'); + if (!Array.isArray(banner.sizes)) { + return false; + } + + return true; +} + +/** + * Validates video bid request. If it is not video media type returns true. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ +function _validateVideo(bidRequest) { + // If there's no video no need to validate + if (!hasVideoMediaType(bidRequest)) { + return true; + } + + const videoPlacement = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + const params = deepAccess(bidRequest, 'params', {}); + + if (params && params.e2etest) { + return true; + } + + const videoParams = { + ...videoPlacement, + ...videoBidderParams // Bidder Specific overrides + }; + + if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { + logError('Validation failed: mimes are invalid'); + return false; + } + + if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { + logError('Validation failed: protocols are invalid'); + return false; + } + + if (!videoParams.context) { + logError('Validation failed: context id not declared'); + return false; + } + + if (videoParams.context !== 'instream') { + logError('Validation failed: only context instream is supported '); + return false; + } + + if (typeof videoParams.playerSize === 'undefined' || !Array.isArray(videoParams.playerSize) || !Array.isArray(videoParams.playerSize[0])) { + logError('Validation failed: player size not declared or is not in format [[w,h]]'); + return false; + } + + return true; +} + +/** + * Prepares video request data. + * + * @param bidRequest + * @param bidderRequest + * @returns openrtbRequest + */ +function buildVideoRequestData(bidRequest, bidderRequest) { + const {params} = bidRequest; + + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + + const videoParams = { + ...videoAdUnit, + ...videoBidderParams // Bidder Specific overrides + }; + + if (bidRequest.params && bidRequest.params.e2etest) { + videoParams.playerSize = [[640, 480]] + videoParams.conext = 'instream' + } + + const video = { + w: parseInt(videoParams.playerSize[0][0], 10), + h: parseInt(videoParams.playerSize[0][1], 10), + } + + // Obtain all ORTB params related video from Ad Unit + OPENRTB_VIDEO_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + video[param] = videoParams[param]; + } + }); + + // Placement Inference Rules: + // - If no placement is defined then default to 1 (In Stream) + video.placement = video.placement || 2; + + // - If product is instream (for instream context) then override placement to 1 + if (params.context === 'instream') { + video.startdelay = video.startdelay || 0; + video.placement = 1; + } + + // bid floor + const bidFloorRequest = { + currency: bidRequest.params.cur || 'USD', + mediaType: 'video', + size: '*' + }; + let floorData = bidRequest.params + if (isFn(bidRequest.getFloor)) { + floorData = bidRequest.getFloor(bidFloorRequest); + } else { + if (params.bidfloor) { + floorData = {floor: params.bidfloor, currency: params.currency || 'USD'}; + } + } + + const openrtbRequest = { + id: bidRequest.bidId, + imp: [ + { + id: '1', + video: video, + secure: isSecure() ? 1 : 0, + bidfloor: floorData.floor, + bidfloorcur: floorData.currency + } + ], + site: { + domain: bidderRequest.refererInfo.domain, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref, + }, + ext: { + hb: 1, + prebidver: '$prebid.version$', + adapterver: spec.VERSION, + }, + }; + + // content + if (videoParams.content && isPlainObject(videoParams.content)) { + openrtbRequest.site.content = {}; + const contentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language', 'url']; + const contentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; + const contentArrayKeys = ['cat']; + const contentObjectKeys = ['ext']; + for (const contentKey in videoBidderParams.content) { + if ( + (contentStringKeys.indexOf(contentKey) > -1 && isStr(videoParams.content[contentKey])) || + (contentNumberkeys.indexOf(contentKey) > -1 && isNumber(videoParams.content[contentKey])) || + (contentObjectKeys.indexOf(contentKey) > -1 && isPlainObject(videoParams.content[contentKey])) || + (contentArrayKeys.indexOf(contentKey) > -1 && isArray(videoParams.content[contentKey]) && + videoParams.content[contentKey].every(catStr => isStr(catStr)))) { + openrtbRequest.site.content[contentKey] = videoParams.content[contentKey]; + } else { + logMessage('KultureMedia bid adapter validation error: ', contentKey, ' is either not supported is OpenRTB V2.5 or value is undefined'); + } + } + } + + return openrtbRequest; +} + +/** + * Prepares video request data. + * + * @param bidRequest + * @param bidderRequest + * @returns openrtbRequest + */ +function buildBannerRequestData(bidRequests, bidderRequest) { + const impr = bidRequests.map(bidRequest => ({ + id: bidRequest.bidId, + banner: { + format: bidRequest.mediaTypes.banner.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1] + })) + }, + ext: { + exchange: { + placementId: bidRequest.params.placementId + } + } + })); + + const openrtbRequest = { + id: bidderRequest.auctionId, + imp: impr, + site: { + domain: bidderRequest.refererInfo?.domain, + page: bidderRequest.refererInfo?.page, + ref: bidderRequest.refererInfo?.ref, + }, + ext: {} + }; + return openrtbRequest; +} + +function _createBidResponse(bid) { + const isADomainPresent = + bid.adomain && bid.adomain.length; + const bidResponse = { + requestId: bid.impid, + bidderCode: spec.code, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: typeof bid.exp === 'number' ? bid.exp : DEFAULT_BID_TTL, + creativeId: bid.crid, + netRevenue: DEFAULT_NET_REVENUE, + currency: DEFAULT_CURRENCY, + mediaType: deepAccess(bid, 'ext.prebid.type', BANNER) + } + + if (isADomainPresent) { + bidResponse.meta = { + advertiserDomains: bid.adomain + }; + } + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastXml = bid.adm; + } + + return bidResponse; +} + +function isSecure() { + return document.location.protocol === 'https:'; +} + +registerBidder(spec); diff --git a/modules/kulturemediaBidAdapter.md b/modules/kulturemediaBidAdapter.md new file mode 100644 index 00000000000..0bd17e97982 --- /dev/null +++ b/modules/kulturemediaBidAdapter.md @@ -0,0 +1,140 @@ +# Overview + +``` +Module Name: Kulture Media Bid Adapter +Module Type: Bidder Adapter +Maintainer: devops@kulture.media +``` + +# Description + +Module that connects to Kulture Media's demand sources. +Kulture Media bid adapter supports Banner and Video. + + +# Test Parameters + +## Banner + +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'kulturemedia', + params: { + placementId: 'test', + publisherId: 'test', + } + }] + } +]; +``` + +## Video + +We support the following OpenRTB params that can be specified in `mediaTypes.video` or in `bids[].params.video` +- 'mimes', +- 'minduration', +- 'maxduration', +- 'placement', +- 'protocols', +- 'startdelay', +- 'skip', +- 'skipafter', +- 'minbitrate', +- 'maxbitrate', +- 'delivery', +- 'playbackmethod', +- 'api', +- 'linearity' + + +## Instream Video adUnit using mediaTypes.video +*Note:* By default, the adapter will read the mandatory parameters from mediaTypes.video. +*Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. +``` + var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + minduration: 10, + maxduration: 30, + placement: 1, + playbackmethod: [1,5], + } + }, + bids: [ + { + bidder: 'kulturemedia', + params: { + bidfloor: 0.5, + publisherId: '12345', + placementId: '6789' + } + } + ] + } + ] +``` + +# End To End testing mode +By passing bid.params.e2etest = true you will be able to receive a test creative + +## Banner +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'kulturemedia', + params: { + e2etest: true + } + }] + } +]; +``` + +## Video +``` +var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: "instream", + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [2,5], + } + }, + bids: [ + { + bidder: 'kulturemedia', + params: { + e2etest: true + } + } + ] + } +] +``` diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js new file mode 100644 index 00000000000..9fa3081a47e --- /dev/null +++ b/modules/lemmaDigitalBidAdapter.js @@ -0,0 +1,570 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +var BIDDER_CODE = 'lemmadigital'; +var LOG_WARN_PREFIX = 'LEMMADIGITAL: '; +var ENDPOINT = 'https://bid.lemmadigital.com/lemma/servad'; +var USER_SYNC = 'https://sync.lemmadigital.com/js/usersync.html?'; +var DEFAULT_CURRENCY = 'USD'; +var AUCTION_TYPE = 2; +var DEFAULT_TMAX = 300; +var DEFAULT_NET_REVENUE = false; +var DEFAULT_SECURE = 1; +var RESPONSE_TTL = 300; +var pubId = 0; +var adunitId = 0; + +export var spec = { + + code: BIDDER_CODE, + 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: (bid) => { + if (!bid || !bid.params) { + utils.logError(LOG_WARN_PREFIX, 'nil/empty bid object'); + return false; + } + if (!utils.isEmpty(bid.params.pubId) || !utils.isNumber(bid.params.pubId)) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be string. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + return false; + } + if (!bid.params.adunitId) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: adUnitId is mandatory. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + return false; + } + // video bid request validation + if (bid.params.hasOwnProperty('video')) { + if (!bid.params.video.hasOwnProperty('mimes') || !utils.isArray(bid.params.video.mimes) || bid.params.video.mimes.length === 0) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); + return false; + } + } + return true; + }, + + /** + * 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: (validBidRequests, bidderRequest) => { + if (validBidRequests.length === 0) { + return; + } + var refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + var conf = spec._setRefURL(refererInfo); + const request = spec._createoRTBRequest(validBidRequests, conf); + if (request && request.imp.length == 0) { + return; + } + spec._setOtherParams(bidderRequest, request); + const endPoint = spec._endPointURL(validBidRequests); + return { + method: 'POST', + url: endPoint, + data: JSON.stringify(request), + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} response A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + **/ + interpretResponse: (response, request) => { + return spec._parseRTBResponse(request, response.body); + }, + + /** + * 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: (syncOptions, serverResponses) => { + let syncurl = USER_SYNC + 'pid=' + pubId; + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: syncurl + }]; + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.'); + } + }, + + /** + * Generate UUID + */ + _createUUID: () => { + return new Date().getTime().toString(); + }, + + /** + * parse object + **/ + _parseJSON: function (rawPayload) { + try { + if (rawPayload) { + return JSON.parse(rawPayload); + } + } catch (ex) { + utils.logError(LOG_WARN_PREFIX, 'Exception: ', ex); + } + return null; + }, + + /** + * + * set referal url + */ + _setRefURL: (refererInfo) => { + var conf = {}; + conf.pageURL = (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href; + if (refererInfo && refererInfo.referer) { + conf.refURL = refererInfo.referer; + } else { + conf.refURL = ''; + } + return conf; + }, + + /** + * set other params into oRTB request + */ + _setOtherParams: (request, ortbRequest) => { + var params = request && request.params ? request.params : null; + if (params) { + ortbRequest.tmax = params.tmax; + ortbRequest.bcat = params.bcat; + } + }, + + /** + * create IAB standard OpenRTB bid request + **/ + _createoRTBRequest: (bidRequests, conf) => { + var oRTBObject = {}; + try { + oRTBObject = { + id: spec._createUUID(), + at: AUCTION_TYPE, + tmax: DEFAULT_TMAX, + cur: [DEFAULT_CURRENCY], + imp: spec._getImpressionArray(bidRequests), + user: {}, + ext: {} + }; + var bid = bidRequests[0]; + + var site = spec._getSiteObject(bid, conf); + if (site) { + oRTBObject.site = site; + // add the content object from config in request + if (typeof config.getConfig('content') === 'object') { + oRTBObject.site.content = config.getConfig('content'); + } + } + var app = spec._getAppObject(bid); + if (app) { + oRTBObject.app = app; + if (typeof oRTBObject.app.content !== 'object' && typeof config.getConfig('content') === 'object') { + oRTBObject.app.content = + config.getConfig('content') || undefined; + } + } + var device = spec._getDeviceObject(bid); + if (device) { + oRTBObject.device = device; + } + var source = spec._getSourceObject(bid); + if (source) { + oRTBObject.source = source; + } + return oRTBObject; + } catch (ex) { + utils.logError(LOG_WARN_PREFIX, 'ERROR ', ex); + } + }, + + /** + * create impression array objects + **/ + _getImpressionArray: (request) => { + var impArray = []; + var map = request.map(bid => spec._getImpressionObject(bid)); + if (map) { + map.forEach(o => { + if (o) { + impArray.push(o); + } + }); + } + return impArray; + }, + + /** + * create impression (single) object + **/ + _getImpressionObject: (bid) => { + var impression = {}; + var bObj; + var vObj; + var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : []; + var mediaTypes = ''; + var format = []; + var params = bid && bid.params ? bid.params : null; + impression = { + id: bid.bidId, + tagid: params.adunitId ? params.adunitId.toString() : undefined, + secure: DEFAULT_SECURE, + bidfloorcur: params.currency ? params.currency : DEFAULT_CURRENCY + }; + if (params.bidFloor) { + impression.bidfloor = params.bidFloor; + } + if (bid.hasOwnProperty('mediaTypes')) { + for (mediaTypes in bid.mediaTypes) { + switch (mediaTypes) { + case BANNER: + bObj = spec._getBannerRequest(bid); + if (bObj) { + impression.banner = bObj; + } + break; + case VIDEO: + vObj = spec._getVideoRequest(bid); + if (vObj) { + impression.video = vObj; + } + break; + } + } + } else { + bObj = { + pos: 0, + w: sizes && sizes[0] ? sizes[0][0] : 0, + h: sizes && sizes[0] ? sizes[0][1] : 0, + }; + if (utils.isArray(sizes) && sizes.length > 1) { + sizes = sizes.splice(1, sizes.length - 1); + sizes.forEach(size => { + format.push({ + w: size[0], + h: size[1] + }); + }); + bObj.format = format; + } + impression.banner = bObj; + } + spec._setFloor(impression, bid); + return impression.hasOwnProperty(BANNER) || + impression.hasOwnProperty(VIDEO) ? impression : undefined; + }, + + /** + * set bid floor + **/ + _setFloor: (impObj, bid) => { + let bidFloor = -1; + // get lowest floor from floorModule + if (typeof bid.getFloor === 'function') { + [BANNER, VIDEO].forEach(mediaType => { + if (impObj.hasOwnProperty(mediaType)) { + let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: '*' }); + if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { + let mediaTypeFloor = parseFloat(floorInfo.floor); + bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)); + } + } + }); + } + // get highest from impObj.bidfllor and floor from floor module + // as we are using Math.max, it is ok if we have not got any floor from floorModule, then value of bidFloor will be -1 + if (impObj.bidfloor) { + bidFloor = Math.max(bidFloor, impObj.bidfloor); + } + + // assign value only if bidFloor is > 0 + impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : undefined); + }, + + /** + * parse Open RTB response + **/ + _parseRTBResponse: (request, response) => { + var bidResponses = []; + try { + if (response.seatbid) { + var currency = response.curr || DEFAULT_CURRENCY; + var seatbid = response.seatbid; + seatbid.forEach(seatbidder => { + var bidder = seatbidder.bid; + bidder.forEach(bid => { + var req = spec._parseJSON(request.data); + var newBid = { + requestId: bid.impid, + cpm: parseFloat(bid.price).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid, + currency: currency, + netRevenue: DEFAULT_NET_REVENUE, + ttl: RESPONSE_TTL, + referrer: req.site.ref, + ad: bid.adm + }; + if (bid.dealid) { + newBid.dealId = bid.dealid; + } + if (req.imp && req.imp.length > 0) { + req.imp.forEach(robj => { + if (bid.impid === robj.id) { + spec._checkMediaType(bid.adm, newBid); + switch (newBid.mediaType) { + case BANNER: + break; + case VIDEO: + newBid.width = bid.hasOwnProperty('w') ? bid.w : robj.video.w; + newBid.height = bid.hasOwnProperty('h') ? bid.h : robj.video.h; + newBid.vastXml = bid.adm; + break; + } + } + }); + } + bidResponses.push(newBid); + }); + }); + } + } catch (error) { + utils.logError(LOG_WARN_PREFIX, 'ERROR ', error); + } + return bidResponses; + }, + + /** + * get bid request api end point url + **/ + _endPointURL: (request) => { + var params = request && request[0].params ? request[0].params : null; + if (params) { + pubId = params.pubId ? params.pubId : 0; + adunitId = params.adunitId ? params.adunitId : 0; + return ENDPOINT + '?pid=' + pubId + '&aid=' + adunitId; + } + return null; + }, + + /** + * get domain name from url + **/ + _getDomain: (url) => { + var a = document.createElement('a'); + a.setAttribute('href', url); + return a.hostname; + }, + + /** + * create the site object + **/ + _getSiteObject: (request, conf) => { + var params = request && request.params ? request.params : null; + if (params) { + pubId = params.pubId ? params.pubId : '0'; + var siteId = params.siteId ? params.siteId : '0'; + var appParams = params.app; + if (!appParams) { + return { + publisher: { + id: pubId.toString() + }, + domain: spec._getDomain(conf.pageURL), + id: siteId.toString(), + ref: conf.refURL, + page: conf.pageURL, + cat: params.category, + pagecat: params.page_category + }; + } + } + return null; + }, + + /** + * create the app object + **/ + _getAppObject: (request) => { + var params = request && request.params ? request.params : null; + if (params) { + pubId = params.pubId ? params.pubId : 0; + var appParams = params.app; + if (appParams) { + return { + publisher: { + id: pubId.toString(), + }, + id: appParams.id, + name: appParams.name, + bundle: appParams.bundle, + storeurl: appParams.storeUrl, + domain: appParams.domain, + cat: appParams.cat || params.category, + pagecat: appParams.pagecat || params.page_category + }; + } + } + return null; + }, + + /** + * create the device object + **/ + _getDeviceObject: (request) => { + var params = request && request.params ? request.params : null; + if (params) { + return { + dnt: utils.getDNT() ? 1 : 0, + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + w: (window.screen.width || window.innerWidth), + h: (window.screen.height || window.innerHeigh), + geo: { + country: params.country, + lat: params.latitude, + lon: params.longitude, + accuracy: params.accuracy, + region: params.region, + city: params.city, + zip: params.zip + }, + ip: params.ip, + make: params.make, + model: params.model, + os: params.os, + carrier: params.carrier, + devicetype: params.device_type, + ifa: params.ifa, + }; + } + return null; + }, + + /** + * create source object + */ + _getSourceObject: (request) => { + var params = request && request.params ? request.params : null; + if (params) { + return { + pchain: params.pchain, + ext: { + schain: request.schain + }, + }; + } + return null; + }, + + /** + * get request ad sizes + **/ + _getSizes: (request) => { + if (request && request.sizes && utils.isArray(request.sizes[0]) && request.sizes[0].length > 0) { + return request.sizes[0]; + } + return null; + }, + + /** + * create the banner object + **/ + _getBannerRequest: (bid) => { + var bObj; + var adFormat = []; + if (utils.deepAccess(bid, 'mediaTypes.banner')) { + var params = bid ? bid.params : null; + var bannerData = params && params.banner; + var sizes = spec._getSizes(bid) || []; + if (sizes && sizes.length == 0) { + sizes = bid.mediaTypes.banner.sizes[0]; + } + if (sizes && sizes.length > 0) { + bObj = {}; + bObj.w = sizes[0]; + bObj.h = sizes[1]; + bObj.pos = 0; + if (bannerData) { + bObj = utils.deepClone(bannerData); + } + sizes = bid.mediaTypes.banner.sizes; + if (sizes.length > 0) { + adFormat = []; + sizes.forEach(function (size) { + if (size.length > 1) { + adFormat.push({ w: size[0], h: size[1] }); + } + }); + if (adFormat.length > 0) { + bObj.format = adFormat; + } + } + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.sizes missing for adunit: ' + bid.params.adunitId); + } + } + return bObj; + }, + + /** + * create the video object + **/ + _getVideoRequest: (bid) => { + var vObj; + if (utils.deepAccess(bid, 'mediaTypes.video')) { + var params = bid ? bid.params : null; + var videoData = utils.mergeDeep(utils.deepAccess(bid.mediaTypes, 'video'), params.video); + var sizes = bid.mediaTypes.video ? bid.mediaTypes.video.playerSize : [] + if (sizes && sizes.length > 0) { + vObj = {}; + if (videoData) { + vObj = utils.deepClone(videoData); + } + vObj.w = sizes[0]; + vObj.h = sizes[1]; + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.video.sizes missing for adunit: ' + bid.params.adunitId); + } + } + return vObj; + }, + + /** + * check media type + **/ + _checkMediaType: (adm, newBid) => { + // Create a regex here to check the strings + var videoRegex = new RegExp(/VAST.*version/); + if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + newBid.mediaType = BANNER; + } + } +}; + +registerBidder(spec); diff --git a/modules/lemmaDigitalBidAdapter.md b/modules/lemmaDigitalBidAdapter.md new file mode 100644 index 00000000000..5a22a7588da --- /dev/null +++ b/modules/lemmaDigitalBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +``` +Module Name: Lemmadigital Bid Adapter +Module Type: Bidder Adapter +Maintainer: lemmadev@lemmatechnologies.com +``` + +# Description + +Connects to Lemma exchange for bids. +Lemmadigital bid adapter supports Video, Banner formats. + +# Sample Banner Ad Unit: For Publishers +``` +var adUnits = [{ + code: 'div-lemma-ad-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], // required + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'lemmadigital', + params: { + pubId: 1, // required + adunitId: '3768', // required + latitude: 37.3230, + longitude: -122.0322, + device_type: 2 + } + }] +}]; +``` + +# Sample Video Ad Unit: For Publishers +``` +var adUnits = [{ + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'instream' + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'lemmadigital', + params: { + pubId: 1, // required + adunitId: '3769', // required + latitude: 37.3230, + longitude: -122.0322, + device_type: 4, + video: { + mimes: ['video/mp4','video/x-flv'], // required + } + } + }] +}]; +``` diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 9f45daeea29..4fd2a80afd0 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -11,6 +11,7 @@ import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/val import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; +const EVENTS_TOPIC = 'pre_lips' const MODULE_NAME = 'liveIntentId'; export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); const defaultRequestedAttributes = {'nonId': true} @@ -39,14 +40,22 @@ let liveConnect = null; * This function is used in tests */ export function reset() { - if (window && window.liQ) { - window.liQ = []; + if (window && window.liQ_instances) { + window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)) + window.liQ_instances = []; } liveIntentIdSubmodule.setModuleMode(null) eventFired = false; liveConnect = null; } +/** + * This function is also used in tests + */ +export function setEventFiredFlag() { + eventFired = true; +} + function parseLiveIntentCollectorConfig(collectConfig) { const config = {}; collectConfig = collectConfig || {} @@ -100,6 +109,7 @@ function initializeLiveConnect(configParams) { liveConnectConfig.wrapperName = 'prebid'; liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; + liveConnectConfig.fireEventDelay = configParams.fireEventDelay; const usPrivacyString = uspDataHandler.getConsentData(); if (usPrivacyString) { liveConnectConfig.usPrivacyString = usPrivacyString; @@ -121,8 +131,14 @@ function initializeLiveConnect(configParams) { function tryFireEvent() { if (!eventFired && liveConnect) { - liveConnect.fire(); - eventFired = true; + const eventDelay = liveConnect.config.fireEventDelay || 500 + setTimeout(() => { + const instances = window.liQ_instances + instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)) + if (!eventFired && liveConnect) { + liveConnect.fire(); + } + }, eventDelay) } } diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index c6911897e84..9d437c0b246 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -305,6 +305,10 @@ const getTopLevelDetails = () => { } } + if (browser) { + deepSetValue(payload, rubiConf.pbaBrowserLocation || 'client.browser', browser); + } + // Add DM wrapper details if (rubiConf.wrapperName) { payload.wrapper = { @@ -595,6 +599,28 @@ const subscribeToGamSlots = () => { }); } +/** + * Lazy parsing of UA to determine browser + * @param {string} userAgent string from prebid ortb ua or navigator + * @returns {string} lazily guessed browser name + */ +export const detectBrowserFromUa = userAgent => { + let normalizedUa = userAgent.toLowerCase(); + + if (normalizedUa.includes('edg')) { + return 'Edge'; + } else if ((/opr|opera|opt/i).test(normalizedUa)) { + return 'Opera'; + } else if ((/chrome|crios/i).test(normalizedUa)) { + return 'Chrome'; + } else if ((/fxios|firefox/i).test(normalizedUa)) { + return 'Firefox'; + } else if (normalizedUa.includes('safari') && !(/chromium|ucbrowser/i).test(normalizedUa)) { + return 'Safari'; + } + return 'OTHER'; +} + let accountId; let endpoint; @@ -656,6 +682,7 @@ magniteAdapter.onDataDeletionRequest = function () { magniteAdapter.MODULE_INITIALIZED_TIME = Date.now(); magniteAdapter.referrerHostname = ''; +let browser; magniteAdapter.track = ({ eventType, args }) => { switch (eventType) { case AUCTION_INIT: @@ -675,6 +702,12 @@ magniteAdapter.track = ({ eventType, args }) => { ]); auctionData.accountId = accountId; + // get browser + if (!browser) { + const userAgent = deepAccess(args, 'bidderRequests.0.ortb2.device.ua', navigator.userAgent) || ''; + browser = detectBrowserFromUa(userAgent); + } + // Order bidders were called auctionData.bidderOrder = args.bidderRequests.map(bidderRequest => bidderRequest.bidderCode); diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 9b1b02e00a2..d807c9b801e 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -19,38 +19,6 @@ const storage = getStorageManager(); let globals = {}; let itemMaps = {}; -/** - * 获取随机id - * @param {number} a random number from 0 to 15 - * @return {string} random number or random string - */ -// function getRandomId( -// a // placeholder -// ) { -// // if the placeholder was passed, return -// // a random number from 0 to 15 -// return a -// ? ( -// a ^ // unless b is 8, -// ((Math.random() * // in which case -// 16) >> // a random number from -// (a / 4)) -// ) // 8 to 11 -// .toString(16) // in hexadecimal -// : ( // or otherwise a concatenated string: -// [1e7] + // 10000000 + -// 1e3 + // -1000 + -// 4e3 + // -4000 + -// 8e3 + // -80000000 + -// 1e11 -// ) // -100000000000, -// .replace( -// // replacing -// /[018]/g, // zeroes, ones, and eights with -// getRandomId // random hex digits -// ); -// } - /* ----- mguid:start ------ */ const COOKIE_KEY_MGUID = '__mguid_'; @@ -247,7 +215,9 @@ function getItems(validBidRequests, bidderRequest) { } } if (!matchSize) { - return {}; + matchSize = sizes[0] + ? { h: sizes[0].height || 0, w: sizes[0].width || 0 } + : { h: 0, w: 0 }; } const bidFloor = getBidFloor(req); @@ -267,6 +237,7 @@ function getItems(validBidRequests, bidderRequest) { h: matchSize.h, w: matchSize.w, pos: 1, + format: sizes, }, ext: { // gpid: gpid, // 加入后无法返回广告 @@ -310,6 +281,7 @@ function getParam(validBidRequests, bidderRequest) { const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); const timeout = bidderRequest.timeout || 2000; + const firstPartyData = bidderRequest.ortb2; if (items && items.length) { let c = { @@ -330,6 +302,7 @@ function getParam(validBidRequests, bidderRequest) { }, ext: { eids, + firstPartyData, }, user: { buyeruid: getUserID(), diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js index 578db846289..69b2387d053 100644 --- a/modules/minutemediaplusBidAdapter.js +++ b/modules/minutemediaplusBidAdapter.js @@ -1,7 +1,8 @@ -import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {config} from '../src/config.js'; const GVLID = 918; const DEFAULT_SUB_DOMAIN = 'exchange'; @@ -22,11 +23,11 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; -const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { - const parsedUrl = parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -54,9 +55,22 @@ function isBidRequestValid(bid) { return !!(extractCID(params) && extractPID(params)); } -function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; - let { bidFloor, ext } = params; +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + auctionId, + transactionId, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + let {bidFloor, ext} = params; const hashUrl = hashCode(topWindowUrl); const uniqueDealId = getUniqueDealId(hashUrl); const cId = extractCID(params); @@ -90,11 +104,24 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { prebidVersion: '$prebid.version$', res: `${screen.width}x${screen.height}`, schain: schain, - mediaTypes: mediaTypes + mediaTypes: mediaTypes, + auctionId: auctionId, + transactionId: transactionId, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout }; appendUserIdsToRequestPayload(data, userId); + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { data.gdprConsent = bidderRequest.gdprConsent.consentString; @@ -107,6 +134,14 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { data.usPrivacy = bidderRequest.uspConsent; } + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + const dto = { method: 'POST', url: `${createDomain(subDomain)}/prebid/multi/${cId}`, @@ -148,10 +183,11 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { function buildRequests(validBidRequests, bidderRequest) { const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); requests.push(request); }); return requests; @@ -161,14 +197,14 @@ function interpretResponse(serverResponse, request) { if (!serverResponse || !serverResponse.body) { return []; } - const { bidId } = request.data; - const { results } = serverResponse.body; + const {bidId} = request.data; + const {results} = serverResponse.body; let output = []; try { results.forEach(result => { - const { creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER } = result; + const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -207,8 +243,8 @@ function interpretResponse(serverResponse, request) { function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { let syncs = []; - const { iframeEnabled, pixelEnabled } = syncOptions; - const { gdprApplies, consentString = '' } = gdprConsent; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` @@ -232,7 +268,9 @@ export function hashCode(s, prefix = '_') { let h = 0 let i = 0; if (l > 0) { - while (i < l) { h = (h << 5) - h + s.charCodeAt(i++) | 0; } + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } } return prefix + h; } @@ -256,7 +294,8 @@ export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { export function getStorageItem(key) { try { return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { } + } catch (e) { + } return null; } @@ -264,9 +303,10 @@ export function getStorageItem(key) { export function setStorageItem(key, value, timestamp) { try { const created = timestamp || Date.now(); - const data = JSON.stringify({ value, created }); + const data = JSON.stringify({value, created}); storage.setDataInLocalStorage(key, data); - } catch (e) { } + } catch (e) { + } } export function tryParseJSON(value) { diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 2ec9d39fc5d..f461440c5a1 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -1,9 +1,11 @@ -import { formatQS, logInfo } from '../src/utils.js'; +import { buildUrl, formatQS, logInfo, triggerPixel } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'missena'; const ENDPOINT_URL = 'https://bid.missena.io/'; +const EVENTS_DOMAIN = 'events.missena.io'; +const EVENTS_DOMAIN_DEV = 'events.staging.missena.xyz'; export const spec = { aliases: [BIDDER_CODE], @@ -30,6 +32,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map((bidRequest) => { const payload = { + adunit: bidRequest.adUnitCode, request_id: bidRequest.bidId, timeout: bidderRequest.timeout, }; @@ -48,6 +51,16 @@ export const spec = { if (bidRequest.params.test) { payload.test = bidRequest.params.test; } + if (bidRequest.params.placement) { + payload.placement = bidRequest.params.placement; + } + if (bidRequest.params.formats) { + payload.formats = bidRequest.params.formats; + } + if (bidRequest.params.isInternal) { + payload.is_internal = bidRequest.params.isInternal; + } + payload.userEids = bidRequest.userIdAsEids || []; return { method: 'POST', url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), @@ -109,6 +122,15 @@ export const spec = { * @param {Bid} The bid that won the auction */ onBidWon: function (bid) { + const hostname = bid.params[0].baseUrl ? EVENTS_DOMAIN_DEV : EVENTS_DOMAIN; + triggerPixel( + buildUrl({ + protocol: 'https', + hostname, + pathname: '/v1/bidsuccess', + search: { t: bid.params[0].apiKey, provider: bid.meta?.networkName, cpm: bid.cpm, currency: bid.currency }, + }) + ); logInfo('Missena - Bid won', bid); }, }; diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index a2549a243d1..01e827c00fe 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -11,13 +11,17 @@ const CATTAX_IAB = 6 // IAB Tech Lab Content Taxonomy 2.2 const RESPONSE_IAB_TIER_1 = 'marketing_categories.iab_tier_1' const RESPONSE_IAB_TIER_2 = 'marketing_categories.iab_tier_2' -function init(config = {}, userConsent = '') { +function init(config = {}, userConsent) { config.params = config.params || {} // ignore module if publicToken is missing (module setup failure) if (!config.params.publicToken) { logError('publicToken missing', 'NeuwoRTDModule', 'config.params.publicToken') return false; } + if (!config.params.apiUrl) { + logError('apiUrl missing', 'NeuwoRTDModule', 'config.params.apiUrl') + return false; + } return true; } @@ -26,9 +30,11 @@ export function getBidRequestData(reqBidsConfigObj, callback, config, userConsen logInfo('NeuwoRTDModule', 'starting getBidRequestData') const wrappedArgUrl = encodeURIComponent(config.params.argUrl || getRefererInfo().page); - const url = 'https://m1apidev.neuwo.ai/edge/GetAiTopics?' + [ + + /* adjust for pages api.url?prefix=test (to add params with '&') as well as api.url (to add params with '?') */ + const joiner = config.params.apiUrl.indexOf('?') < 0 ? '?' : '&' + const url = config.params.apiUrl + joiner + [ 'token=' + config.params.publicToken, - 'lang=en', 'url=' + wrappedArgUrl ].join('&') const billingId = generateUUID(); diff --git a/modules/neuwoRtdProvider.md b/modules/neuwoRtdProvider.md index 3552a018563..2adead66d4e 100644 --- a/modules/neuwoRtdProvider.md +++ b/modules/neuwoRtdProvider.md @@ -6,7 +6,15 @@ Maintainer: neuwo.ai # Description -Real-time data provider for Neuwo AI. Contact neuwo.ai [https://neuwo.ai/contact-us] for more information. +The Neuwo AI RTD module is an advanced AI solution for real-time data processing in the field of contextual targeting and advertising. With its cutting-edge algorithms, it allows advertisers to target their audiences with the highest level of precision based on context, while also delivering a seamless user experience. + +The module provides advertiser with valuable insights and real-time contextual bidding capabilities. Whether you're a seasoned advertising professional or just starting out, Neuwo AI RTD module is the ultimate tool for contextual targeting and advertising. + +The benefit of Neuwo AI RTD module is that it provides an alternative solution for advertisers to target their audiences and deliver relevant advertisements, as the widespread use of cookies for tracking and targeting is becoming increasingly limited. + +The RTD module uses cutting-edge algorithms to process real-time data, allowing advertisers to target their audiences based on contextual information, such as segments, IAB Tiers and brand safety. The RTD module is designed to be flexible and scalable, making it an ideal solution for advertisers looking to stay ahead of the curve in the post-cookie era. + +Generate your token at: [https://neuwo.ai/generatetoken/] # Configuration @@ -15,7 +23,8 @@ Real-time data provider for Neuwo AI. Contact neuwo.ai [https://neuwo.ai/contact const neuwoDataProvider = { name: 'NeuwoRTDModule', params: { - publicToken: '' + publicToken: '', + apiUrl: '' } } pbjs.setConfig({realTimeData: { dataProviders: [ neuwoDataProvider ]}}) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 6abb369522b..6f9385094e7 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -88,6 +88,7 @@ export const spec = { }; const imp = { + id: bid.adUnitCode, ext: { prebid: { storedrequest: {id} diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 397196ee7a9..a1971f4f9a5 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -1,252 +1,177 @@ import {config} from '../src/config.js'; -import * as utils from '../src/utils.js'; +import { deepAccess, deepSetValue, generateUUID, logError, logInfo } from '../src/utils.js'; +import {Renderer} from '../src/Renderer.js'; +import {getStorageManager} from '../src/storageManager.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import { INSTREAM, OUTSTREAM } from '../src/video.js'; + +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', 'delivery', - 'startdelay', 'skip', 'playbackmethod', 'api', 'protocol', 'boxingallowed', 'maxextended', - 'linearity', 'delivery', 'protocols', 'placement', 'minbitrate', 'maxbitrate', 'battr', 'ext']; const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; - -const PAGE_VIEW_ID = utils.generateUUID(); - -const BIDDER_VERSION = '1.0'; - +const PAGE_VIEW_ID = generateUUID(); +const BIDDER_VERSION = '2.0'; const GVLID = 965; +const NEXXID_KEY = 'nexx360_storage'; -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - aliases: [ - { code: 'revenuemaker' }, - { code: 'first-id', gvlid: 1178 }, - { code: 'adwebone' }, - { code: 'league-m', gvlid: 965 } - ], - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs, -}; +const ALIASES = [ + { code: 'revenuemaker' }, + { code: 'first-id', gvlid: 1178 }, + { code: 'adwebone' }, + { code: 'league-m', gvlid: 965 } +]; -registerBidder(spec); - -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(bid) { - return !!bid.params.tagId || !!bid.params.videoTagId; -}; +export const storage = getStorageManager({ + gvlid: GVLID, + bidderCode: BIDDER_CODE, +}); /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Get the NexxId + * @param + * @return {object | false } false if localstorageNotEnabled + */ -function buildRequests(bids, bidderRequest) { - const data = getBaseRequest(bids[0], bidderRequest); - bids.forEach((bid) => { - const impObject = createImpObject(bid); - if (isBannerBid(bid)) impObject.banner = getBannerObject(bid); - if (isVideoBid(bid)) impObject.video = getVideoObject(bid); - data.imp.push(impObject); - }); - return { - method: 'POST', - url: REQUEST_URL, - data, +export function getNexx360LocalStorage() { + if (!storage.localStorageIsEnabled()) { + logInfo(`localstorage not enabled for Nexx360`); + return false; } -} - -function createImpObject(bid) { - const floor = getFloor(bid, BANNER); - const imp = { - id: bid.bidId, - tagid: bid.adUnitCode, - ext: { - divId: bid.adUnitCode, - nexx360: { - videoTagId: bid.params.videoTagId, - tagId: bid.params.tagId, - allBids: bid.params.allBids === true, - } - } - }; - enrichImp(imp, bid, floor); - return imp; -} - -function getBannerObject(bid) { - return { - format: toFormat(bid.mediaTypes.banner.sizes), - topframe: utils.inIframe() ? 0 : 1 - }; -} - -function getVideoObject(bid) { - let width, height; - const videoParams = utils.deepAccess(bid, `mediaTypes.video`); - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - // normalize config for video size - if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { - width = parseInt(bid.sizes[0], 10); - height = parseInt(bid.sizes[1], 10); - } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { - width = parseInt(bid.sizes[0][0], 10); - height = parseInt(bid.sizes[0][1], 10); - } else if (utils.isArray(playerSize) && playerSize.length === 2) { - width = parseInt(playerSize[0], 10); - height = parseInt(playerSize[1], 10); + const output = storage.getDataFromLocalStorage(NEXXID_KEY); + if (output === null) { + const nexx360Storage = { nexx360Id: generateUUID() }; + storage.setDataInLocalStorage(NEXXID_KEY, JSON.stringify(nexx360Storage)); + return nexx360Storage; } - const video = { - playerSize: [height, width], - context, - }; - - Object.keys(videoParams) - .filter(param => VIDEO_TARGETING.includes(param)) - .forEach(param => video[param] = videoParams[param]); - return video; -} - -function isVideoBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.video'); -} - -function isBannerBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - -function toFormat(sizes) { - return sizes.map((s) => { - return { w: s[0], h: s[1] }; - }); -} - -function getFloor(bid, mediaType) { - let floor = 0; - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: mediaType, - size: '*' - }); - - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); - } + try { + return JSON.parse(output) + } catch (e) { + return false; } - - return floor; } -function enrichImp(imp, bid, floor) { - if (floor > 0) { - imp.bidfloor = floor; - imp.bidfloorcur = 'USD'; - } else if (bid.params.customFloor) { - imp.bidfloor = bid.params.customFloor; - } - if (bid.ortb2Imp && bid.ortb2Imp.ext && bid.ortb2Imp.ext.data) { - imp.ext.data = bid.ortb2Imp.ext.data; +function getAdContainer(container) { + if (document.getElementById(container)) { + return document.getElementById(container); } } -function getBaseRequest(bid, bidderRequest) { - let req = { - id: bidderRequest.auctionId, - imp: [], - cur: [config.getConfig('currency.adServerCurrency') || 'USD'], - at: 1, - tmax: config.getConfig('bidderTimeout'), - site: { - page: bidderRequest.refererInfo.topmostLocation || bidderRequest.refererInfo.page, - domain: bidderRequest.refererInfo.domain, - }, - regs: { - coppa: (config.getConfig('coppa') === true || bid.params.coppa) ? 1 : 0, - }, - device: { - dnt: (utils.getDNT() || bid.params.doNotTrack) ? 1 : 0, - h: screen.height, - w: screen.width, - ua: window.navigator.userAgent, - language: window.navigator.language.split('-').shift() - }, - user: {}, - ext: { - source: 'prebid.js', - version: '$prebid.version$', - pageViewId: PAGE_VIEW_ID, - bidderVersion: BIDDER_VERSION, - } - }; - - if (bid.params.platform) { - utils.deepSetValue(req, 'ext.platform', bid.params.platform); - } - if (bid.params.response_template_name) { - utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); - } - req.test = config.getConfig('debug') ? 1 : 0; - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - utils.deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0); - } - if (bidderRequest.gdprConsent.consentString !== undefined) { - utils.deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); +const converter = ortbConverter({ + context: { + netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false + ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + }, + imp(buildImp, bidRequest, context) { + // console.log(bidRequest, context); + const imp = buildImp(bidRequest, context); + const tagid = bidRequest.params.adUnitName ? bidRequest.params.adUnitName : bidRequest.adUnitCode; + deepSetValue(imp, 'tagid', tagid); + deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); + const divId = bidRequest.params.divId ? bidRequest.params.divId : bidRequest.adUnitCode; + deepSetValue(imp, 'ext.divId', divId); + const slotEl = getAdContainer(divId); + if (slotEl) { + deepSetValue(imp, 'ext.dimensions.slotW', slotEl.offsetWidth); + deepSetValue(imp, 'ext.dimensions.slotH', slotEl.offsetHeight); + deepSetValue(imp, 'ext.dimensions.cssMaxW', slotEl.style?.maxWidth); + deepSetValue(imp, 'ext.dimensions.cssMaxH', slotEl.style?.maxHeight); } - if (bidderRequest.gdprConsent.addtlConsent !== undefined) { - utils.deepSetValue(req, 'user.ext.ConsentedProvidersSettings.consented_providers', bidderRequest.gdprConsent.addtlConsent); + deepSetValue(imp, 'ext.nexx360', bidRequest.params.tagId); + deepSetValue(imp, 'ext.nexx360.tagId', bidRequest.params.tagId); + deepSetValue(imp, 'ext.nexx360.videoTagId', bidRequest.params.videoTagId); + deepSetValue(imp, 'ext.nexx360.allBids', bidRequest.params.allBids); + if (imp.video) { + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + deepSetValue(imp, 'video.ext.playerSize', playerSize); + deepSetValue(imp, 'video.ext.context', videoContext); } + + if (bidRequest.params.adUnitName) deepSetValue(imp, 'ext.adUnitName', bidRequest.params.adUnitName); + if (bidRequest.params.adUnitPath) deepSetValue(imp, 'ext.adUnitPath', bidRequest.params.adUnitPath); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const nexx360LocalStorage = getNexx360LocalStorage(); + if (nexx360LocalStorage) deepSetValue(request, 'ext.nexx360Id', nexx360LocalStorage.nexx360Id); + deepSetValue(request, 'ext.version', '$prebid.version$'); + deepSetValue(request, 'ext.source', 'prebid.js'); + deepSetValue(request, 'ext.pageViewId', PAGE_VIEW_ID); + deepSetValue(request, 'ext.bidderVersion', BIDDER_VERSION); + deepSetValue(request, 'cur', [config.getConfig('currency.adServerCurrency') || 'USD']); + if (!request.user) deepSetValue(request, 'user', {}); + return request; + }, +}); + +/** + * 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. + */ +function isBidRequestValid(bid) { + if (bid.params.adUnitName && (typeof bid.params.adUnitName !== 'string' || bid.params.adUnitName === '')) { + logError('bid.params.adUnitName needs to be a string'); + return false; } - if (bidderRequest.uspConsent) { - utils.deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + if (bid.params.adUnitPath && (typeof bid.params.adUnitPath !== 'string' || bid.params.adUnitPath === '')) { + logError('bid.params.adUnitPath needs to be a string'); + return false; } - if (bid.schain) { - utils.deepSetValue(req, 'source.ext.schain', bid.schain); + if (bid.params.divId && (typeof bid.params.divId !== 'string' || bid.params.divId === '')) { + logError('bid.params.divId needs to be a string'); + return false; } - if (bid.userIdAsEids) { - utils.deepSetValue(req, 'user.ext.eids', bid.userIdAsEids); + if (bid.params.allBids && typeof bid.params.allBids !== 'boolean') { + logError('bid.params.allBids needs to be a boolean'); + return false; } - const commonFpd = bidderRequest.ortb2 || {}; - if (commonFpd.site) { - utils.mergeDeep(req, {site: commonFpd.site}); + if (!bid.params.tagId && !bid.params.videoTagId && !bid.params.nativeTagId) { + logError('bid.params.tagId or bid.params.videoTagId or bid.params.nativeTagId must be defined'); + return false; } - if (commonFpd.user) { - utils.mergeDeep(req, {user: commonFpd.user}); + return true; +}; + +/** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + +function buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}) + return { + method: 'POST', + url: REQUEST_URL, + data, } - return req; } /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function interpretResponse(response, req) { - const { bidderSettings } = getGlobal(); - const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; - const respBody = response.body; + * 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. + */ + +function interpretResponse(serverResponse) { + const respBody = serverResponse.body; if (!respBody || !Array.isArray(respBody.seatbid)) { return []; } + const { bidderSettings } = getGlobal(); + const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; + let bids = []; respBody.seatbid.forEach(seatbid => { - const ssp = seatbid.seat; bids = [...bids, ...seatbid.bid.map(bid => { const response = { requestId: bid.impid, @@ -255,27 +180,30 @@ function interpretResponse(response, req) { height: bid.h, creativeId: bid.crid, dealId: bid.dealid, - currency: respBody.cur || 'USD', + currency: respBody.cur, netRevenue: true, ttl: 120, - mediaType: bid.type === 'banner' ? 'banner' : 'video', + mediaType: [OUTSTREAM, INSTREAM].includes(bid.ext.mediaType) ? 'video' : bid.ext.mediaType, meta: { advertiserDomains: bid.adomain, - demandSource: ssp, + demandSource: bid.ext.ssp, }, }; - if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ssp}`; - - if (response.mediaType === 'banner') { - response.adUrl = bid.adUrl; - } + if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ext.ssp}`; - if (['instream', 'outstream'].includes(bid.type)) response.vastXml = bid.vastXml; + if (bid.ext.mediaType === BANNER) response.adUrl = bid.ext.adUrl; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType)) response.vastXml = bid.ext.vastXml; - if (bid.ext) { - response.meta.networkId = bid.ext.dsp_id; - response.meta.advertiserId = bid.ext.buyer_id; - response.meta.brandId = bid.ext.brand_id; + if (bid.ext.mediaType === OUTSTREAM) { + response.renderer = createRenderer(bid, OUTSTREAM_RENDERER_URL); + response.divId = bid.ext.divId + }; + if (bid.ext.mediaType === NATIVE) { + try { + response.native = { + ortb: JSON.parse(bid.adm) + } + } catch (e) {} } return response; })]; @@ -284,17 +212,65 @@ function interpretResponse(response, req) { } /** - * 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. - */ + * 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. + */ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && - serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { - return serverResponses[0].body.cookies.slice(0, 5); + if (typeof serverResponses === 'object' && + serverResponses != null && + serverResponses.length > 0 && + serverResponses[0].hasOwnProperty('body') && + serverResponses[0].body.hasOwnProperty('ext') && + serverResponses[0].body.ext.hasOwnProperty('cookies') && + typeof serverResponses[0].body.ext.cookies === 'object') { + return serverResponses[0].body.ext.cookies.slice(0, 5); } else { return []; } }; + +function outstreamRender(response) { + response.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [response.width, response.height], + targetId: response.divId, + adResponse: response.vastXml, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: response.vastXml + } + }); + }); +} + +function createRenderer(bid, url) { + const renderer = Renderer.install({ + id: bid.id, + url: url, + loaded: false, + adUnitCode: bid.ext.adUnitCode, + targetId: bid.ext.divId, + }); + renderer.setRender(outstreamRender); + return renderer; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: ALIASES, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 20878b545e4..6f6cbec3400 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -8,7 +8,7 @@ import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); -window.nobidVersion = '1.3.2'; +window.nobidVersion = '1.3.3'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; window.nobid.timeoutTotal = 0; @@ -175,6 +175,9 @@ function nobidBuildRequests(bids, bidderRequest) { if (adunitObject.div) { a.d = adunitObject.div; } + if (adunitObject.floor) { + a.floor = adunitObject.floor; + } if (adunitObject.targeting) { a.g = adunitObject.targeting; } else { @@ -201,6 +204,12 @@ function nobidBuildRequests(bids, bidderRequest) { adunits.push(a); return adunits; } + function getFloor (bid) { + if (bid && typeof bid.getFloor === 'function' && bid.getFloor().floor) { + return bid.getFloor().floor; + } + return null; + } if (typeof window.nobid.refreshLimit !== 'undefined') { if (window.nobid.refreshLimit < window.nobid.refreshCount) return false; } @@ -227,6 +236,7 @@ function nobidBuildRequests(bids, bidderRequest) { if (bid.mediaType === VIDEO || (videoMediaType && (context === 'instream' || context === 'outstream'))) { adType = 'video'; } + const floor = getFloor(bid); if (siteId) { newAdunit({ @@ -235,7 +245,8 @@ function nobidBuildRequests(bids, bidderRequest) { siteId: siteId, placementId: placementId, ad_type: adType, - params: bid.params + params: bid.params, + floor: floor }, adunits); } diff --git a/modules/nobidBidAdapter.md b/modules/nobidBidAdapter.md index 9e47aa5f43f..4449ad5c88b 100644 --- a/modules/nobidBidAdapter.md +++ b/modules/nobidBidAdapter.md @@ -4,7 +4,7 @@ title: Nobid description: Prebid Nobid Bidder Adaptor biddercode: nobid hide: true -media_types: banner +media_types: banner, video gdpr_supported: true usp_supported: true --- @@ -51,4 +51,4 @@ usp_supported: true ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index 7bd1ee8acd9..1d1ad6f054d 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -35,6 +35,16 @@ export const novatiqIdSubmodule = { snowflake: novatiqId } }; + + if (novatiqId.syncResponse !== undefined) { + responseObj.novatiq.ext = {}; + responseObj.novatiq.ext.syncResponse = novatiqId.syncResponse; + } + + if (typeof config != 'undefined' && typeof config.params !== 'undefined' && typeof config.params.removeAdditionalInfo !== 'undefined' && config.params.removeAdditionalInfo === true) { + delete responseObj.novatiq.snowflake.syncResponse; + } + return responseObj; }, @@ -120,7 +130,7 @@ export const novatiqIdSubmodule = { getNovatiqId(urlParams) { // standard uuid format let uuidFormat = [1e7] + -1e3 + -4e3 + -8e3 + -1e11; - if (urlParams.useStandardUuid == false) { + if (urlParams.useStandardUuid === false) { // novatiq standard uuid(like) format uuidFormat = uuidFormat + 1e3; } diff --git a/modules/optidigitalBidAdapter.js b/modules/optidigitalBidAdapter.js new file mode 100755 index 00000000000..4372aa830e6 --- /dev/null +++ b/modules/optidigitalBidAdapter.js @@ -0,0 +1,221 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { deepAccess, parseSizesInput, getAdUnitSizes } from '../src/utils.js'; + +const BIDDER_CODE = 'optidigital'; +const GVL_ID = 915; +const ENDPOINT_URL = 'https://pbs.optidigital.com/bidder'; +const USER_SYNC_URL_IFRAME = 'https://scripts.opti-digital.com/js/presync.html?endpoint=optidigital'; +let CUR = 'USD'; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVL_ID, + 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) { + let isValid = false; + if (typeof bid.params !== 'undefined' && bid.params.placementId && bid.params.publisherId) { + isValid = true; + } + + return isValid; + }, + /** + * 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) { + if (!validBidRequests || validBidRequests.length === 0 || !bidderRequest || !bidderRequest.bids) { + return []; + } + + const ortb2 = bidderRequest.ortb2 || { + bcat: [], + badv: [] + }; + + const payload = { + referrer: (bidderRequest.refererInfo && bidderRequest.refererInfo.page) ? bidderRequest.refererInfo.page : '', + hb_version: '$prebid.version$', + deviceWidth: document.documentElement.clientWidth, + auctionId: deepAccess(validBidRequests[0], 'auctionId'), + bidderRequestId: deepAccess(validBidRequests[0], 'bidderRequestId'), + publisherId: deepAccess(validBidRequests[0], 'params.publisherId'), + imp: validBidRequests.map(bidRequest => buildImp(bidRequest, ortb2)), + badv: ortb2.badv || deepAccess(validBidRequests[0], 'params.badv') || [], + bcat: ortb2.bcat || deepAccess(validBidRequests[0], 'params.bcat') || [], + bapp: deepAccess(validBidRequests[0], 'params.bapp') || [] + } + + if (validBidRequests[0].params.pageTemplate && validBidRequests[0].params.pageTemplate !== '') { + payload.pageTemplate = validBidRequests[0].params.pageTemplate; + } + + if (validBidRequests[0].schain) { + payload.schain = validBidRequests[0].schain; + } + + const gdpr = deepAccess(bidderRequest, 'gdprConsent'); + if (bidderRequest && gdpr) { + const isConsentString = typeof gdpr.consentString === 'string'; + payload.gdpr = { + consent: isConsentString ? gdpr.consentString : '', + required: true + }; + } + if (bidderRequest && !gdpr) { + payload.gdpr = { + consent: '', + required: false + } + } + + if (window.location.href.indexOf('optidigitalTestMode=true') !== -1) { + payload.testMode = true; + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.uspConsent = bidderRequest.uspConsent; + } + + const payloadObject = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadObject + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + serverResponse = serverResponse.body; + + if (serverResponse.bids) { + serverResponse.bids.forEach((bid) => { + const bidResponse = { + placementId: bid.placementId, + transactionId: bid.transactionId, + requestId: bid.bidId, + ttl: bid.ttl, + creativeId: bid.creativeId, + currency: bid.cur, + cpm: bid.cpm, + width: bid.w, + height: bid.h, + ad: bid.adm, + netRevenue: true, + meta: { + advertiserDomains: bid.adomain && bid.adomain.length > 0 ? bid.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) { + let syncurl = ''; + + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + if (uspConsent && uspConsent.consentString) { + syncurl += `&ccpa_consent=${uspConsent.consentString}`; + } + + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_URL_IFRAME + syncurl + }]; + } + }, +}; + +function buildImp(bidRequest, ortb2) { + let imp = {}; + imp = { + sizes: parseSizesInput(deepAccess(bidRequest, 'mediaTypes.banner.sizes')), + bidId: deepAccess(bidRequest, 'bidId'), + adUnitCode: deepAccess(bidRequest, 'adUnitCode'), + transactionId: deepAccess(bidRequest, 'transactionId'), + placementId: deepAccess(bidRequest, 'params.placementId') + }; + + if (bidRequest.params.divId && bidRequest.params.divId !== '') { + if (getAdContainer(bidRequest.params.divId)) { + imp.adContainerWidth = getAdContainer(bidRequest.params.divId).offsetWidth; + imp.adContainerHeight = getAdContainer(bidRequest.params.divId).offsetHeight; + } + } + + let floorSizes = []; + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + floorSizes = getAdUnitSizes(bidRequest); + } + + if (bidRequest.params.currency && bidRequest.params.currency !== '') { + CUR = bidRequest.params.currency; + } + + let bidFloor = _getFloor(bidRequest, floorSizes, CUR); + if (bidFloor) { + imp.bidFloor = bidFloor; + } + + let battr = ortb2.battr || deepAccess(bidRequest, 'params.battr'); + if (battr && Array.isArray(battr) && battr.length) { + imp.battr = battr; + } + + return imp; +} + +function getAdContainer(container) { + if (document.getElementById(container)) { + return document.getElementById(container); + } +} + +function _getFloor (bid, sizes, currency) { + let floor = null; + let size = sizes.length === 1 ? sizes[0] : '*'; + if (typeof bid.getFloor === 'function') { + try { + const floorInfo = bid.getFloor({ + currency: currency, + mediaType: 'banner', + size: size + }); + if (typeof floorInfo === 'object' && floorInfo.currency === CUR && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } catch (err) {} + } + return floor !== null ? floor : bid.params.floor; +} + +registerBidder(spec); diff --git a/modules/optidigitalBidAdapter.md b/modules/optidigitalBidAdapter.md new file mode 100755 index 00000000000..466dfb3bef2 --- /dev/null +++ b/modules/optidigitalBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +**Module Name**: OptiDigital Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@optidigital.com + +# Description + +Bidder Adapter for Prebid.js. + +## AdUnits configuration example +``` + var adUnits = [{ + code: 'your-slot_1-div', //use exactly the same code as your slot div id. + mediaTypes: { + banner: { + sizes: [[300,600]] + } + }, + bids: [{ + bidder: 'optidigital', + params: { + publisherId: 'test', + placementId: 'Billboard_Top', + divId: 'Billboard_Top_3c5425', // optional parameter + pageTemplate: 'home', // optional parameter + badv: ['example.com'], // optional parameter + bcat: ['IAB1-1'], // optional parameter + bapp: ['com.blocked'], // optional parameter + battr: [1, 2] // optional parameter + } + }] + }]; +``` + +## UserSync example + +``` +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + syncEnabled: true, + syncDelay: 3000 + } +}); +``` diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 305e175bf2c..7e53a0f5271 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -8,12 +8,16 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; -import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse} from '../src/utils.js'; +import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse, prefixLog} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'permutive' +const logger = prefixLog('[PermutiveRTD]') + export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd' +export const PERMUTIVE_STANDARD_KEYWORD = 'p_standard' +export const PERMUTIVE_CUSTOM_COHORTS_KEYWORD = 'permutive' export const PERMUTIVE_STANDARD_AUD_KEYWORD = 'p_standard_aud' export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) @@ -24,30 +28,6 @@ function init(moduleConfig, userConsent) { return true } -/** - * Set segment targeting from cache and then try to wait for Permutive - * to initialise to get realtime segment targeting - * @param {Object} reqBidsConfigObj - * @param {function} callback - Called when submodule is done - * @param {customModuleConfig} reqBidsConfigObj - Publisher config for module - */ -export function initSegments (reqBidsConfigObj, callback, customModuleConfig) { - const permutiveOnPage = isPermutiveOnPage() - const moduleConfig = getModuleConfig(customModuleConfig) - const segmentData = getSegments(moduleConfig.params.maxSegs) - - setSegments(reqBidsConfigObj, moduleConfig, segmentData) - - if (moduleConfig.waitForIt && permutiveOnPage) { - window.permutive.ready(function () { - setSegments(reqBidsConfigObj, moduleConfig, segmentData) - callback() - }, 'realtime') - } else { - callback() - } -} - function liftIntoParams(params) { return isPlainObject(params) ? { params } : {} } @@ -109,15 +89,13 @@ export function getModuleConfig(customModuleConfig) { /** * Sets ortb2 config for ac bidders - * @param {Object} bidderOrtb2 + * @param {Object} bidderOrtb2 - The ortb2 object for the all bidders * @param {Object} customModuleConfig - Publisher config for module */ -export function setBidderRtb (bidderOrtb2, customModuleConfig) { - const moduleConfig = getModuleConfig(customModuleConfig) +export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { const acBidders = deepAccess(moduleConfig, 'params.acBidders') const maxSegs = deepAccess(moduleConfig, 'params.maxSegs') const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || [] - const segmentData = getSegments(maxSegs) const ssps = segmentData?.ssp?.ssps ?? [] const sspCohorts = segmentData?.ssp?.cohorts ?? [] @@ -126,28 +104,39 @@ export function setBidderRtb (bidderOrtb2, customModuleConfig) { bidders.forEach(function (bidder) { const currConfig = { ortb2: bidderOrtb2[bidder] || {} } + let cohorts = [] + const isAcBidder = acBidders.indexOf(bidder) > -1 - const isSspBidder = ssps.indexOf(bidder) > -1 + if (isAcBidder) { + cohorts = segmentData.ac + } - let cohorts = [] - if (isAcBidder) cohorts = segmentData.ac - if (isSspBidder) cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) + const isSspBidder = ssps.indexOf(bidder) > -1 + if (isSspBidder) { + cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) + } - const nextConfig = updateOrtbConfig(currConfig, cohorts, sspCohorts, transformationConfigs) - bidderOrtb2[bidder] = nextConfig.ortb2; + const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, transformationConfigs, segmentData) + bidderOrtb2[bidder] = nextConfig.ortb2 }) } /** * Updates `user.data` object in existing bidder config with Permutive segments + * @param string bidder - The bidder * @param {Object} currConfig - Current bidder config * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine * the transformations on user data to include the ORTB2 object * @param {string[]} segmentIDs - Permutive segment IDs * @param {string[]} sspSegmentIDs - Permutive SSP segment IDs + * @param {Object} segmentData - The segments available for targeting * @return {Object} Merged ortb2 object */ -function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformationConfigs) { +function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transformationConfigs, segmentData) { + logger.logInfo(`Current ortb2 config`, { bidder, config: currConfig }) + + const customCohortsData = deepAccess(segmentData, bidder) || [] + const name = 'permutive.com' const permutiveUserData = { @@ -159,21 +148,59 @@ function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformation .filter(({ id }) => ortb2UserDataTransformations.hasOwnProperty(id)) .map(({ id, config }) => ortb2UserDataTransformations[id](permutiveUserData, config)) + const customCohortsUserData = { + name: PERMUTIVE_CUSTOM_COHORTS_KEYWORD, + segment: customCohortsData.map(cohortID => ({ id: cohortID })), + } + const ortbConfig = mergeDeep({}, currConfig) const currentUserData = deepAccess(ortbConfig, 'ortb2.user.data') || [] const updatedUserData = currentUserData - .filter(el => el.name !== name) - .concat(permutiveUserData, transformedUserData) + .filter(el => el.name !== permutiveUserData.name && el.name !== customCohortsUserData.name) + .concat(permutiveUserData, transformedUserData, customCohortsUserData) + logger.logInfo(`Updating ortb2.user.data`, { bidder, user_data: updatedUserData }) deepSetValue(ortbConfig, 'ortb2.user.data', updatedUserData) - // As of writing this, only used for AppNexus/Xandr in place of appnexusAuctionKeywords in config - const currentUserKeywords = deepAccess(ortbConfig, 'ortb2.user.keywords') || '' - const keywords = sspSegmentIDs.map(segment => `${PERMUTIVE_STANDARD_AUD_KEYWORD}=${segment}`).join(',') - const updatedUserKeywords = (currentUserKeywords === '') ? keywords : `${currentUserKeywords},${keywords}` - deepSetValue(ortbConfig, 'ortb2.user.keywords', updatedUserKeywords) + // Set ortb2.user.keywords + const currentKeywords = deepAccess(ortbConfig, 'ortb2.user.keywords') + const keywordGroups = { + [PERMUTIVE_STANDARD_KEYWORD]: segmentIDs, + [PERMUTIVE_STANDARD_AUD_KEYWORD]: sspSegmentIDs, + [PERMUTIVE_CUSTOM_COHORTS_KEYWORD]: customCohortsData, + } + // Transform groups of key-values into a single array of strings + // i.e { permutive: ['1', '2'], p_standard: ['3', '4'] } => ['permutive=1', 'permutive=2', 'p_standard=3',' p_standard=4'] + const transformedKeywordGroups = Object.entries(keywordGroups) + .flatMap(([keyword, ids]) => ids.map(id => `${keyword}=${id}`)) + + const keywords = [ + currentKeywords, + ...transformedKeywordGroups, + ] + .filter(Boolean) + .join(',') + + logger.logInfo(`Updating ortb2.user.keywords`, { + bidder, + keywords, + }) + deepSetValue(ortbConfig, 'ortb2.user.keywords', keywords) + + // Set user extensions + if (segmentIDs.length > 0) { + deepSetValue(ortbConfig, `ortb2.user.ext.data.${PERMUTIVE_STANDARD_KEYWORD}`, segmentIDs) + logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_STANDARD_KEYWORD}"`, segmentIDs) + } + + if (customCohortsData.length > 0) { + deepSetValue(ortbConfig, `ortb2.user.ext.data.${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}`, customCohortsData.map(String)) + logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}"`, customCohortsData) + } + + logger.logInfo(`Updated ortb2 config`, { bidder, config: ortbConfig }) return ortbConfig } @@ -251,28 +278,6 @@ function getDefaultBidderFn (bidder) { return [...new Set([...ac, ...ssp])] } const bidderMap = { - appnexus: function (bid, data, acEnabled) { - if (isPStandardTargetingEnabled(data, acEnabled)) { - const segments = pStandardTargeting(data, acEnabled) - deepSetValue(bid, 'params.keywords.p_standard', segments) - } - if (data.appnexus && data.appnexus.length) { - deepSetValue(bid, 'params.keywords.permutive', data.appnexus) - } - - return bid - }, - rubicon: function (bid, data, acEnabled) { - if (isPStandardTargetingEnabled(data, acEnabled)) { - const segments = pStandardTargeting(data, acEnabled) - deepSetValue(bid, 'params.visitor.p_standard', segments) - } - if (data.rubicon && data.rubicon.length) { - deepSetValue(bid, 'params.visitor.permutive', data.rubicon.map(String)) - } - - return bid - }, ozone: function (bid, data, acEnabled) { if (isPStandardTargetingEnabled(data, acEnabled)) { const segments = pStandardTargeting(data, acEnabled) @@ -283,7 +288,12 @@ function getDefaultBidderFn (bidder) { } } - return bidderMap[bidder] + // On no default bidder just return the same bid as passed in + function bidIdentity(bid) { + return bid + } + + return bidderMap[bidder] || bidIdentity } /** @@ -317,6 +327,7 @@ export function getSegments (maxSegs) { const segments = { ac: [..._pcrprs, ..._ppam, ...legacySegs], + ix: readSegments('_pindexs', []), rubicon: readSegments('_prubicons', []), appnexus: readSegments('_papns', []), gam: readSegments('_pdfps', []), @@ -383,17 +394,55 @@ function iabSegmentId(permutiveSegmentId, iabIds) { return iabIds[permutiveSegmentId] || unknownIabSegmentId } +/** + * Pull the latest configuration and cohort information and update accordingly. + * + * @param reqBidsConfigObj - Bidder provided config for request + * @param customModuleConfig - Publisher provide config + */ +export function readAndSetCohorts(reqBidsConfigObj, moduleConfig) { + const segmentData = getSegments(deepAccess(moduleConfig, 'params.maxSegs')) + + makeSafe(function () { + // Legacy route with custom parameters + // ACK policy violation, in process of removing + setSegments(reqBidsConfigObj, moduleConfig, segmentData) + }); + + makeSafe(function () { + // Route for bidders supporting ORTB2 + setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, moduleConfig, segmentData) + }) +} + +let permutiveSDKInRealTime = false + /** @type {RtdSubmodule} */ export const permutiveSubmodule = { name: MODULE_NAME, getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { + const completeBidRequestData = () => { + logger.logInfo(`Request data updated`) + callback() + } + + const moduleConfig = getModuleConfig(customModuleConfig) + + readAndSetCohorts(reqBidsConfigObj, moduleConfig) + makeSafe(function () { - // Legacy route with custom parameters - initSegments(reqBidsConfigObj, callback, customModuleConfig) - }); - makeSafe(function () { - // Route for bidders supporting ORTB2 - setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, customModuleConfig) + if (permutiveSDKInRealTime || !(moduleConfig.waitForIt && isPermutiveOnPage())) { + return completeBidRequestData() + } + + window.permutive.ready(function () { + logger.logInfo(`SDK is realtime, updating cohorts`) + permutiveSDKInRealTime = true + readAndSetCohorts(reqBidsConfigObj, getModuleConfig(customModuleConfig)) + completeBidRequestData() + }, 'realtime') + + logger.logInfo(`Registered cohort update when SDK is realtime`) }) }, init: init diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index 1180a74db30..908be5400e4 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -9,9 +9,9 @@ import CONSTANTS from '../src/constants.json'; * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager */ export const storage = getStorageManager({gvlid: undefined, moduleName: 'prebidmanager'}); -const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint'; +const DEFAULT_EVENT_URL = 'https://endpt.prebidmanager.com/endpoint'; const analyticsType = 'endpoint'; -const analyticsName = 'Prebid Manager Analytics: '; +const analyticsName = 'Prebid Manager Analytics'; let ajax = ajaxBuilder(0); @@ -28,8 +28,8 @@ var w = window; var d = document; var e = d.documentElement; var g = d.getElementsByTagName('body')[0]; -var x = w.innerWidth || e.clientWidth || g.clientWidth; -var y = w.innerHeight || e.clientHeight || g.clientHeight; +var x = (w && w.innerWidth) || (e && e.clientWidth) || (g && g.clientWidth); +var y = (w && w.innerHeight) || (e && e.clientHeight) || (g && g.clientHeight); var _pageView = { eventType: 'pageView', @@ -56,13 +56,20 @@ prebidmanagerAnalytics.originEnableAnalytics = prebidmanagerAnalytics.enableAnal prebidmanagerAnalytics.enableAnalytics = function (config) { initOptions = config.options || {}; initOptions.url = initOptions.url || DEFAULT_EVENT_URL; - pmAnalyticsEnabled = true; + initOptions.sampling = initOptions.sampling || 1; + + if (Math.floor(Math.random() * initOptions.sampling) === 0) { + pmAnalyticsEnabled = true; + flushInterval = setInterval(flush, 1000); + } else { + logInfo(`${analyticsName} isn't enabled because of sampling`); + } + prebidmanagerAnalytics.originEnableAnalytics(config); - flushInterval = setInterval(flush, 1000); }; prebidmanagerAnalytics.originDisableAnalytics = prebidmanagerAnalytics.disableAnalytics; -prebidmanagerAnalytics.disableAnalytics = function() { +prebidmanagerAnalytics.disableAnalytics = function () { if (!pmAnalyticsEnabled) { return; } @@ -95,7 +102,7 @@ function collectUtmTagData() { }); } } catch (e) { - logError(`${analyticsName}Error`, e); + logError(`${analyticsName} Error`, e); pmUtmTags['error_utm'] = 1; } return pmUtmTags; @@ -126,6 +133,16 @@ function flush() { pageInfo: collectPageInfo(), }; + if ('version' in initOptions) { + data.version = initOptions.version; + } + if ('tcf_compliant' in initOptions) { + data.tcf_compliant = initOptions.tcf_compliant; + } + if ('sampling' in initOptions) { + data.sampling = initOptions.sampling; + } + ajax( initOptions.url, () => logInfo(`${analyticsName} sent events batch`), @@ -142,65 +159,156 @@ function flush() { } } +function trimAdUnit(adUnit) { + if (!adUnit) return adUnit; + const res = {}; + res.code = adUnit.code; + res.sizes = adUnit.sizes; + return res; +} + +function trimBid(bid) { + if (!bid) return bid; + const res = {}; + res.auctionId = bid.auctionId; + res.bidder = bid.bidder; + res.bidderRequestId = bid.bidderRequestId; + res.bidId = bid.bidId; + res.crumbs = bid.crumbs; + res.cpm = bid.cpm; + res.currency = bid.currency; + res.mediaTypes = bid.mediaTypes; + res.sizes = bid.sizes; + res.transactionId = bid.transactionId; + res.adUnitCode = bid.adUnitCode; + res.bidRequestsCount = bid.bidRequestsCount; + res.serverResponseTimeMs = bid.serverResponseTimeMs; + return res; +} + +function trimBidderRequest(bidderRequest) { + if (!bidderRequest) return bidderRequest; + const res = {}; + res.auctionId = bidderRequest.auctionId; + res.auctionStart = bidderRequest.auctionStart; + res.bidderRequestId = bidderRequest.bidderRequestId; + res.bidderCode = bidderRequest.bidderCode; + res.bids = bidderRequest.bids && bidderRequest.bids.map(trimBid); + return res; +} + function handleEvent(eventType, eventArgs) { - eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {}; - var pmEvent = {}; + try { + eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {}; + } catch (e) { + // keep eventArgs as is + } + + const pmEvent = {}; switch (eventType) { case CONSTANTS.EVENTS.AUCTION_INIT: { - pmEvent = eventArgs; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.timeout = eventArgs.timeout; + pmEvent.eventType = eventArgs.eventType; + pmEvent.adUnits = eventArgs.adUnits && eventArgs.adUnits.map(trimAdUnit) + pmEvent.bidderRequests = eventArgs.bidderRequests && eventArgs.bidderRequests.map(trimBidderRequest) _startAuction = pmEvent.timestamp; _bidRequestTimeout = pmEvent.timeout; break; } case CONSTANTS.EVENTS.AUCTION_END: { - pmEvent = eventArgs; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.end = eventArgs.end; + pmEvent.start = eventArgs.start; + pmEvent.adUnitCodes = eventArgs.adUnitCodes; + pmEvent.bidsReceived = eventArgs.bidsReceived && eventArgs.bidsReceived.map(trimBid); pmEvent.start = _startAuction; pmEvent.end = Date.now(); break; } case CONSTANTS.EVENTS.BID_ADJUSTMENT: { - pmEvent.bidders = eventArgs; break; } case CONSTANTS.EVENTS.BID_TIMEOUT: { - pmEvent.bidders = eventArgs; + pmEvent.bidders = eventArgs && eventArgs.map ? eventArgs.map(trimBid) : eventArgs; pmEvent.duration = _bidRequestTimeout; break; } case CONSTANTS.EVENTS.BID_REQUESTED: { - pmEvent = eventArgs; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.bidderCode = eventArgs.bidderCode; + pmEvent.doneCbCallCount = eventArgs.doneCbCallCount; + pmEvent.start = eventArgs.start; + pmEvent.bidderRequestId = eventArgs.bidderRequestId; + pmEvent.bids = eventArgs.bids && eventArgs.bids.map(trimBid); + pmEvent.auctionStart = eventArgs.auctionStart; + pmEvent.timeout = eventArgs.timeout; break; } case CONSTANTS.EVENTS.BID_RESPONSE: { - pmEvent = eventArgs; - delete pmEvent.ad; + pmEvent.bidderCode = eventArgs.bidderCode; + pmEvent.width = eventArgs.width; + pmEvent.height = eventArgs.height; + pmEvent.adId = eventArgs.adId; + pmEvent.mediaType = eventArgs.mediaType; + pmEvent.cpm = eventArgs.cpm; + pmEvent.currency = eventArgs.currency; + pmEvent.requestId = eventArgs.requestId; + pmEvent.adUnitCode = eventArgs.adUnitCode; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.timeToRespond = eventArgs.timeToRespond; + pmEvent.responseTimestamp = eventArgs.responseTimestamp; + pmEvent.requestTimestamp = eventArgs.requestTimestamp; + pmEvent.netRevenue = eventArgs.netRevenue; + pmEvent.size = eventArgs.size; + pmEvent.adserverTargeting = eventArgs.adserverTargeting; break; } case CONSTANTS.EVENTS.BID_WON: { - pmEvent = eventArgs; - delete pmEvent.ad; - delete pmEvent.adUrl; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.adId = eventArgs.adId; + pmEvent.adserverTargeting = eventArgs.adserverTargeting; + pmEvent.adUnitCode = eventArgs.adUnitCode; + pmEvent.bidderCode = eventArgs.bidderCode; + pmEvent.height = eventArgs.height; + pmEvent.mediaType = eventArgs.mediaType; + pmEvent.netRevenue = eventArgs.netRevenue; + pmEvent.cpm = eventArgs.cpm; + pmEvent.requestTimestamp = eventArgs.requestTimestamp; + pmEvent.responseTimestamp = eventArgs.responseTimestamp; + pmEvent.size = eventArgs.size; + pmEvent.width = eventArgs.width; + pmEvent.currency = eventArgs.currency; + pmEvent.bidder = eventArgs.bidder; break; } case CONSTANTS.EVENTS.BIDDER_DONE: { - pmEvent = eventArgs; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.auctionStart = eventArgs.auctionStart; + pmEvent.bidderCode = eventArgs.bidderCode; + pmEvent.bidderRequestId = eventArgs.bidderRequestId; + pmEvent.bids = eventArgs.bids && eventArgs.bids.map(trimBid); + pmEvent.doneCbCallCount = eventArgs.doneCbCallCount; + pmEvent.start = eventArgs.start; + pmEvent.timeout = eventArgs.timeout; + pmEvent.tid = eventArgs.tid; + pmEvent.src = eventArgs.src; break; } case CONSTANTS.EVENTS.SET_TARGETING: { - pmEvent.targetings = eventArgs; break; } case CONSTANTS.EVENTS.REQUEST_BIDS: { - pmEvent = eventArgs; break; } case CONSTANTS.EVENTS.ADD_AD_UNITS: { - pmEvent = eventArgs; break; } case CONSTANTS.EVENTS.AD_RENDER_FAILED: { - pmEvent = eventArgs; + pmEvent.bid = eventArgs.bid; + pmEvent.message = eventArgs.message; + pmEvent.reason = eventArgs.reason; break; } default: @@ -215,7 +323,7 @@ function handleEvent(eventType, eventArgs) { function sendEvent(event) { _eventQueue.push(event); - logInfo(`${analyticsName}Event ${event.eventType}:`, event); + logInfo(`${analyticsName} Event ${event.eventType}:`, event); if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { flush(); diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index cae94f6fe7b..a4a13c56a68 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -224,6 +224,38 @@ function getAdDomain(bidResponse) { } } +function isObject(object) { + return typeof object === 'object' && object !== null; +}; + +function isEmptyObject(object) { + return isObject(object) && Object.keys(object).length === 0; +}; + +/** + * Prepare meta object to pass in logger call + * @param {*} meta + */ +export function getMetadata(meta) { + if (!meta || isEmptyObject(meta)) return; + const metaObj = {}; + if (meta.networkId) metaObj.nwid = meta.networkId; + if (meta.advertiserId) metaObj.adid = meta.advertiserId; + if (meta.networkName) metaObj.nwnm = meta.networkName; + if (meta.primaryCatId) metaObj.pcid = meta.primaryCatId; + if (meta.advertiserName) metaObj.adnm = meta.advertiserName; + if (meta.agencyId) metaObj.agid = meta.agencyId; + if (meta.agencyName) metaObj.agnm = meta.agencyName; + if (meta.brandId) metaObj.brid = meta.brandId; + if (meta.brandName) metaObj.brnm = meta.brandName; + if (meta.dchain) metaObj.dc = meta.dchain; + if (meta.demandSource) metaObj.ds = meta.demandSource; + if (meta.secondaryCatIds) metaObj.scids = meta.secondaryCatIds; + + if (isEmptyObject(metaObj)) return; + return metaObj; +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { @@ -251,7 +283,8 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, 'piid': bid.bidResponse ? (bid.bidResponse.partnerImpId || EMPTY_STRING) : EMPTY_STRING, - 'frv': (s2sBidders.indexOf(bid.bidder) > -1) ? undefined : (bid.bidResponse ? (bid.bidResponse.floorData ? bid.bidResponse.floorData.floorRuleValue : undefined) : undefined) + 'frv': (s2sBidders.indexOf(bid.bidder) > -1) ? undefined : (bid.bidResponse ? (bid.bidResponse.floorData ? bid.bidResponse.floorData.floorRuleValue : undefined) : undefined), + 'md': bid.bidResponse ? getMetadata(bid.bidResponse.meta) : undefined }); }); return partnerBids; diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index d8f5002ebd7..5ca8a2c04b3 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -378,18 +378,14 @@ function _createNativeRequest(params) { if (!(nativeRequestObject.assets && nativeRequestObject.assets.length > 0 && nativeRequestObject.assets.hasOwnProperty(key))) { switch (key) { case NATIVE_ASSETS.TITLE.KEY: - if (params[key].len || params[key].length) { - assetObj = { - id: NATIVE_ASSETS.TITLE.ID, - required: params[key].required ? 1 : 0, - title: { - len: params[key].len || params[key].length, - ext: params[key].ext - } - }; - } else { - logWarn(LOG_WARN_PREFIX + 'Error: Title Length is required for native ad: ' + JSON.stringify(params)); - } + assetObj = { + id: NATIVE_ASSETS.TITLE.ID, + required: params[key].required ? 1 : 0, + title: { + len: params[key].len || params[key].length, + ext: params[key].ext + } + }; break; case NATIVE_ASSETS.IMAGE.KEY: assetObj = { @@ -607,7 +603,7 @@ function _addDealCustomTargetings(imp, bid) { } } -function _addJWPlayerSegmentData(imp, bid, isS2S) { +function _addJWPlayerSegmentData(imp, bid) { var jwSegData = (bid.rtd && bid.rtd.jwplayer && bid.rtd.jwplayer.targeting) || undefined; var jwPlayerData = ''; const jwMark = 'jw-'; @@ -624,12 +620,8 @@ function _addJWPlayerSegmentData(imp, bid, isS2S) { var ext; - if (isS2S) { - (imp.dctr === undefined || imp.dctr.length == 0) ? imp.dctr = jwPlayerData : imp.dctr += '|' + jwPlayerData; - } else { - ext = imp.ext; - ext && ext.key_val === undefined ? ext.key_val = jwPlayerData : ext.key_val += '|' + jwPlayerData; - } + ext = imp.ext; + ext && ext.key_val === undefined ? ext.key_val = jwPlayerData : ext.key_val += '|' + jwPlayerData; } function _createImpressionObject(bid, conf) { @@ -977,6 +969,49 @@ function isNonEmptyArray(test) { return false; } +/** + * Prepare meta object to pass as params + * @param {*} br : bidResponse + * @param {*} bid : bids + */ +export function prepareMetaObject(br, bid, seat) { + br.meta = {}; + + if (bid.ext && bid.ext.dspid) { + br.meta.networkId = bid.ext.dspid; + br.meta.demandSource = bid.ext.dspid; + } + + // NOTE: We will not recieve below fields from the translator response also not sure on what will be the key names for these in the response, + // when we needed we can add it back. + // New fields added, assignee fields name may change + // if (bid.ext.networkName) br.meta.networkName = bid.ext.networkName; + // if (bid.ext.advertiserName) br.meta.advertiserName = bid.ext.advertiserName; + // if (bid.ext.agencyName) br.meta.agencyName = bid.ext.agencyName; + // if (bid.ext.brandName) br.meta.brandName = bid.ext.brandName; + if (bid.ext && bid.ext.dchain) { + br.meta.dchain = bid.ext.dchain; + } + + const advid = seat || (bid.ext && bid.ext.advid); + if (advid) { + br.meta.advertiserId = advid; + br.meta.agencyId = advid; + br.meta.buyerId = advid; + } + + if (bid.adomain && isNonEmptyArray(bid.adomain)) { + br.meta.advertiserDomains = bid.adomain; + br.meta.clickUrl = bid.adomain[0]; + br.meta.brandId = bid.adomain[0]; + } + + if (bid.cat && isNonEmptyArray(bid.cat)) { + br.meta.secondaryCatIds = bid.cat; + br.meta.primaryCatId = bid.cat[0]; + } +} + export const spec = { code: BIDDER_CODE, gvlid: 76, @@ -1297,17 +1332,7 @@ export const spec = { newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; } - newBid.meta = {}; - if (bid.ext && bid.ext.dspid) { - newBid.meta.networkId = bid.ext.dspid; - } - if (bid.ext && bid.ext.advid) { - newBid.meta.buyerId = bid.ext.advid; - } - if (bid.adomain && bid.adomain.length > 0) { - newBid.meta.advertiserDomains = bid.adomain; - newBid.meta.clickUrl = bid.adomain[0]; - } + prepareMetaObject(newBid, bid, seatbidder.seat); // adserverTargeting if (seatbidder.ext && seatbidder.ext.buyid) { @@ -1375,7 +1400,6 @@ export const spec = { */ transformBidParams: function (params, isOpenRtb, adUnit, bidRequests) { - _addJWPlayerSegmentData(params, adUnit.bids[0], true); return convertTypes({ 'publisherId': 'string', 'adSlot': 'string' diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index 19a74c7c245..0a775b07b7f 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -304,11 +304,14 @@ pubwiseAnalytics.storeSessionID = function (userSessID) { // ensure a session exists, if not make one, always store it pubwiseAnalytics.ensureSession = function () { - if (sessionExpired() === true || userSessionID() === null || userSessionID() === '') { + let sessionId = userSessionID(); + if (sessionExpired() === true || sessionId === null || sessionId === '') { let generatedId = generateUUID(); expireUtmData(); this.storeSessionID(generatedId); sessionData.sessionId = generatedId; + } else if (sessionId != null) { + sessionData.sessionId = sessionId; } // eslint-disable-next-line // console.log('ensured session'); diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index 5e381e74a18..a7381bb2884 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -1,19 +1,32 @@ -import { _each, isStr, deepClone, isArray, deepSetValue, inIframe, logMessage, logInfo, logWarn, logError } from '../src/utils.js'; -import { config } from '../src/config.js'; + +import { _each, isBoolean, isEmptyStr, isNumber, isStr, deepClone, isArray, deepSetValue, inIframe, mergeDeep, deepAccess, logMessage, logInfo, logWarn, logError } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -const VERSION = '0.2.0'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { OUTSTREAM, INSTREAM } from '../src/video.js'; + +const VERSION = '0.3.0'; const GVLID = 842; const NET_REVENUE = true; const UNDEFINED = undefined; const DEFAULT_CURRENCY = 'USD'; const AUCTION_TYPE = 1; const BIDDER_CODE = 'pwbid'; +const LOG_PREFIX = 'PubWise: '; const ENDPOINT_URL = 'https://bid.pubwise.io/prebid'; +// const ENDPOINT_URL = 'https://bid.pubwise.io/prebid'; // testing observable endpoint const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; const PREBID_NATIVE_HELP_LINK = 'https://prebid.org/dev-docs/show-native-ads.html'; // const USERSYNC_URL = '//127.0.0.1:8080/usersync' +const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; + +const MEDIATYPE = [ + BANNER, + VIDEO, + NATIVE +] const CUSTOM_PARAMS = { 'gender': '', // User gender @@ -22,6 +35,32 @@ const CUSTOM_PARAMS = { 'lon': '', // User Location - Longitude }; +const DATA_TYPES = { + 'NUMBER': 'number', + 'STRING': 'string', + 'BOOLEAN': 'boolean', + 'ARRAY': 'array', + 'OBJECT': 'object' +}; + +const VIDEO_CUSTOM_PARAMS = { + 'mimes': DATA_TYPES.ARRAY, + 'minduration': DATA_TYPES.NUMBER, + 'maxduration': DATA_TYPES.NUMBER, + 'startdelay': DATA_TYPES.NUMBER, + 'playbackmethod': DATA_TYPES.ARRAY, + 'api': DATA_TYPES.ARRAY, + 'protocols': DATA_TYPES.ARRAY, + 'w': DATA_TYPES.NUMBER, + 'h': DATA_TYPES.NUMBER, + 'battr': DATA_TYPES.ARRAY, + 'linearity': DATA_TYPES.NUMBER, + 'placement': DATA_TYPES.NUMBER, + 'minbitrate': DATA_TYPES.NUMBER, + 'maxbitrate': DATA_TYPES.NUMBER, + 'skip': DATA_TYPES.NUMBER +} + // rtb native types are meant to be dynamic and extendable // the extendable data asset types are nicely aligned // in practice we set an ID that is distinct for each real type of return @@ -88,7 +127,7 @@ _each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = a export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER, NATIVE], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. * @@ -96,18 +135,40 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - // siteId is required + // siteId is required for any type if (bid.params && bid.params.siteId) { // it must be a string if (!isStr(bid.params.siteId)) { _logWarn('siteId is required for bid', bid); return false; } - } else { - return false; + + // video ad validation + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + // bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array + let mediaTypesVideoMimes = deepAccess(bid.mediaTypes, 'video.mimes'); + let paramsVideoMimes = deepAccess(bid, 'params.video.mimes'); + if (_isNonEmptyArray(mediaTypesVideoMimes) === false && _isNonEmptyArray(paramsVideoMimes) === false) { + _logWarn('Error: For video ads, bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array. Call suppressed:', JSON.stringify(bid)); + return false; + } + + if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { + _logError(`no context specified in bid. Rejecting bid: `, JSON.stringify(bid)); + return false; + } + + if (bid.mediaTypes[VIDEO].context === 'outstream') { + delete bid.mediaTypes[VIDEO]; + _logWarn(`outstream not currently supported `, JSON.stringify(bid)); + return false; + } + } + + return true; } - return true; + return false; }, /** * Make a server request from the list of BidRequests. @@ -116,6 +177,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -210,7 +272,7 @@ export const spec = { return { method: 'POST', - url: ENDPOINT_URL, + url: _getEndpointURL(bid), data: payload, options: options, bidderRequest: bidderRequest, @@ -231,6 +293,7 @@ export const spec = { // try { if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + _logInfo('interpretResponse response body', response.body); // Supporting multiple bid responses for same adSize respCur = response.body.cur || respCur; response.body.seatbid.forEach(seatbidder => { @@ -254,10 +317,24 @@ export const spec = { if (parsedRequest.imp && parsedRequest.imp.length > 0) { parsedRequest.imp.forEach(req => { if (bid.impid === req.id) { - _checkMediaType(bid.adm, newBid); + _checkMediaType(bid, newBid); switch (newBid.mediaType) { case BANNER: break; + case VIDEO: + const videoContext = deepAccess(request, 'mediaTypes.video.context'); + switch (videoContext) { + case OUTSTREAM: + // not currently supported + break; + case INSTREAM: + break; + } + newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; + newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; + newBid.vastXml = bid.adm; + newBid.vastUrl = bid.vastUrl; + break; case NATIVE: _parseNativeResponse(bid, newBid); break; @@ -289,20 +366,31 @@ export const spec = { } } -function _checkMediaType(adm, newBid) { - // Create a regex here to check the strings - var admJSON = ''; - if (adm.indexOf('"ver":') >= 0) { - try { - admJSON = JSON.parse(adm.replace(/\\/g, '')); - if (admJSON && admJSON.assets) { - newBid.mediaType = NATIVE; +function _checkMediaType(bid, newBid) { + // Check Various ADM Aspects to Determine Media Type + if (bid.ext && bid.ext['bidtype'] != undefined) { + // this is the most explicity check + newBid.mediaType = MEDIATYPE[bid.ext.bidtype]; + } else { + _logInfo('bid.ext.bidtype does not exist, checking alternatively for mediaType'); + var adm = bid.adm; + var videoRegex = new RegExp(/VAST\s+version/); + + if (adm.indexOf('"ver":') >= 0) { + try { + var admJSON = ''; + admJSON = JSON.parse(adm.replace(/\\/g, '')); + if (admJSON && admJSON.assets) { + newBid.mediaType = NATIVE; + } + } catch (e) { + _logWarn('Error: Cannot parse native reponse for ad response: ', adm); } - } catch (e) { - _logWarn('Error: Cannot parse native reponse for ad response: ' + adm); + } else if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + newBid.mediaType = BANNER; } - } else { - newBid.mediaType = BANNER; } } @@ -416,7 +504,8 @@ function _createOrtbTemplate(conf) { dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, h: screen.height, w: screen.width, - language: navigator.language + language: navigator.language, + devicetype: _getDeviceType() }, user: {}, ext: { @@ -428,6 +517,7 @@ function _createOrtbTemplate(conf) { function _createImpressionObject(bid, conf) { var impObj = {}; var bannerObj; + var videoObj; var nativeObj = {}; var mediaTypes = ''; @@ -459,6 +549,12 @@ function _createImpressionObject(bid, conf) { _logWarn('Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); } break; + case VIDEO: + videoObj = _createVideoRequest(bid); + if (videoObj !== UNDEFINED) { + impObj.video = videoObj; + } + break; } } } else { @@ -468,7 +564,8 @@ function _createImpressionObject(bid, conf) { _addFloorFromFloorModule(impObj, bid); return impObj.hasOwnProperty(BANNER) || - impObj.hasOwnProperty(NATIVE) ? impObj : UNDEFINED; + impObj.hasOwnProperty(NATIVE) || + impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; } function _parseSlotParam(paramName, paramValue) { @@ -492,7 +589,7 @@ function _parseSlotParam(paramName, paramValue) { } function _parseAdSlot(bid) { - _logInfo('parseAdSlot bid', bid) + _logInfo('parseAdSlot bid', bid); if (bid.adUnitCode) { bid.params.adUnit = bid.adUnitCode; } else { @@ -504,7 +601,7 @@ function _parseAdSlot(bid) { if (bid.hasOwnProperty('mediaTypes')) { if (bid.mediaTypes.hasOwnProperty(BANNER) && - bid.mediaTypes.banner.hasOwnProperty('sizes')) { // if its a banner, has mediaTypes and sizes + bid.mediaTypes.banner.hasOwnProperty('sizes')) { // if its a banner, has mediaTypes and sizes var i = 0; var sizeArray = []; for (;i < bid.mediaTypes.banner.sizes.length; i++) { @@ -522,7 +619,7 @@ function _parseAdSlot(bid) { } } } else { - _logWarn('MediaTypes are Required for all Adunit Configs', bid) + _logWarn('MediaTypes are Required for all Adunit Configs', bid); } } @@ -558,7 +655,7 @@ function _addFloorFromFloorModule(impObj, bid) { // get lowest floor from floorModule if (typeof bid.getFloor === 'function' && !config.getConfig('pubwise.disableFloors')) { - [BANNER, NATIVE].forEach(mediaType => { + [BANNER, VIDEO, NATIVE].forEach(mediaType => { if (impObj.hasOwnProperty(mediaType)) { let floorInfo = bid.getFloor({ currency: impObj.bidFloorCur, mediaType: mediaType, size: '*' }); if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidFloorCur && !isNaN(parseInt(floorInfo.floor))) { @@ -746,28 +843,162 @@ function _createBannerRequest(bid) { _logWarn('Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); bannerObj = UNDEFINED; } + return bannerObj; } // various error levels are not always used // eslint-disable-next-line no-unused-vars function _logMessage(textValue, objectValue) { - logMessage('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logMessage(LOG_PREFIX + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logInfo(textValue, objectValue) { - logInfo('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logInfo(LOG_PREFIX + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logWarn(textValue, objectValue) { - logWarn('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logWarn(LOG_PREFIX + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logError(textValue, objectValue) { - logError('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logError(LOG_PREFIX + textValue, objectValue); +} + +function _checkVideoPlacement(videoData, adUnitCode) { + // Check for video.placement property. If property is missing display log message. + if (!deepAccess(videoData, 'placement')) { + _logWarn(`${MSG_VIDEO_PLACEMENT_MISSING} for ${adUnitCode}`, adUnitCode); + }; +} + +function _createVideoRequest(bid) { + var videoData = mergeDeep(deepAccess(bid.mediaTypes, 'video'), bid.params.video); + var videoObj; + + if (videoData !== UNDEFINED) { + videoObj = {}; + _checkVideoPlacement(videoData, bid.adUnitCode); + for (var key in VIDEO_CUSTOM_PARAMS) { + if (videoData.hasOwnProperty(key)) { + videoObj[key] = _checkParamDataType(key, videoData[key], VIDEO_CUSTOM_PARAMS[key]); + } + } + // read playersize and assign to h and w. + if (isArray(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0][0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[0][1], 10); + } else if (isNumber(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[1], 10); + } + } else { + videoObj = UNDEFINED; + _logWarn('Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.', bid.params); + } + return videoObj; +} + +/** + * Determines if the array has values + * + * @param {object} test + * @returns {boolean} + */ +function _isNonEmptyArray(test) { + if (isArray(test) === true) { + if (test.length > 0) { + return true; + } + } + return false; +} + +/** + * Returns the overridden bid endpoint_url if it is set, primarily used for testing + * + * @param {object} bid the current bid + * @returns + */ +function _getEndpointURL(bid) { + if (!isEmptyStr(bid?.params?.endpoint_url) && bid?.params?.endpoint_url != UNDEFINED) { + return bid.params.endpoint_url; + } + + return ENDPOINT_URL; +} + +/** + * + * @param {object} key + * @param {object}} value + * @param {object} datatype + * @returns + */ +function _checkParamDataType(key, value, datatype) { + var errMsg = 'Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value; + var functionToExecute; + switch (datatype) { + case DATA_TYPES.BOOLEAN: + functionToExecute = isBoolean; + break; + case DATA_TYPES.NUMBER: + functionToExecute = isNumber; + break; + case DATA_TYPES.STRING: + functionToExecute = isStr; + break; + case DATA_TYPES.ARRAY: + functionToExecute = isArray; + break; + } + if (functionToExecute(value)) { + return value; + } + _logWarn(errMsg, key); + return UNDEFINED; +} + +function _isMobile() { + if (navigator.userAgentData && navigator.userAgentData.mobile) { + return true; + } else { + return (/(mobi)/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 _isTablet() { + return (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase())); +} + +/** + * Very high level device detection, order matters + */ +function _getDeviceType() { + if (_isTablet()) { + return 5; + } + + if (_isMobile()) { + return 4; + } + + if (_isConnectedTV()) { + return 3; + } + + return 2; } // function _decorateLog() { @@ -777,6 +1008,7 @@ function _logError(textValue, objectValue) { // these are exported only for testing so maintaining the JS convention of _ to indicate the intent export { + _checkVideoPlacement, _checkMediaType, _parseAdSlot } diff --git a/modules/relevadRtdProvider.js b/modules/relevadRtdProvider.js new file mode 100644 index 00000000000..a7d0305da62 --- /dev/null +++ b/modules/relevadRtdProvider.js @@ -0,0 +1,365 @@ +/** + * This module adds Relevad provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch categories and segments from Relevad server and pass them to the bidders + * @module modules/relevadRtdProvider + * @requires module:modules/realTimeData + */ + +import {deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import {findIndex} from '../src/polyfill.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {config} from '../src/config.js'; + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'RelevadRTDModule'; + +const SEGTAX_IAB = 6; // IAB Content Taxonomy v2 +const CATTAX_IAB = 6; // IAB Contextual Taxonomy v2.2 +const RELEVAD_API_DOMAIN = 'https://prebid.relestar.com'; +const entries = Object.entries; +const AJAX_OPTIONS = { + withCredentials: true, + referrerPolicy: 'unsafe-url', + crossOrigin: true, +}; + +export let serverData = {}; // Tracks data returned from Relevad RTD server + +/** + * Provides contextual IAB categories and segments to the bidders. + * + * @param {} reqBidsConfigObj Bids request configuration + * @param {Function} onDone Ajax callbacek + * @param {} moduleConfig Rtd module configuration + * @param {} userConsent user GDPR consent + */ +export function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig, userConsent) { + moduleConfig.params = moduleConfig.params || {}; + moduleConfig.params.partnerid = moduleConfig.params.partnerid ? moduleConfig.params.partnerid : 1; + + let adunitInfo = reqBidsConfigObj.adUnits.map(adunit => { return [adunit.code, adunit.bids.map(bid => { return [bid.bidder, bid.params] })]; }); + serverData.page = moduleConfig.params.actualUrl || getRefererInfo().page || ''; + const url = (RELEVAD_API_DOMAIN + '/apis/rweb2/' + + '?url=' + encodeURIComponent(serverData.page) + + '&au=' + encodeURIComponent(JSON.stringify(adunitInfo)) + + '&pid=' + encodeURIComponent(moduleConfig.params?.publisherid || '') + + '&aid=' + encodeURIComponent(moduleConfig.params?.apikey || '') + + '&cid=' + encodeURIComponent(moduleConfig.params?.partnerid || '') + + '&gdpra=' + encodeURIComponent(userConsent?.gdpr?.gdprApplies || '') + + '&gdprc=' + encodeURIComponent(userConsent?.gdpr?.consentString || '') + ); + + ajax(url, + { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + serverData.rawdata = data; + if (data) { + addRtdData(reqBidsConfigObj, data, moduleConfig); + } + } catch (e) { + logError(SUBMODULE_NAME, 'unable to parse data: ' + e); + } + onDone(); + } + }, + error: function () { + logError(SUBMODULE_NAME, 'unable to receive data'); + onDone(); + } + }, + null, + { method: 'GET', ...AJAX_OPTIONS, }, + ); +} + +/** + * Sets global ORTB user and site data + * + * @param {dictionary} ortb2 The gloabl ORTB structure + * @param {dictionary} rtdData Rtd segments and categories + */ +export function setGlobalOrtb2(ortb2, rtdData) { + try { + let addOrtb2 = composeOrtb2Data(rtdData, 'site'); + !isEmpty(addOrtb2) && mergeDeep(ortb2, addOrtb2); + } catch (e) { + logError(e) + } +} + +/** + * Compose ORTB2 data fragment from RTD data + * + * @param {dictionary} rtdData RTD segments and categories + * @param {string} prefix Site path prefix + * @return {dictionary} ORTB2 fragment ready to be merged into global or bidder ORTB + */ +function composeOrtb2Data(rtdData, prefix) { + const segments = rtdData.segments; + const categories = rtdData.categories; + const content = rtdData.content; + let addOrtb2 = {}; + + !isEmpty(segments) && deepSetValue(addOrtb2, 'user.ext.data.relevad_rtd', segments); + !isEmpty(categories.cat) && deepSetValue(addOrtb2, prefix + '.cat', categories.cat); + !isEmpty(categories.pagecat) && deepSetValue(addOrtb2, prefix + '.pagecat', categories.pagecat); + !isEmpty(categories.sectioncat) && deepSetValue(addOrtb2, prefix + '.sectioncat', categories.sectioncat); + !isEmpty(categories.cattax) && deepSetValue(addOrtb2, prefix + '.cattax', categories.cattax); + + if (!isEmpty(content) && !isEmpty(content.segs) && content.segtax) { + const contentSegments = { + name: 'relevad', + ext: { segtax: content.segtax }, + segment: content.segs.map(x => { return {id: x}; }) + }; + deepSetValue(addOrtb2, prefix + '.content.data', [contentSegments]); + } + return addOrtb2; +} + +/** + * Sets ORTB user and site data for a given bidder + * + * @param {dictionary} bidderOrtbFragment The bidder ORTB fragment + * @param {object} bidder The bidder name + * @param {object} rtdData RTD categories and segments + */ +function setBidderSiteAndContent(bidderOrtbFragment, bidder, rtdData) { + try { + let addOrtb2 = composeOrtb2Data(rtdData, 'site'); + !isEmpty(rtdData.segments) && deepSetValue(addOrtb2, 'user.ext.data.relevad_rtd', rtdData.segments); + !isEmpty(rtdData.categories?.sectioncat) && deepSetValue(addOrtb2, 'site.ext.data.relevad_rtd', rtdData.categories.sectioncat); + if (isEmpty(addOrtb2)) { + return; + } + bidderOrtbFragment[bidder] = bidderOrtbFragment[bidder] || {}; + mergeDeep(bidderOrtbFragment[bidder], addOrtb2); + } catch (e) { + logError(e) + } +} + +/** + * Filters dictionary entries + * + * @param {array of {key:value}} dict A dictionary with numeric values + * @param {string} minscore The minimum value + * @return {array[names]} Array of category names with scores greater or equal to minscore + */ +function filterByScore(dict, minscore) { + if (dict && !isEmpty(dict)) { + minscore = minscore && typeof minscore == 'number' ? minscore : 30; + try { + const filteredCategories = Object.keys(Object.fromEntries(Object.entries(dict).filter(([k, v]) => v > minscore))); + return isEmpty(filteredCategories) ? null : filteredCategories; + } catch (e) { + logError(e); + } + } + return null; +} + +/** + * Filters RTD by relevancy score + * + * @param {object} data The Input RTD + * @param {string} minscore The minimum relevancy score + * @return {object} Filtered RTD + */ +function getFiltered(data, minscore) { + let relevadData = {'segments': []}; + + minscore = minscore && typeof minscore == 'number' ? minscore : 30; + + const cats = filterByScore(data.cats, minscore); + const pcats = filterByScore(data.pcats, minscore) || cats; + const scats = filterByScore(data.scats, minscore) || pcats; + const cattax = data.cattax ? data.cattax : CATTAX_IAB; + relevadData.categories = {cat: cats, pagecat: pcats, sectioncat: scats, cattax: cattax}; + + const contsegs = filterByScore(data.contsegs, minscore); + const segtax = data.segtax ? data.segtax : SEGTAX_IAB; + relevadData.content = {segs: contsegs, segtax: segtax}; + + try { + if (data && data.segments) { + for (let segId in data.segments) { + if (data.segments.hasOwnProperty(segId)) { + relevadData.segments.push(data.segments[segId].toString()); + } + } + } + } catch (e) { + logError(e); + } + return relevadData; +} + +/** + * Adds Rtd data to global ORTB structure and bidder requests + * + * @param {} reqBids The bid requests list + * @param {} data The Rtd data + * @param {} moduleConfig The Rtd module configuration + */ +export function addRtdData(reqBids, data, moduleConfig) { + moduleConfig = moduleConfig || {}; + moduleConfig.params = moduleConfig.params || {}; + const globalMinScore = moduleConfig.params.hasOwnProperty('minscore') ? moduleConfig.params.minscore : 30; + const relevadData = getFiltered(data, globalMinScore); + const relevadList = relevadData.segments.concat(relevadData.categories.pagecat); + // Publisher side bidder whitelist + const biddersParamsExist = !!(moduleConfig?.params?.bidders); + // RTD Server-side bidder whitelist + const wl = data.wl || null; + const noWhitelists = !biddersParamsExist && isEmpty(wl); + + // Add RTD data to the global ORTB fragments when no whitelists present + noWhitelists && setGlobalOrtb2(reqBids.ortb2Fragments?.global, relevadData); + + // Target GAM/GPT + let setgpt = moduleConfig.params.setgpt || !moduleConfig.params.hasOwnProperty('setgpt'); + if (moduleConfig.dryrun || (typeof window.googletag !== 'undefined' && setgpt)) { + try { + if (window.googletag && window.googletag.pubads && (typeof window.googletag.pubads === 'function')) { + window.googletag.pubads().getSlots().forEach(function (n) { + if (typeof n.setTargeting !== 'undefined' && relevadList && relevadList.length > 0) { + n.setTargeting('relevad_rtd', relevadList); + } + }); + } + } catch (e) { + logError(e); + } + } + + // Set per-bidder RTD + const adUnits = reqBids.adUnits; + adUnits.forEach(adUnit => { + noWhitelists && deepSetValue(adUnit, 'ortb2Imp.ext.data.relevad_rtd', relevadList); + + adUnit.hasOwnProperty('bids') && adUnit.bids.forEach(bid => { + let bidderIndex = (moduleConfig.params.hasOwnProperty('bidders') ? findIndex(moduleConfig.params.bidders, function (i) { + return i.bidder === bid.bidder; + }) : false); + const indexFound = !!(typeof bidderIndex == 'number' && bidderIndex >= 0); + try { + if ( + !biddersParamsExist || + (indexFound && + (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || + moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1 + ) + ) + ) { + let wb = isEmpty(wl) || wl[bid.bidder] === true; + if (!wb && !isEmpty(wl[bid.bidder])) { + wb = true; + for (const [key, value] of entries(wl[bid.bidder])) { + let params = bid?.params || {}; + wb = wb && (key in params) && params[key] == value; + } + } + if (wb && !isEmpty(relevadList)) { + setBidderSiteAndContent(reqBids.ortb2Fragments?.bidder, bid.bidder, relevadData); + deepSetValue(bid, 'params.keywords.relevad_rtd', relevadList); + deepSetValue(bid, 'params.target', [].concat(bid.params?.target ? [bid.params.target] : []).concat(relevadList.map(entry => { return 'relevad_rtd=' + entry; })).join(';')); + let firstPartyData = {}; + firstPartyData[bid.bidder] = { firstPartyData: { relevad_rtd: relevadList } }; + config.setConfig(firstPartyData); + !isEmpty(relevadData.segments) && deepSetValue(bid, 'ortb2.user.ext.data.segments', relevadData.segments); + !isEmpty(relevadData.categories) && deepSetValue(bid, 'ortb2.user.ext.data.contextual_categories', relevadData.categories.pagecat); + !isEmpty(relevadData.categories) && deepSetValue(bid, 'ortb2.site.ext.data.relevad_rtd', relevadData.categories.pagecat); + !isEmpty(relevadData.segments) && deepSetValue(bid, 'ortb2.user.ext.data.relevad_rtd', relevadData.segments); + } + } + } catch (e) { + logError(e); + } + }); + }); + + serverData = {...serverData, ...relevadData}; + return adUnits; +} + +/** + * Sends bid info to the RTD server + * + * @param {JSON} data Bids information + * @param {object} config Configuraion + */ +function sendBids(data, config) { + let dataJson = JSON.stringify(data); + + if (!config.dryrun) { + ajax(RELEVAD_API_DOMAIN + '/apis/bids/', () => {}, dataJson, AJAX_OPTIONS); + } + serverData = { clientdata: data }; +}; + +/** + * Processes AUCTION_END event + * + * @param {object} auctionDetails Auction details + * @param {object} config Module configuration + * @param {object} userConsent User GDPR consent object + */ +function onAuctionEnd(auctionDetails, config, userConsent) { + let adunitObj = {}; + let adunits = []; + + // Add Bids Received + auctionDetails.bidsReceived.forEach((bidObj) => { + if (!adunitObj[bidObj.adUnitCode]) { adunitObj[bidObj.adUnitCode] = []; } + + adunitObj[bidObj.adUnitCode].push({ + bidder: bidObj.bidderCode || bidObj.bidder, + cpm: bidObj.cpm, + currency: bidObj.currency, + dealId: bidObj.dealId, + type: bidObj.mediaType, + ttr: bidObj.timeToRespond, + size: bidObj.size + }); + }); + + entries(adunitObj).forEach(([adunitCode, bidsReceived]) => { + adunits.push({code: adunitCode, bids: bidsReceived}); + }); + + let data = { + event: 'bids', + adunits: adunits, + reledata: serverData.rawdata, + pid: encodeURIComponent(config.params?.publisherid || ''), + aid: encodeURIComponent(config.params?.apikey || ''), + cid: encodeURIComponent(config.params?.partnerid || ''), + gdpra: encodeURIComponent(userConsent?.gdpr?.gdprApplies || ''), + gdprc: encodeURIComponent(userConsent?.gdpr?.consentString || ''), + } + if (!config.dryrun) { + data.page = serverData?.page || config?.params?.actualUrl || getRefererInfo().page || ''; + } + + sendBids(data, config); +} + +export function init(config) { + return true; +} + +export const relevadSubmodule = { + name: SUBMODULE_NAME, + init: init, + onAuctionEndEvent: onAuctionEnd, + getBidRequestData: getBidRequestData +}; + +submodule(MODULE_NAME, relevadSubmodule); diff --git a/modules/relevadRtdProvider.md b/modules/relevadRtdProvider.md new file mode 100644 index 00000000000..fcbc7a7fb36 --- /dev/null +++ b/modules/relevadRtdProvider.md @@ -0,0 +1,108 @@ +# Relevad Real-Time Data Submodule + +Module Name: Relevad Rtd Provider +Module Type: Rtd Provider +Maintainer: anna@relevad.com + +# Description + +Relevad is a contextual semantic analytics company. Our privacy-first, cookieless contextual categorization, segmentation, and keyword generation platform is designed to help publishers and advertisers optimize targeting and increase ad inventory yield. + +Our real-time data processing module provides quality contextual IAB categories and segments along with their relevancy scores to the publisher’s web page. It places them into auction bid requests as global and/or bidder-specific: + +| Attrubute Type | ORTB2 Attribute | +| -------------- | ------------------------------------------------------------ | +| Contextual | “site.cat”: [IAB category codes]
“site.pagecat”: [IAB category codes],
“site.sectioncat”: [IAB category codes]
“site.cattax”: 6 | +| Content | “site.content.data”: {“name”: “relevad”, “ext”: …, “segment”: …} | +| User Data | “user.ext.data.relevad_rtd”: {segments} | + +Publisher may configre minimum relevancy score to restrict the categories and segments we pass to the bidders. +Relevad service does not use browser cookies and is fully GDPR compliant. + +### Publisher Integration + +Compile the Relevad RTD module into the Prebid.js package with + +`gulp build --modules=rtdModule,relevadRtdProvider` + +Add Relevad RTD provider to your Prebid config. Here is an example: + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "RelevadRTDModule", + waitForIt: true, + params: { + partnerId: your_partner_id, // Your Relevad partner id. + setgpt: true, // Target or not google GAM/GPT on your page. + minscore: 30, // Minimum relevancy score (0-100). If absent, defaults to 30. + + // The list of bidders to target with Relevad categories and segments. If absent or empty, target all bidders. + bidders: [ + { bidder: "appnexus", // Bidder name + adUnitCodes: ['adUnit-1','adUnit-2'], // List of adUnit codes to target. If absent or empty, target all ad units. + minscore: 70, // Minimum relevancy score for this bidder (0-100). If absent, defaults to the global minscore. + }, + ... + ] + } + } + ] + } + ... +} +``` + +### Relevad Real Time Submodule Configuration Parameters + + + +{: .table .table-bordered .table-striped } +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Relevad RTD module name | Mandatory, must be **RelevadRTDModule** | +| waitForIt | Boolean | Whether to delay auction for the RTD module response | Optional. Defaults to false.We recommend setting it to true. Relevad RTD service is very fast. | +| params | Object | | Relevad RTD module configuration | +| params.partnerid | String | Relevad Partner ID, required to enable the service | Mandatory | +| params.publisherid | String | Relevad publisher id | Mandatory | +| params.apikey | String | Relevad API key | Mandatory | +| param.actualUrl | String | Your page URL. When present, will be categorized instead of the browser-provided URL | Optional, defaults to the browser-providedURL | +| params.setgpt | Boolean | Target or not Google GPT/GAM when it is configured on your page | Optional, defaults to true. | +| params.minscore | Integer | Minimum categorization relevancy score in the range of 0-100. Our categories and segments come with their relevancy scores. We’ll send to the bidders only categories and segments with the scores higher than the minscore. |Optional, defaults to 30| +| params.bidders | Dictionary | Bidders with which to share category and segment information | Optional. If empty or absent, target all bidders. | + + + +#### Bidder-specific configuration. Every bidder may have these configuration parameters + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| bidder | String | Bidder name | Mandatory. Example: “appnexus” | +| adUnitCodes | Array of Strings | List of specific AdUnit codes you with to target | Optional. If empty or absent, all ad units are targeted. | +| minscore | Integer | Bidder-specific minimum categorization relevancy score (0, 100) | Optional, defaults to global minscore above. | + +If you do not have your own `partnerid, publisherid, apikey` please reach out to [info@relevad.com](mailto:info@relevad.com). + +### Testing + +To view an example of the on page setup required: + +```bash +gulp serve-fast --modules=rtdModule,relevadRtdProvider +``` + +Then in your browser access: + +``` +http://localhost:9999/integrationExamples/gpt/relevadRtdProvider_example.html +``` + +Run the unit tests for Relevad RTD module: + +```bash +gulp test --file "test/spec/modules/relevadRtdProvider_spec.js" +``` \ No newline at end of file diff --git a/modules/resetdigitalBidAdapter.js b/modules/resetdigitalBidAdapter.js index a606f0c0b7d..93846e402e9 100644 --- a/modules/resetdigitalBidAdapter.js +++ b/modules/resetdigitalBidAdapter.js @@ -1,9 +1,11 @@ - -import { timestamp, deepAccess } from '../src/utils.js'; +import { timestamp, deepAccess, isStr, deepClone } from '../src/utils.js'; import { getOrigin } from '../libraries/getOrigin/index.js'; import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + const BIDDER_CODE = 'resetdigital'; +const CURRENCY = 'USD'; export const spec = { code: BIDDER_CODE, @@ -43,16 +45,70 @@ export const spec = { }; } + if (bidderRequest && bidderRequest.uspConsent) { + payload.ccpa = bidderRequest.uspConsent; + } + + function getOrtb2Keywords(ortb2Obj) { + const fields = ['site.keywords', 'site.content.keywords', 'user.keywords', 'app.keywords', 'app.content.keywords']; + let result = []; + + fields.forEach(path => { + let keyStr = deepAccess(ortb2Obj, path); + if (isStr(keyStr)) result.push(keyStr); + }); + return result; + } + + // get the ortb2 keywords data (if it exists) + let ortb2 = deepClone(bidderRequest && bidderRequest.ortb2); + let ortb2KeywordsList = getOrtb2Keywords(ortb2); + // get meta keywords data (if it exists) + let metaKeywords = document.getElementsByTagName('meta')['keywords']; + if (metaKeywords && metaKeywords.content) { + metaKeywords = metaKeywords.content.split(','); + } + for (let x = 0; x < validBidRequests.length; x++) { - let req = validBidRequests[x] + let req = validBidRequests[x]; + + let bidFloor = req.params.bidFloor ? req.params.bidFloor : null; + let bidFloorCur = req.params.bidFloor ? req.params.bidFloorCur : null; + + if (typeof req.getFloor === 'function') { + const floorInfo = req.getFloor({ + currency: CURRENCY, + mediaType: BANNER, + size: '*' + }); + if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + bidFloor = parseFloat(floorInfo.floor); + bidFloorCur = CURRENCY; + } + } + + // get param kewords (if it exists) + let paramsKeywords = req.params.keywords ? req.params.keywords.split(',') : []; + // merge all keywords + let keywords = ortb2KeywordsList.concat(paramsKeywords).concat(metaKeywords); payload.imps.push({ pub_id: req.params.pubId, + site_id: req.params.siteID ? req.params.siteID : null, + placement_id: req.params.placement ? req.params.placement : null, + position: req.params.position ? req.params.position : null, + bid_floor: bidFloor, + bid_floor_cur: bidFloorCur, + lat_long: req.params.latLong ? req.params.latLong : null, + inventory: req.params.inventory ? req.params.inventory : null, + visitor: req.params.visitor ? req.params.visitor : null, + keywords: keywords.join(','), zone_id: req.params.zoneId, bid_id: req.bidId, imp_id: req.transactionId, sizes: req.sizes, force_bid: req.params.forceBid, + coppa: config.getConfig('coppa') === true ? 1 : 0, media_types: deepAccess(req, 'mediaTypes') }); } @@ -62,7 +118,8 @@ export const spec = { return { method: 'POST', url: url, - data: JSON.stringify(payload) + data: JSON.stringify(payload), + bids: validBidRequests }; }, interpretResponse: function(serverResponse, bidRequest) { diff --git a/modules/resetdigitalBidAdapter.md b/modules/resetdigitalBidAdapter.md index 2f9f69b5e84..a368c7f5633 100644 --- a/modules/resetdigitalBidAdapter.md +++ b/modules/resetdigitalBidAdapter.md @@ -21,17 +21,43 @@ Video is supported but requires a publisher supplied renderer at this time. mediaTypes: { banner: { sizes: [[300,250]] + }, + + }, + bids: [ + { + bidder: "resetdigital", + params: { + pubId: "your-pub-id", + site_id: "your-site-id", + forceBid: true, + } } + ] + } + ]; + + + var videoAdUnits = [ + { + code: 'your-div', + mediaTypes: { + video: { + playerSize: [640, 480] + }, + }, bids: [ { bidder: "resetdigital", params: { pubId: "your-pub-id", - forceBid: true + site_id: "your-site-id", + forceBid: true, } } ] } ]; + ``` diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index bd53e9d5104..11d60e77ece 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,5 +1,12 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.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 '../src/polyfill.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { Renderer } from '../src/Renderer.js'; import { - _each, convertTypes, deepAccess, deepSetValue, @@ -11,14 +18,8 @@ import { logMessage, logWarn, mergeDeep, - parseSizesInput + parseSizesInput, _each } 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 '../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'; @@ -115,12 +116,14 @@ var sizeMap = { 257: '400x600', 258: '500x200', 259: '998x200', + 261: '480x480', 264: '970x1000', 265: '1920x1080', 274: '1800x200', 278: '320x500', 282: '320x400', 288: '640x380', + 524: '1x2', 548: '500x1000', 550: '980x480', 552: '300x200', @@ -137,17 +140,101 @@ var sizeMap = { 580: '505x656', 622: '192x160' }; + _each(sizeMap, (item, key) => sizeMap[item] = key); +export const converter = ortbConverter({ + request(buildRequest, imps, bidderRequest, context) { + const {bidRequests} = context; + const data = buildRequest(imps, bidderRequest, context); + data.cur = ['USD']; + data.test = config.getConfig('debug') ? 1 : 0; + deepSetValue(data, 'ext.prebid.cache', { + vastxml: { + returnCreative: rubiConf.returnVast === true + } + }); + + deepSetValue(data, 'ext.prebid.bidders', { + rubicon: { + integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION, + } + }); + + deepSetValue(data, 'ext.prebid.targeting.pricegranularity', getPriceGranularity(config)); + + let modules = (getGlobal()).installedModules; + if (modules && (!modules.length || modules.indexOf('rubiconAnalyticsAdapter') !== -1)) { + deepSetValue(data, 'ext.prebid.analytics', {'rubicon': {'client-analytics': true}}); + } + + addOrtbFirstPartyData(data, bidRequests); + + delete data?.ext?.prebid?.storedrequest; + + // floors + if (rubiConf.disableFloors === true) { + delete data.ext.prebid.floors; + } + + // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check + const haveFloorDataBidRequests = bidRequests.filter(bidRequest => typeof bidRequest.floorData === 'object'); + if (haveFloorDataBidRequests.length > 0) { + data.ext.prebid.floors = { enabled: false }; + } + return data; + }, + imp(buildImp, bidRequest, context) { + // skip banner-only requests + const bidRequestType = bidType(bidRequest); + if (bidRequestType.includes(BANNER) && bidRequestType.length == 1) return; + + const imp = buildImp(bidRequest, context); + imp.id = bidRequest.adUnitCode; + delete imp.banner; + if (config.getConfig('s2sConfig.defaultTtl')) { + imp.exp = config.getConfig('s2sConfig.defaultTtl'); + }; + bidRequest.params.position === 'atf' && (imp.video.pos = 1); + bidRequest.params.position === 'btf' && (imp.video.pos = 3); + delete imp.ext?.prebid?.storedrequest; + + setBidFloors(bidRequest, imp); + + return imp; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.meta.mediaType = deepAccess(bid, 'ext.prebid.type'); + const {bidRequest} = context; + if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.renderer = outstreamRenderer(bidResponse); + } + bidResponse.width = bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth'); + bidResponse.height = bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight'); + + if (deepAccess(bid, 'ext.bidder.rp.advid')) { + deepSetValue(bidResponse, 'meta.advertiserId', bid.ext.bidder.rp.advid); + } + return bidResponse; + }, + context: { + netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true + ttl: 300, + }, + processors: pbsExtensions +}); + export const spec = { code: 'rubicon', gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * @param {object} bid * @return boolean */ isBidRequestValid: function (bid) { + let valid = true; if (typeof bid.params !== 'object') { return false; } @@ -159,15 +246,16 @@ export const spec = { return false } } - let bidFormat = bidType(bid, true); + let bidFormats = bidType(bid, true); // bidType is undefined? Return false - if (!bidFormat) { + if (!bidFormats.length) { return false; - } else if (bidFormat === 'video') { // bidType is video, make sure it has required params - return hasValidVideoParams(bid); + } else if (bidFormats.includes(VIDEO)) { // bidType is video, make sure it has required params + valid = hasValidVideoParams(bid); } - // bidType is banner? return true - return true; + const hasBannerOrNativeMediaType = [BANNER, NATIVE].filter(mediaType => bidFormats.includes(mediaType)).length > 0; + if (!hasBannerOrNativeMediaType) return valid; + return valid && hasBannerOrNativeMediaType; }, /** * @param {BidRequest[]} bidRequests @@ -177,166 +265,57 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { // separate video bids because the requests are structured differently let requests = []; - const videoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'video').map(bidRequest => { - bidRequest.startTime = new Date().getTime(); - - const data = { - id: bidRequest.transactionId, - test: config.getConfig('debug') ? 1 : 0, - cur: ['USD'], - source: { - tid: bidRequest.transactionId - }, - tmax: bidderRequest.timeout, - imp: [{ - exp: config.getConfig('s2sConfig.defaultTtl'), - id: bidRequest.adUnitCode, - secure: 1, - ext: { - [bidRequest.bidder]: bidRequest.params - }, - video: deepAccess(bidRequest, 'mediaTypes.video') || {} - }], - ext: { - prebid: { - channel: { - name: 'pbjs', - version: $$PREBID_GLOBAL$$.version - }, - cache: { - vastxml: { - returnCreative: rubiConf.returnVast === true - } - }, - targeting: { - includewinners: true, - // includebidderkeys always false for openrtb - includebidderkeys: false, - pricegranularity: getPriceGranularity(config) - }, - bidders: { - rubicon: { - integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION - } - } - } - } - } - - // Add alias if it is there - if (bidRequest.bidder !== 'rubicon') { - data.ext.prebid.aliases = { - [bidRequest.bidder]: 'rubicon' - } - } - - let modules = (getGlobal()).installedModules; - if (modules && (!modules.length || modules.indexOf('rubiconAnalyticsAdapter') !== -1)) { - deepSetValue(data, 'ext.prebid.analytics', {'rubicon': {'client-analytics': true}}); - } - - let bidFloor; - if (typeof bidRequest.getFloor === 'function' && !rubiConf.disableFloors) { - let floorInfo; - try { - floorInfo = bidRequest.getFloor({ - currency: 'USD', - mediaType: 'video', - size: parseSizes(bidRequest, 'video') - }); - } catch (e) { - logError('Rubicon: 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; - } - - // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check - if (typeof bidRequest.floorData === 'object') { - data.ext.prebid.floors = { enabled: false }; - } - - // if value is set, will overwrite with same value - data.imp[0].ext[bidRequest.bidder].video.size_id = determineRubiconVideoSizeId(bidRequest) - - appendSiteAppDevice(data, bidRequest, bidderRequest); - - addVideoParameters(data, bidRequest); - - 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); - } - - const eids = deepAccess(bidderRequest, 'bids.0.userIdAsEids'); - if (eids && eids.length) { - deepSetValue(data, 'user.ext.eids', eids); - } - - // set user.id value from config value - const configUserId = config.getConfig('user.id'); - if (configUserId) { - deepSetValue(data, 'user.id', configUserId); - } - - if (config.getConfig('coppa') === true) { - deepSetValue(data, 'regs.coppa', 1); - } - - if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { - deepSetValue(data, 'source.ext.schain', bidRequest.schain); - } - - const multibid = config.getConfig('multibid'); - if (multibid) { - deepSetValue(data, 'ext.prebid.multibid', multibid.reduce((result, i) => { - let obj = {}; - - Object.keys(i).forEach(key => { - obj[key.toLowerCase()] = i[key]; - }); - - result.push(obj); - - return result; - }, [])); - } - - applyFPD(bidRequest, VIDEO, data); - - // set ext.prebid.auctiontimestamp using auction time - deepSetValue(data.imp[0], 'ext.prebid.auctiontimestamp', bidderRequest.auctionStart); + let filteredHttpRequest = []; + let filteredRequests; + + filteredRequests = bidRequests.filter(req => { + const mediaTypes = bidType(req) || []; + const { length } = mediaTypes; + const { bidonmultiformat, video } = req.params || {}; + + return ( + // if there's just one mediaType and it's video or native, just send it! + (length === 1 && (mediaTypes.includes(VIDEO) || mediaTypes.includes(NATIVE))) || + // if it's two mediaTypes, and they don't contain banner, send to PBS both native & video + (length === 2 && !mediaTypes.includes(BANNER)) || + // if it contains the video param and the Video mediaType, send Video to PBS (not native!) + (video && mediaTypes.includes(VIDEO)) || + // if bidonmultiformat is on, send everything to PBS + (bidonmultiformat && (mediaTypes.includes(VIDEO) || mediaTypes.includes(NATIVE))) + ) + }); - // set storedrequests to undefined so not sent to PBS - // top level and imp level both 'ext.prebid' objects are set above so no exception thrown here - data.ext.prebid.storedrequest = undefined; - data.imp[0].ext.prebid.storedrequest = undefined; + if (filteredRequests && filteredRequests.length) { + const data = converter.toORTB({bidRequests: filteredRequests, bidderRequest}); - return { + filteredHttpRequest.push({ method: 'POST', url: `https://${rubiConf.videoHost || 'prebid-server'}.rubiconproject.com/openrtb2/auction`, data, - bidRequest - } - }); + bidRequest: filteredRequests + }); + } + const bannerBidRequests = bidRequests.filter((req) => { + const mediaTypes = bidType(req) || []; + const {bidonmultiformat, video} = req.params || {}; + return ( + // Send to fastlane if: it must include BANNER and... + mediaTypes.includes(BANNER) && ( + // if it's just banner + (mediaTypes.length === 1) || + // if bidonmultiformat is true + bidonmultiformat || + // if bidonmultiformat is false and there's no video parameter + (!bidonmultiformat && !video) || + // if there's video parameter, but there's no video mediatype + (!bidonmultiformat && video && !mediaTypes.includes(VIDEO)) + ) + ); + }); if (config.getConfig('rubicon.singleRequest') !== true) { // bids are not grouped if single request mode is not enabled - requests = videoRequests.concat(bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner').map(bidRequest => { + requests = filteredHttpRequest.concat(bannerBidRequests.map(bidRequest => { const bidParams = spec.createSlotParams(bidRequest, bidderRequest); return { method: 'GET', @@ -351,8 +330,7 @@ export const spec = { } else { // single request requires bids to be grouped by site id into a single request // note: groupBy wasn't used because deep property access was needed - const nonVideoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner'); - const groupedBidRequests = nonVideoRequests.reduce((groupedBids, bid) => { + const groupedBidRequests = bannerBidRequests.reduce((groupedBids, bid) => { (groupedBids[bid.params['siteId']] = groupedBids[bid.params['siteId']] || []).push(bid); return groupedBids; }, {}); @@ -361,7 +339,7 @@ export const spec = { const SRA_BID_LIMIT = 10; // multiple requests are used if bids groups have more than 10 bids - requests = videoRequests.concat(Object.keys(groupedBidRequests).reduce((aggregate, bidGroupKey) => { + requests = filteredHttpRequest.concat(Object.keys(groupedBidRequests).reduce((aggregate, bidGroupKey) => { // for each partioned bidGroup, append a bidRequest to requests list partitionArray(groupedBidRequests[bidGroupKey], SRA_BID_LIMIT).forEach(bidsInGroup => { const combinedSlotParams = spec.combineSlotUrlParams(bidsInGroup.map(bidRequest => { @@ -415,7 +393,6 @@ export const spec = { 'tk_flint', 'x_source.tid', 'l_pb_bid_id', - 'x_source.pchain', 'p_screen_res', 'rp_floor', 'rp_secure', @@ -489,7 +466,6 @@ export const spec = { '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, 'p_geo.latitude': isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4), @@ -614,106 +590,36 @@ export const spec = { /** * @param {*} responseObj - * @param {BidRequest|Object.} bidRequest - if request was SRA the bidRequest argument will be a keyed BidRequest array object, + * @param {BidRequest|Object.} request - if request was SRA the bidRequest argument will be a keyed BidRequest array object, * non-SRA responses return a plain BidRequest object * @return {Bid[]} An array of bids which */ - interpretResponse: function (responseObj, {bidRequest}) { + interpretResponse: function (responseObj, request) { responseObj = responseObj.body; + const {data} = request; // check overall response if (!responseObj || typeof responseObj !== 'object') { return []; } - // video response from PBS Java openRTB + // Response from PBS Java openRTB if (responseObj.seatbid) { const responseErrors = deepAccess(responseObj, 'ext.errors.rubicon'); if (Array.isArray(responseErrors) && responseErrors.length > 0) { logWarn('Rubicon: Error in video response'); } - const bids = []; - responseObj.seatbid.forEach(seatbid => { - (seatbid.bid || []).forEach(bid => { - let bidObject = { - requestId: bidRequest.bidId, - currency: responseObj.cur || 'USD', - creativeId: bid.crid, - cpm: bid.price || 0, - bidderCode: seatbid.seat, - ttl: 300, - netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true - width: bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth'), - height: bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight'), - }; - - if (bid.id) { - bidObject.seatBidId = bid.id; - } - - if (bid.dealid) { - bidObject.dealId = bid.dealid; - } - - if (bid.adomain) { - deepSetValue(bidObject, 'meta.advertiserDomains', Array.isArray(bid.adomain) ? bid.adomain : [bid.adomain]); - } - - if (deepAccess(bid, 'ext.bidder.rp.advid')) { - deepSetValue(bidObject, 'meta.advertiserId', bid.ext.bidder.rp.advid); - } - - let serverResponseTimeMs = deepAccess(responseObj, 'ext.responsetimemillis.rubicon'); - if (bidRequest && serverResponseTimeMs) { - bidRequest.serverResponseTimeMs = serverResponseTimeMs; - } - - if (deepAccess(bid, 'ext.prebid.type') === VIDEO) { - bidObject.mediaType = VIDEO; - deepSetValue(bidObject, 'meta.mediaType', VIDEO); - const extPrebidTargeting = deepAccess(bid, 'ext.prebid.targeting'); - - // If ext.prebid.targeting exists, add it as a property value named 'adserverTargeting' - if (extPrebidTargeting && typeof extPrebidTargeting === 'object') { - bidObject.adserverTargeting = extPrebidTargeting; - } - - // try to get cache values from 'response.ext.prebid.cache.js' - // else try 'bid.ext.prebid.targeting' as fallback - if (bid.ext.prebid.cache && typeof bid.ext.prebid.cache.vastXml === 'object' && bid.ext.prebid.cache.vastXml.cacheId && bid.ext.prebid.cache.vastXml.url) { - bidObject.videoCacheKey = bid.ext.prebid.cache.vastXml.cacheId; - bidObject.vastUrl = bid.ext.prebid.cache.vastXml.url; - } else if (extPrebidTargeting && extPrebidTargeting.hb_uuid && extPrebidTargeting.hb_cache_host && extPrebidTargeting.hb_cache_path) { - bidObject.videoCacheKey = extPrebidTargeting.hb_uuid; - // build url using key and cache host - bidObject.vastUrl = `https://${extPrebidTargeting.hb_cache_host}${extPrebidTargeting.hb_cache_path}?uuid=${extPrebidTargeting.hb_uuid}`; - } - - if (bid.adm) { bidObject.vastXml = bid.adm; } - if (bid.nurl) { bidObject.vastUrl = bid.nurl; } - if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } - - const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); - if (videoContext.toLowerCase() === 'outstream') { - bidObject.renderer = outstreamRenderer(bidObject); - } - } else { - logWarn('Rubicon: video response received non-video media type'); - } - - bids.push(bidObject); - }); - }); - + const bids = converter.fromORTB({request: data, response: responseObj}).bids; return bids; } let ads = responseObj.ads; let lastImpId; let multibid = 0; + const {bidRequest} = request; // video ads array is wrapped in an object - if (typeof bidRequest === 'object' && !Array.isArray(bidRequest) && bidType(bidRequest) === 'video' && typeof ads === 'object') { + if (typeof bidRequest === 'object' && !Array.isArray(bidRequest) && bidType(bidRequest).includes(VIDEO) && typeof ads === 'object') { ads = ads[bidRequest.adUnitCode]; } @@ -779,7 +685,6 @@ export const spec = { } else { logError(`Rubicon: bidRequest undefined at index position:${i}`, bidRequest, responseObj); } - return bids; }, []).sort((adA, adB) => { return (adB.cpm || 0.0) - (adA.cpm || 0.0); @@ -918,7 +823,7 @@ function outstreamRenderer(rtbBid) { function parseSizes(bid, mediaType) { let params = bid.params; - if (mediaType === 'video') { + if (mediaType === VIDEO) { let size = []; if (params.video && params.video.playerWidth && params.video.playerHeight) { size = [ @@ -948,65 +853,6 @@ function parseSizes(bid, mediaType) { return masSizeOrdering(sizes); } -/** - * @param {Object} data - * @param bidRequest - * @param bidderRequest - */ -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'); - } - // Add language to site and device objects if there - if (bidRequest.params.video.language) { - ['site', 'device'].forEach(function(param) { - if (data[param]) { - if (param === 'site') { - data[param].content = Object.assign({language: bidRequest.params.video.language}, data[param].content) - } else { - data[param] = Object.assign({language: bidRequest.params.video.language}, data[param]) - } - } - }); - } -} - -/** - * @param {Object} data - * @param {BidRequest} bidRequest - */ -function addVideoParameters(data, bidRequest) { - if (typeof data.imp[0].video === 'object' && data.imp[0].video.skip === undefined) { - data.imp[0].video.skip = bidRequest.params.video.skip; - } - if (typeof data.imp[0].video === 'object' && data.imp[0].video.skipafter === undefined) { - data.imp[0].video.skipafter = bidRequest.params.video.skipdelay; - } - // video.pos can already be specified by adunit.mediatypes.video.pos. - // but if not, it might be specified in the params - if (typeof data.imp[0].video === 'object' && data.imp[0].video.pos === undefined) { - if (bidRequest.params.position === 'atf') { - data.imp[0].video.pos = 1; - } else if (bidRequest.params.position === 'btf') { - data.imp[0].video.pos = 3; - } - } - - const size = parseSizes(bidRequest, 'video') - data.imp[0].video.w = size[0] - data.imp[0].video.h = size[1] -} - function applyFPD(bidRequest, mediaType, data) { const BID_FPD = { user: {ext: {data: {...bidRequest.params.visitor}}}, @@ -1117,11 +963,15 @@ function mapSizes(sizes) { export function classifiedAsVideo(bidRequest) { let isVideo = typeof deepAccess(bidRequest, `mediaTypes.${VIDEO}`) !== 'undefined'; let isBanner = typeof deepAccess(bidRequest, `mediaTypes.${BANNER}`) !== 'undefined'; + let isBidOnMultiformat = typeof deepAccess(bidRequest, `params.bidonmultiformat`) !== 'undefined'; let isMissingVideoParams = typeof deepAccess(bidRequest, 'params.video') !== 'object'; // If an ad has both video and banner types, a legacy implementation allows choosing video over banner // based on whether or not there is a video object defined in the params // Given this legacy implementation, other code depends on params.video being defined + // if it's bidonmultiformat, we don't care of the video object + if (isVideo && isBidOnMultiformat) return true; + if (isBanner && isMissingVideoParams) { isVideo = false; } @@ -1132,13 +982,14 @@ export function classifiedAsVideo(bidRequest) { } /** - * Determine bidRequest mediaType + * Determine bidRequest mediaTypes. All mediaTypes must be correct. If one fails, all the others will fail too. * @param bid the bid to test - * @param log whether we should log errors/warnings for invalid bids - * @returns {string|undefined} Returns 'video' or 'banner' if resolves to a type, or undefined otherwise (invalid). + * @param log boolean. whether we should log errors/warnings for invalid bids + * @returns {string|undefined} Returns an array containing one of 'video' or 'banner' or 'native' if resolves to a type. */ function bidType(bid, log = false) { // Is it considered video ad unit by rubicon + let bidTypes = []; if (classifiedAsVideo(bid)) { // Removed legacy mediaType support. new way using mediaTypes.video object is now required // We require either context as instream or outstream @@ -1146,37 +997,43 @@ function bidType(bid, log = false) { if (log) { logError('Rubicon: mediaTypes.video.context must be outstream or instream'); } - return; + return bidTypes; } // we require playerWidth and playerHeight to come from one of params.playerWidth/playerHeight or mediaTypes.video.playerSize or adUnit.sizes - if (parseSizes(bid, 'video').length < 2) { + if (parseSizes(bid, VIDEO).length < 2) { if (log) { logError('Rubicon: could not determine the playerSize of the video'); } - return; + return bidTypes; } if (log) { logMessage('Rubicon: making video request for adUnit', bid.adUnitCode); } - return 'video'; - } else { + bidTypes.push(VIDEO); + } + if (typeof deepAccess(bid, `mediaTypes.${NATIVE}`) !== 'undefined') { + bidTypes.push(NATIVE); + } + + if (typeof deepAccess(bid, `mediaTypes.${BANNER}`) !== 'undefined') { // we require banner sizes to come from one of params.sizes or mediaTypes.banner.sizes or adUnit.sizes, in that order // if we cannot determine them, we reject it! - if (parseSizes(bid, 'banner').length === 0) { + if (parseSizes(bid, BANNER).length === 0) { if (log) { logError('Rubicon: could not determine the sizes for banner request'); } - return; + return bidTypes; } // everything looks good for banner so lets do it if (log) { logMessage('Rubicon: making banner request for adUnit', bid.adUnitCode); } - return 'banner'; + bidTypes.push(BANNER); } + return bidTypes; } export const resetRubiConf = () => rubiConf = {}; @@ -1306,4 +1163,64 @@ export function resetUserSync() { hasSynced = false; } +/** + * Sets the floor on the bidRequest. imp.bidfloor and imp.bidfloorcur + * should be already set by the conversion library. if they're not, + * or invalid, try to read from params.floor. + * @param {*} bidRequest + * @param {*} imp + */ +function setBidFloors(bidRequest, imp) { + if (imp.bidfloorcur != 'USD') { + delete imp.bidfloor; + delete imp.bidfloorcur; + } + + if (!imp.bidfloor) { + let bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + + if (!isNaN(bidFloor)) { + imp.bidfloor = bidFloor; + imp.bidfloorcur = 'USD'; + } + } +} + +function addOrtbFirstPartyData(data, nonBannerRequests) { + let fpd = {}; + const keywords = new Set(); + nonBannerRequests.forEach(bidRequest => { + const bidFirstPartyData = { + user: {ext: {data: {...bidRequest.params.visitor}}}, + site: {ext: {data: {...bidRequest.params.inventory}}} + }; + + // add site.content.language + const impThatHasVideoLanguage = data.imp.find(imp => imp.ext?.prebid?.bidder?.rubicon?.video?.language); + if (impThatHasVideoLanguage) { + bidFirstPartyData.site.content = { + language: impThatHasVideoLanguage.ext?.prebid?.bidder?.rubicon?.video?.language + } + } + + if (bidRequest.params.keywords) { + const keywordsArray = (!Array.isArray(bidRequest.params.keywords) ? bidRequest.params.keywords.split(',') : bidRequest.params.keywords); + keywordsArray.forEach(keyword => keywords.add(keyword)); + } + fpd = mergeDeep(fpd, bidRequest.ortb2 || {}, bidFirstPartyData); + + // add user.id from config. + // NOTE: This is DEPRECATED. user.id should come from setConfig({ortb2}). + const configUserId = config.getConfig('user.id'); + fpd.user.id = fpd.user.id || configUserId; + }); + + mergeDeep(data, fpd); + + if (keywords && keywords.size) { + deepSetValue(data, 'site.keywords', Array.from(keywords.values()).join(',')); + } + delete data?.ext?.prebid?.storedrequest; +} + registerBidder(spec); diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index f9ce3a450cf..4fa95e4ba51 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -38,7 +38,9 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { let adUnits = []; - const pageURL = validBidRequests[0].params.contentPageUrl || bidderRequest.refererInfo.referer; + const pageURL = validBidRequests[0].params.contentPageUrl || + bidderRequest.refererInfo.canonicalUrl || + deepAccess(window, 'location.href'); const isStage = !!validBidRequests[0].params.stage; const isViralize = !!validBidRequests[0].params.unitId; const isOutstream = deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; @@ -50,6 +52,7 @@ export const spec = { const defaultSchain = validBidRequests[0].schain || {}; const consentData = bidderRequest.gdprConsent || {}; + const uspConsent = bidderRequest.uspConsent || ''; const gdprConsent = { apiVersion: consentData.apiVersion || 2, gdprApplies: consentData.gdprApplies || 0, @@ -104,6 +107,7 @@ export const spec = { height: size[1] }; rBid.gdprConsent = gdprConsent; + rBid.uspConsent = uspConsent; } return rBid; @@ -138,6 +142,7 @@ export const spec = { 'bidRequests': adUnits, 'context': { 'gdprConsent': gdprConsent, + 'uspConsent': uspConsent, 'schain': defaultSchain, 'pageURL': QA.pageURL || encodeURIComponent(pageURL) } diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 18ce56c7a80..8c2d9d4ff17 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -8,7 +8,7 @@ import CONSTANTS from '../src/constants.json'; const { NATIVE_IMAGE_TYPES } = CONSTANTS; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.7' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.8' const CURRENCY = 'USD'; const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { @@ -63,6 +63,11 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { deepSetValue(requestTemplate, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + if (ortb2.regs?.gpp !== undefined) { + deepSetValue(requestTemplate, 'regs.ext.gpp', ortb2.regs.gpp); + deepSetValue(requestTemplate, 'regs.ext.gpp_sid', ortb2.regs.gpp_sid); + } + if (deepAccess(bidRequest, 'params.app')) { const geo = deepAccess(bidRequest, 'params.app.geo'); deepSetValue(requestTemplate, 'device.geo', geo); diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index e5800e7cad0..89749aed433 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -17,7 +17,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.title && bid.native.image && bid.native.impressionTrackers); default: diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index 9f275a761c7..34cf9285909 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -57,6 +57,7 @@ export const spec = { const bidRequests = validBidRequests.map((validBidRequest) => { let video = deepAccess(validBidRequest, 'mediaTypes.video', false); let banner = deepAccess(validBidRequest, 'mediaTypes.banner', false); + let sizes = validBidRequest.params.sizes; let oneRequest = { endpointId: validBidRequest.params.endpointId, @@ -67,8 +68,16 @@ export const spec = { if (video) { oneRequest.video = video; + + if (sizes) { + oneRequest.video.sizes = sizes; + } } else if (banner) { oneRequest.banner = banner; + + if (sizes) { + oneRequest.banner.sizes = sizes; + } } return oneRequest diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index ffe2bef054c..acfa9fe7945 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -10,10 +10,9 @@ 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.7'; +const BIDDER_VERSION = '5.8'; const DEFAULT_CURRENCY = 'PLN'; const W = window; const { navigator } = W; @@ -23,6 +22,39 @@ const adSizesCalled = {}; const pageView = {}; var consentApiVersion; +/** + * Native asset mapping - we use constant id per type + * id > 10 indicates additional images + */ +var nativeAssetMap = { + title: 0, + cta: 1, + icon: 2, + image: 3, + body: 4, + sponsoredBy: 5 +}; + +/** + * return native asset type, based on asset id + * @param {int} id - native asset id + * @returns {string} asset type + */ +const getNativeAssetType = id => { + // id>10 will always be an image... + if (id > 10) { + return 'image'; + } + + // ...others should be decoded from nativeAssetMap + for (let assetName in nativeAssetMap) { + const assetId = nativeAssetMap[assetName]; + if (assetId === id) { + return assetName; + } + } +} + /** * Get preferred language of browser (i.e. user) * @returns {string} languageCode - ISO language code @@ -42,6 +74,16 @@ const getContentLanguage = () => { } }; +/** + * Get Bid parameters - returns bid params from Object, or 1el array + * @param {*} bidData - bid (bidWon), or array of bids (timeout) + * @returns {object} params object + */ +const unpackParams = (bidParams) => { + const result = isArray(bidParams) ? bidParams[0] : bidParams; + return result || {}; +} + /** * Get bid parameters for notification * @param {*} bidData - bid (bidWon), or array of bids (timeout) @@ -58,8 +100,7 @@ const getNotificationPayload = bidData => { } bids.forEach(bid => { const { adUnitCode, auctionId, cpm, creativeId, meta, params: bidParams, requestId, timeout } = bid; - let params = isArray(bidParams) ? bidParams[0] : bidParams; - params = params || {}; + const params = unpackParams(bidParams); // basic notification data const bidBasicData = { @@ -178,6 +219,46 @@ const applyGdpr = (bidderRequest, ortbRequest) => { } } +/** + * Get highest floorprice for a given adslot + * (sspBC adapter accepts one floor per imp) + * returns floor = 0 if getFloor() is not defined + * + * @param {object} slot bid request adslot + * @returns {float} floorprice + */ +const getHighestFloor = (slot) => { + const currency = getCurrency(); + let result = { floor: 0, currency }; + + if (typeof slot.getFloor === 'function') { + let bannerFloor = 0; + + if (slot.sizes.length) { + bannerFloor = slot.sizes.reduce(function (prev, next) { + const { floor: currentFloor = 0 } = slot.getFloor({ + mediaType: 'banner', + size: next, + currency + }); + return prev > currentFloor ? prev : currentFloor; + }, 0); + } + + const { floor: nativeFloor = 0 } = slot.getFloor({ + mediaType: 'native', currency + }); + + const { floor: videoFloor = 0 } = slot.getFloor({ + mediaType: 'video', currency + }); + + result.floor = Math.max(bannerFloor, nativeFloor, videoFloor); + } + + return result; +}; + /** * Get currency (either default or adserver) * @returns {string} currency name @@ -226,71 +307,111 @@ const mapBanner = slot => { * @param {object} paramValue Native parameter value * @returns {object} native asset object that conforms to ortb native ads spec */ -const mapAsset = (paramName, paramValue) => { - let asset; - switch (paramName) { - case 'title': - asset = { - id: 0, - required: paramValue.required, - title: { len: paramValue.len } - } - break; - case 'cta': - asset = { - id: 1, - required: paramValue.required, - data: { type: 12 } - } - break; - case 'icon': - asset = { - id: 2, - required: paramValue.required, - img: { type: 1, w: paramValue.sizes[0], h: paramValue.sizes[1] } - } - break; - case 'image': - asset = { - id: 3, - required: paramValue.required, - img: { type: 3, w: paramValue.sizes[0], h: paramValue.sizes[1] } - } - break; - case 'body': - asset = { - id: 4, - required: paramValue.required, - data: { type: 2 } - } - break; - case 'sponsoredBy': - asset = { - id: 5, - required: paramValue.required, - data: { type: 1 } - } - break; +var mapAsset = function mapAsset(paramName, paramValue) { + const { required, sizes, wmin, hmin, len } = paramValue; + var id = nativeAssetMap[paramName]; + var assets = []; + + if (id !== undefined) { + switch (paramName) { + case 'title': + assets.push({ + id: id, + required: required, + title: { + len: len + } + }); + break; + + case 'cta': + assets.push({ + id: id, + required: required, + data: { + type: 12 + } + }); + break; + + case 'icon': + assets.push({ + id: id, + required: required, + img: { + type: 1, + w: sizes && sizes[0], + h: sizes && sizes[1] + } + }); + break; + + case 'image': + var hasMultipleImages = sizes && Array.isArray(sizes[0]); + var imageSizes = hasMultipleImages ? sizes : [sizes]; + + for (var i = 0; i < imageSizes.length; i++) { + assets.push({ + id: i > 0 ? 10 + i : id, + required: required, + img: { + type: 3, + w: imageSizes[i][0], + h: imageSizes[i][1], + wmin: wmin, + hmin: hmin + } + }); + } + + break; + + case 'body': + assets.push({ + id: id, + required: required, + data: { + type: 2 + } + }); + break; + + case 'sponsoredBy': + assets.push({ + id: id, + required: required, + data: { + type: 1 + } + }); + break; + } } - return asset; -} + + return assets; +}; /** * @param {object} slot Ad Unit Params by Prebid * @returns {object} native object that conforms to ortb native ads spec */ -const mapNative = slot => { +const mapNative = (slot) => { const native = deepAccess(slot, 'mediaTypes.native'); - let assets; if (native) { - const nativeParams = Object.keys(native); - assets = []; - nativeParams.forEach(par => { - const newAsset = mapAsset(par, native[par]); - if (newAsset) { assets.push(newAsset) }; + var nativeParams = Object.keys(native); + var assets = []; + nativeParams.forEach(function (par) { + var newAssets = mapAsset(par, native[par]); + assets = assets.concat(newAssets); }); + return { + request: JSON.stringify({ + native: { + assets: assets + } + }) + }; } - return assets ? { request: JSON.stringify({ native: { assets } }) } : undefined; } var mapVideo = (slot, videoFromBid) => { @@ -346,41 +467,18 @@ const mapImpression = slot => { const imp = { id: id && siteId ? id.padStart(3, '0') : 'bidid-' + bidId, banner: mapBanner(slot), - native: mapNative(slot), + native: mapNative(slot, bidId), video: mapVideo(slot, video), tagid: adUnitCode, ext, }; // Check floorprices for this imp - const currency = getCurrency(); - if (typeof slot.getFloor === 'function') { - var bannerFloor = 0; - var nativeFloor = 0; - var videoFloor = 0; // sspBC adapter accepts only floor per imp - check for maximum value for requested ad types and sizes - - if (slot.sizes.length) { - bannerFloor = slot.sizes.reduce(function (prev, next) { - var currentFloor = slot.getFloor({ - mediaType: 'banner', - size: next, - currency - }).floor; - return prev > currentFloor ? prev : currentFloor; - }, 0); - } + const { floor, currency } = getHighestFloor(slot); - nativeFloor = slot.getFloor({ - mediaType: 'native', currency - }); - videoFloor = slot.getFloor({ - mediaType: 'video', currency - }); - imp.bidfloor = Math.max(bannerFloor, nativeFloor, videoFloor); - } else { - imp.bidfloor = 0; - } + imp.bidfloor = floor; imp.bidfloorcur = currency; + return imp; } @@ -395,50 +493,51 @@ const isNativeAd = bid => { return bid.admNative || (bid.adm && bid.adm.match(xmlTester)); } -const parseNative = nativeData => { - const result = {}; - nativeData.assets.forEach(asset => { - const id = parseInt(asset.id); - switch (id) { - case 0: - result.title = asset.title.text; - break; - case 1: - result.cta = asset.data.value; - break; - case 2: - result.icon = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h, - }; - break; - case 3: - result.image = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h, - }; - break; - case 4: - result.body = asset.data.value; - break; - case 5: - result.sponsoredBy = asset.data.value; - break; +const parseNative = (nativeData) => { + const { link = {}, imptrackers: impressionTrackers, jstracker } = nativeData; + const { url: clickUrl, clicktrackers: clickTrackers = [] } = link; - default: - logWarn('Unrecognized native asset', asset); + const result = { + clickUrl, + clickTrackers, + impressionTrackers, + javascriptTrackers: isArray(jstracker) ? jstracker : jstracker && [jstracker], + }; + + nativeData.assets.forEach(asset => { + const { id, img = {}, title = {}, data = {} } = asset; + const { w: imgWidth, h: imgHeight, url: imgUrl, type: imgType } = img; + const { type: dataType, value: dataValue } = data; + const { text: titleText } = title; + const detectedType = getNativeAssetType(id); + if (titleText) { + result.title = titleText; + } + if (imgUrl) { + // image or icon + const thisImage = { + url: imgUrl, + width: imgWidth, + height: imgHeight, + }; + if (imgType === 3 || detectedType === 'image') { + result.image = thisImage; + } else if (imgType === 1 || detectedType === 'icon') { + result.icon = thisImage; + } + } + if (dataValue) { + // call-to-action, sponsored-by or body + if (dataType === 1 || detectedType === 'sponsoredBy') { + result.sponsoredBy = dataValue; + } else if (dataType === 2 || detectedType === 'body') { + result.body = dataValue; + } else if (dataType === 12 || detectedType === 'cta') { + result.cta = dataValue; + } } }); - result.clickUrl = nativeData.link.url; - result.impressionTrackers = nativeData.imptrackers; - if (isArray(nativeData.jstracker)) { - result.javascriptTrackers = nativeData.jstracker; - } else if (nativeData.jstracker) { - result.javascriptTrackers = [nativeData.jstracker]; - } return result; } @@ -505,10 +604,6 @@ const renderCreative = (site, auctionId, bid, seat, request) => { window.requestPVID = "${pageView.id}"; `; - if (gam) { - adcode += `window.gam = ${JSON.stringify(gam)};`; - } - adcode += ` @@ -550,7 +645,7 @@ const spec = { const payload = { id: bidderRequest.auctionId, site: { - id: siteId, + id: siteId ? `${siteId}` : undefined, publisher: publisherId ? { id: publisherId } : undefined, page, domain, @@ -586,7 +681,7 @@ const spec = { const { bidderRequest } = request; const response = serverResponse.body; const bids = []; - const site = JSON.parse(request.data).site; // get page and referer data from request + let site = JSON.parse(request.data).site; // get page and referer data from request site.sn = response.sn || 'mc_adapter'; // WPM site name (wp_sn) pageView.sn = site.sn; // store site_name (for syncing and notifications) let seat; @@ -597,39 +692,35 @@ const spec = { 'bidid-' prefix indicates oneCode (parameterless) request and response */ response.seatbid.forEach(seatbid => { - let creativeCache; seat = seatbid.seat; seatbid.bid.forEach(serverBid => { // get data from bid response - const { adomain, crid = `mcad_${bidderRequest.auctionId}_${site.slot}`, impid, exp = 300, ext, price, w, h } = serverBid; + const { adomain, crid = `mcad_${bidderRequest.auctionId}_${site.slot}`, impid, exp = 300, ext = {}, price, w, h } = serverBid; const bidRequest = bidderRequest.bids.filter(b => { - const { bidId, params = {} } = b; + const { bidId, params: requestParams = {} } = b; + const params = unpackParams(requestParams); const { id, siteId } = params; const currentBidId = id && siteId ? id : 'bidid-' + bidId; return currentBidId === impid; })[0]; - // get data from linked bidRequest - const { bidId, params } = bidRequest || {}; - - // get slot id for current bid - site.slot = params && params.id; - - if (ext) { - /* - 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, pubid, adlabel, cache } = ext; - site.id = siteid || site.id; - site.slot = slotid || site.slot; - site.publisherId = pubid; - site.adLabel = adlabel; - creativeCache = cache; - } + // get bidid from linked bidRequest + const { bidId } = bidRequest || {}; + + // get ext data from bid + const { siteid = site.id, slotid = site.slot, pubid, adlabel, cache: creativeCache, vurls = [] } = ext; + + // update site data + site = { + ...site, + ...{ + id: siteid, + slot: slotid, + publisherId: pubid, + adLabel: adlabel + } + }; if (bidRequest && site.id && !strIncludes(site.id, 'bidid')) { // found a matching request; add this bid @@ -652,6 +743,7 @@ const spec = { pricepl: ext && ext.pricepl, }, netRevenue: true, + vurls, }; // mediaType and ad data for instream / native / banner @@ -671,24 +763,6 @@ const spec = { 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 = '', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + }, + { + 'id': 'bidId2', + 'impid': 'bidId2', + 'price': 0.1, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 386046, + 'auction_id': 517067951122925501, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'kulturemedia' + } + ], + 'ext': { + 'usersync': { + 'sovrn': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlsovrn', + 'type': 'iframe' + } + ] + }, + 'appnexus': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlappnexus', + 'type': 'pixel' + } + ] + } + }, + 'responsetimemillis': { + 'appnexus': 127 + } + } + } +}; + +const DEFAULT_NETWORK_ID = 1; + +describe('kulturemediaBidAdapter:', function () { + let videoBidRequest; + + const VIDEO_REQUEST = { + 'bidderCode': 'kulturemedia', + 'auctionId': 'e158486f-8c7f-472f-94ce-b0cbfbb50ab4', + 'bidderRequestId': '34feaad34lkj2', + 'bids': videoBidRequest, + 'auctionStart': 1520001292880, + 'timeout': 3000, + 'start': 1520001292884, + 'doneCbCallCount': 0, + 'refererInfo': { + 'numIframes': 1, + 'reachedTop': true, + 'referer': 'test.com' + } + }; + + beforeEach(function () { + videoBidRequest = { + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + } + }, + bidder: 'kulturemedia', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 134, + rewarded: 1, + placement: 1, + hp: 1, + inventoryid: 123 + }, + site: { + id: 1, + page: 'https://test.com', + referrer: 'http://test.com' + }, + publisherId: 'km123' + } + }; + }); + + describe('isBidRequestValid', function () { + context('basic validation', function () { + beforeEach(function () { + // Basic Valid BidRequest + this.bid = { + bidder: 'kulturemedia', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + }); + + it('should accept request if placementId and publisherId are passed', function () { + expect(spec.isBidRequestValid(this.bid)).to.be.true; + }); + + it('reject requests without params', function () { + this.bid.params = {}; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + + it('returns false when banner mediaType does not exist', function () { + this.bid.mediaTypes = {} + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + context('banner validation', function () { + it('returns true when banner sizes are defined', function () { + const bid = { + bidder: 'kulturemedia', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns false when banner sizes are invalid', function () { + const invalidSizes = [ + undefined, + '2:1', + 123, + 'test' + ]; + + invalidSizes.forEach((sizes) => { + const bid = { + bidder: 'kulturemedia', + mediaTypes: { + banner: { + sizes + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + }); + + context('video validation', function () { + beforeEach(function () { + // Basic Valid BidRequest + this.bid = { + bidder: 'kulturemedia', + mediaTypes: { + video: { + playerSize: [[300, 50]], + context: 'instream', + mimes: ['foo', 'bar'], + protocols: [1, 2] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + }); + + it('should return true (skip validations) when e2etest = true', function () { + this.bid.params = { + e2etest: true + }; + expect(spec.isBidRequestValid(this.bid)).to.equal(true); + }); + + it('returns false when video context is not defined', function () { + delete this.bid.mediaTypes.video.context; + + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + + it('returns false when video playserSize is invalid', function () { + const invalidSizes = [ + undefined, + '2:1', + 123, + 'test' + ]; + + invalidSizes.forEach((playerSize) => { + this.bid.mediaTypes.video.playerSize = playerSize; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + it('returns false when video mimes is invalid', function () { + const invalidMimes = [ + undefined, + 'test', + 1, + [] + ] + + invalidMimes.forEach((mimes) => { + this.bid.mediaTypes.video.mimes = mimes; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + + it('returns false when video protocols is invalid', function () { + const invalidMimes = [ + undefined, + 'test', + 1, + [] + ] + + invalidMimes.forEach((protocols) => { + this.bid.mediaTypes.video.protocols = protocols; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + }); + }); + + describe('buildRequests', function () { + context('when mediaType is banner', function () { + it('creates request data', function () { + let request = spec.buildRequests(BANNER_REQUEST.bidRequest, BANNER_REQUEST); + + expect(request).to.exist.and.to.be.a('object'); + const payload = JSON.parse(request.data); + expect(payload.imp[0]).to.have.property('id', BANNER_REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', BANNER_REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, BANNER_REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(BANNER_REQUEST.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + + it('should properly forward eids parameters', function () { + const req = Object.assign({}, BANNER_REQUEST); + req.bidRequest[0].userIdAsEids = [ + { + source: 'dummy.com', + uids: [ + { + id: 'd6d0a86c-20c6-4410-a47b-5cba383a698a', + atype: 1 + } + ] + }]; + let request = spec.buildRequests(req.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext.eids[0].source).to.equal('dummy.com'); + expect(payload.user.ext.eids[0].uids[0].id).to.equal('d6d0a86c-20c6-4410-a47b-5cba383a698a'); + expect(payload.user.ext.eids[0].uids[0].atype).to.equal(1); + }); + }); + + context('when mediaType is video', function () { + it('should create a POST request for every bid', function () { + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + expect(requests.method).to.equal('POST'); + expect(requests.url.trim()).to.equal(spec.ENDPOINT + '?pid=' + videoBidRequest.params.publisherId + '&nId=' + DEFAULT_NETWORK_ID); + }); + + it('should attach request data', function () { + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + const data = JSON.parse(requests.data); + const [width, height] = videoBidRequest.sizes; + const VERSION = '1.0.0'; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].bidfloor).to.equal(videoBidRequest.params.bidfloor); + expect(data.ext.prebidver).to.equal('$prebid.version$'); + expect(data.ext.adapterver).to.equal(spec.VERSION); + }); + + it('should set pubId to e2etest when bid.params.e2etest = true', function () { + videoBidRequest.params.e2etest = true; + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + expect(requests.method).to.equal('POST'); + expect(requests.url).to.equal(spec.ENDPOINT + '?pid=e2etest&nId=' + DEFAULT_NETWORK_ID); + }); + + it('should attach End 2 End test data', function () { + videoBidRequest.params.e2etest = true; + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + const data = JSON.parse(requests.data); + expect(data.imp[0].bidfloor).to.not.exist; + expect(data.imp[0].video.w).to.equal(640); + expect(data.imp[0].video.h).to.equal(480); + }); + }); + }); + + describe('interpretResponse', function () { + context('when mediaType is banner', function () { + it('have bids', function () { + let bids = spec.interpretResponse(RESPONSE, BANNER_REQUEST); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + validateBidOnIndex(1); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].impid); + expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); + expect(bids[index].meta).to.have.property('advertiserDomains', RESPONSE.body.seatbid[0].bid[index].adomain); + expect(bids[index]).to.have.property('ttl', 300); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, BANNER_REQUEST); + + expect(bids).to.be.empty; + }); + }); + + context('when mediaType is video', function () { + it('should return no bids if the response is not valid', function () { + const bidResponse = spec.interpretResponse({ + body: null + }, { + videoBidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "nurl" and "adm" are missing', function () { + const serverResponse = { + seatbid: [{ + bid: [{ + price: 6.01 + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + videoBidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "price" is missing', function () { + const serverResponse = { + seatbid: [{ + bid: [{ + adm: '' + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + videoBidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid video bid response with just "adm"', function () { + const serverResponse = { + id: '123', + seatbid: [{ + bid: [{ + id: 1, + adid: 123, + impid: 456, + crid: 2, + price: 6.01, + adm: '', + adomain: [ + 'kulturemedia.com' + ], + w: 640, + h: 480, + ext: { + prebid: { + type: 'video' + }, + } + }] + }], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + videoBidRequest + }); + let o = { + requestId: serverResponse.seatbid[0].bid[0].impid, + ad: '', + bidderCode: spec.code, + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].crid, + vastXml: serverResponse.seatbid[0].bid[0].adm, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true, + meta: { + advertiserDomains: ['kulturemedia.com'] + } + }; + expect(bidResponse[0]).to.deep.equal(o); + }); + + it('should default ttl to 300', function () { + const serverResponse = { + seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); + expect(bidResponse[0].ttl).to.equal(300); + }); + it('should not allow ttl above 3601, default to 300', function () { + videoBidRequest.params.video.ttl = 3601; + const serverResponse = { + seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); + expect(bidResponse[0].ttl).to.equal(300); + }); + it('should not allow ttl below 1, default to 300', function () { + videoBidRequest.params.video.ttl = 0; + const serverResponse = { + seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); + expect(bidResponse[0].ttl).to.equal(300); + }); + }); + }); + + describe('getUserSyncs', function () { + it('handles no parameters', function () { + let opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; + }); + it('returns non if sync is not allowed', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + + expect(opts).to.be.an('array').that.is.empty; + }); + + it('iframe sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['sovrn'].syncs[0].url); + }); + + it('pixel sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['appnexus'].syncs[0].url); + }); + + it('all sync enabled should return all results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(2); + }); + }); +}) +; diff --git a/test/spec/modules/lemmaDigitalBidAdapter_spec.js b/test/spec/modules/lemmaDigitalBidAdapter_spec.js new file mode 100644 index 00000000000..d50728dce3c --- /dev/null +++ b/test/spec/modules/lemmaDigitalBidAdapter_spec.js @@ -0,0 +1,623 @@ +import { expect } from 'chai'; +import { spec } from 'modules/lemmaDigitalBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +const constants = require('src/constants.json'); + +describe('lemmaDigitalBidAdapter', function () { + let bidRequests; + let videoBidRequests; + let bidResponses; + let videoBidResponse; + let schainConfig; + beforeEach(function () { + schainConfig = { + 'complete': 0, + 'nodes': [ + { + 'asi': 'mobupps.com', + 'sid': 'c74d97b01eae257e44aa9d5bade97baf5149', + 'rid': '79c25703ad5935b0b23b66d210dad1f3', + 'hp': 1 + }, + { + 'asi': 'lemmatechnologies.com', + 'sid': '975', + 'rid': 'a455157a-a1fb-11ed-a0e4-d08e79f7ace0', + 'hp': 1 + } + ] + }; + bidRequests = [{ + bidder: 'lemmadigital', + bidId: '22bddb28db77d', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD', + bidFloor: 1.3, + geo: { + lat: '12.3', + lon: '23.7', + }, + banner: { + w: 300, + h: 250, + }, + tmax: 300, + bcat: ['IAB-26'] + }, + sizes: [ + [300, 250], + [300, 600] + ], + schain: schainConfig + }]; + videoBidRequests = [{ + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'lemmadigital', + bidId: '22bddb28db77d', + params: { + pubId: 1001, + adunitId: 1, + bidFloor: 1.3, + tmax: 300, + bcat: ['IAB-26'], + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + }, + schain: schainConfig + }]; + bidResponses = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': '

lemma"Connecting Advertisers and Publishers directly"

', + 'adomain': ['amazon.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '22918', + 'crid': 'v55jutrh', + 'dealid': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'h': 250, + 'w': 300, + 'ext': {} + }] + }] + } + }; + videoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1', + 'adomain': ['amazon.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '22918', + 'crid': 'v55jutrh', + 'dealid': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'h': 250, + 'w': 300, + 'ext': {} + }] + }] + } + }; + }); + describe('implementation', function () { + describe('Bid validations', function () { + it('valid bid case', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + it('invalid bid case', function () { + let isValid = spec.isBidRequestValid(); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId not passed', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId is not number', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: '301', + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: adunitId is not passed', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: 1001 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: video bid request mimes is not passed', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1, + video: { + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + validBid.params.video.mimes = []; + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + }); + describe('Request formation', function () { + it('bidRequest check empty', function () { + let bidRequests = []; + let request = spec.buildRequests(bidRequests); + expect(request).to.equal(undefined); + }); + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let request = spec.buildRequests(bidRequests); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + it('bidRequest imp array check empty', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + data.imp = []; + expect(data.imp.length).to.equal(0); + }); + it('Endpoint checking', function () { + let request = spec.buildRequests(bidRequests); + expect(request.url).to.equal('https://bid.lemmadigital.com/lemma/servad?pid=1001&aid=1'); + expect(request.method).to.equal('POST'); + }); + it('Request params check', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal('1'); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + + it('Set sizes from mediaTypes object', function () { + let newBannerRequest = utils.deepClone(bidRequests); + delete newBannerRequest[0].sizes; + let request = spec.buildRequests(newBannerRequest); + let data = JSON.parse(request.data); + expect(data.sizes).to.equal(undefined); + }); + it('Check request banner object present', function () { + let newBannerRequest = utils.deepClone(bidRequests); + let request = spec.buildRequests(newBannerRequest); + let data = JSON.parse(request.data); + expect(data.banner).to.deep.equal(undefined); + }); + it('Check device, source object not present', function () { + let newBannerRequest = utils.deepClone(bidRequests); + delete newBannerRequest[0].schain; + let request = spec.buildRequests(newBannerRequest); + let data = JSON.parse(request.data); + delete data.device; + delete data.source; + expect(data.source).to.equal(undefined); + expect(data.device).to.equal(undefined); + }); + it('Set content from config, set site.content', function () { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.content).to.deep.equal(content); + sandbox.restore(); + }); + it('Set content from config, set app.content', function () { + let bidRequest = [{ + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1, + video: { + skippable: true, + minduration: 5, + maxduration: 30 + }, + app: { + id: 'e0977d04e6bafece57b4b6e93314f10a', + name: 'AMC', + bundle: 'com.roku.amc', + storeurl: 'https://channelstore.roku.com/details/12716/amc', + cat: [ + 'IAB-26' + ], + publisher: { + 'id': '975' + } + }, + } + }]; + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequest); + let data = JSON.parse(request.data); + expect(data.app.content).to.deep.equal(content); + sandbox.restore(); + }); + it('Set tmax from requestBids method', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.tmax).to.deep.equal(300); + }); + it('Request params check without mediaTypes object', function () { + let bidRequests = [{ + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(600); // height + }); + it('Request params check: without tagId', function () { + delete bidRequests[0].params.adunitId; + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal(undefined); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); + }); + it('Request params multi size format object check', function () { + let bidRequests = [{ + bidder: 'lemmadigital', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - size passed in adslot */ + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + /* case 2 - size passed in adslot as well as in sizes array */ + bidRequests[0].sizes = [ + [300, 600], + [300, 250] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 600], + [300, 250] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(600); // height + /* case 3 - size passed in sizes but not in adslot */ + bidRequests[0].params.adunitId = 1; + bidRequests[0].sizes = [ + [300, 250], + [300, 600] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(250); // height + }); + it('Request params currency check', function () { + let bidRequest = [{ + bidder: 'lemmadigital', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - + currency specified in adunits + output: imp[0] use currency specified in bidRequests[0].params.currency + */ + let request = spec.buildRequests(bidRequest); + let data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + /* case 2 - + currency specified in adunit + output: imp[0] use default currency - USD + */ + delete bidRequest[0].params.currency; + request = spec.buildRequests(bidRequest); + data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + it('Request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.source.ext.schain).to.deep.equal(videoBidRequests[0].schain); + }); + describe('setting imp.floor using floorModule', function () { + /* + Use the minimum value among floor from floorModule per mediaType + If params.bidFloor is set then take max(floor, min(floors from floorModule)) + set imp.bidfloor only if it is more than 0 + */ + + let newRequest; + let floorModuleTestData; + let getFloor = function (req) { + return floorModuleTestData[req.mediaType]; + }; + + beforeEach(() => { + floorModuleTestData = { + 'banner': { + 'currency': 'AUD', + 'floor': 1.50 + }, + 'video': { + 'currency': 'AUD', + 'floor': 2.00 + } + }; + newRequest = utils.deepClone(bidRequests); + newRequest[0].getFloor = getFloor; + }); + + it('bidfloor should be undefined if calculation is <= 0', function () { + floorModuleTestData.banner.floor = 0; // lowest of them all + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); + }); + + it('ignore floormodule o/p if floor is not number', function () { + floorModuleTestData.banner.floor = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('ignore floormodule o/p if currency is not matched', function () { + floorModuleTestData.banner.currency = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('bidFloor is not passed, use minimum from floorModule', function () { + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + + it('bidFloor is passed as 1, use min of floorModule as it is highest', function () { + newRequest[0].params.bidFloor = '1.0';// yes, we want it as a string + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + }); + describe('Response checking', function () { + it('should check for valid response values', function () { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].cpm).to.equal((bidResponses.body.seatbid[0].bid[0].price).toFixed(2)); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); + expect(response[0].currency).to.equal('USD'); + expect(response[0].netRevenue).to.equal(false); + expect(response[0].ttl).to.equal(300); + }); + it('should check for valid banner mediaType in request', function () { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + + expect(response[0].mediaType).to.equal('banner'); + }); + it('should check for valid video mediaType in request', function () { + let request = spec.buildRequests(videoBidRequests); + let response = spec.interpretResponse(videoBidResponse, request); + + expect(response[0].mediaType).to.equal('video'); + }); + }); + }); + describe('Video request params', function () { + let sandbox, utilsMock, newVideoRequest; + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + newVideoRequest = utils.deepClone(videoBidRequests); + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }); + + it('Video params from mediaTypes and params obj of bid are not present', function () { + delete newVideoRequest[0].mediaTypes.video; + delete newVideoRequest[0].params.video; + let request = spec.buildRequests(newVideoRequest); + expect(request).to.equal(undefined); + }); + + it('Should consider video params from mediaType object of bid', function () { + delete newVideoRequest[0].params.video; + + let request = spec.buildRequests(newVideoRequest); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.imp[0]['video']['battr']).to.equal(undefined); + }); + }); + describe('getUserSyncs', function () { + const syncurl_iframe = 'https://sync.lemmadigital.com/js/usersync.html?pid=1001'; + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function () { + sandbox.restore(); + }); + + it('execute as per config', function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl_iframe + }]); + }); + + it('not execute as per config', function () { + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal(undefined); + }); + }); + }); +}); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 3c22cda1154..af34c209a1d 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -5,7 +5,7 @@ import { server } from 'test/mocks/xhr.js'; resetLiveIntentIdSubmodule(); liveIntentIdSubmodule.setModuleMode('standard') const PUBLISHER_ID = '89899'; -const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; +const defaultConfigParams = { params: {publisherId: PUBLISHER_ID, fireEventDelay: 1} }; const responseHeader = {'Content-Type': 'application/json'} describe('LiveIntentId', function() { @@ -45,7 +45,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*/); const response = { unifiedId: 'a_unified_id', @@ -59,25 +59,31 @@ describe('LiveIntentId', function() { expect(callBackSpy.calledOnceWith(response)).to.be.true; }); - it('should fire an event when getId', function() { + it('should fire an event when getId', function(done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, consentString: 'consentDataString' }) liveIntentIdSubmodule.getId(defaultConfigParams); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString.*/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString.*/); + done(); + }, 200); }); - it('should fire an event when getId and a hash is provided', function() { + it('should fire an event when getId and a hash is provided', function(done) { liveIntentIdSubmodule.getId({ params: { ...defaultConfigParams, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + done(); + }, 200); }); - it('should initialize LiveConnect with the config params when decode and emit an event', function () { + it('should initialize LiveConnect with the config params when decode and emit an event', function (done) { liveIntentIdSubmodule.decode({}, { params: { ...defaultConfigParams.params, ...{ @@ -88,25 +94,34 @@ describe('LiveIntentId', function() { } } }}); - expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + done(); + }, 200); }); - it('should initialize LiveConnect and emit an event with a privacy string when decode', function() { + it('should initialize LiveConnect and emit an event with a privacy string when decode', function(done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: false, consentString: 'consentDataString' }) liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*/); + done(); + }, 200); }); - it('should fire an event when decode and a hash is provided', function() { + it('should fire an event when decode and a hash is provided', function(done) { liveIntentIdSubmodule.decode({}, { params: { ...defaultConfigParams.params, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + done(); + }, 200); }); it('should not return a decoded identifier when the unifiedId is not present in the value', function() { @@ -114,25 +129,31 @@ describe('LiveIntentId', function() { expect(result).to.be.eql({}); }); - it('should fire an event when decode', function() { + it('should fire an event when decode', function(done) { liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(server.requests[0].url).to.be.not.null + setTimeout(() => { + expect(server.requests[0].url).to.be.not.null + done(); + }, 200); }); - it('should initialize LiveConnect and send data only once', function() { + it('should initialize LiveConnect and send data only once', function(done) { liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(server.requests.length).to.be.eq(1); + setTimeout(() => { + expect(server.requests.length).to.be.eq(1); + done(); + }, 200); }); - it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function() { + it('should call the custom URL of the LiveIntent Identity Exchange endpoint', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?resolve=nonId'); request.respond( 204, @@ -152,7 +173,7 @@ describe('LiveIntentId', function() { } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?resolve=nonId'); request.respond( 200, @@ -167,7 +188,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 200, @@ -182,7 +203,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 503, @@ -199,7 +220,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&resolve=nonId`); request.respond( 200, @@ -222,7 +243,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&_thirdPC=third-pc&resolve=nonId`); request.respond( 200, @@ -244,7 +265,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); request.respond( 200, @@ -277,7 +298,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=nonId&resolve=foo`); request.respond( 200, @@ -304,7 +325,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=uid2`); request.respond( 200, diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 3606b4b4550..b27e8f30881 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -3,6 +3,7 @@ import magniteAdapter, { getHostNameFromReferer, storage, rubiConf, + detectBrowserFromUa } from '../../../modules/magniteAnalyticsAdapter.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; @@ -101,6 +102,11 @@ const MOCK = { 'startTime': 1658868383748 } ], + 'ortb2': { + 'device': { + 'ua': 'Mozilla/ 5.0(Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/ 537.36(KHTML, like Gecko) Chrome/ 109.0.0.0 Safari / 537.36' + } + }, 'refererInfo': { 'page': 'http://a-test-domain.com:8000/test_pages/sanity/TEMP/prebidTest.html?pbjs_debug=true', }, @@ -209,6 +215,9 @@ const ANALYTICS_MESSAGE = { 'start': 1519767013781, 'expires': 1519788613781 }, + 'client': { + 'browser': 'Chrome' + }, 'auctions': [ { 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', @@ -550,6 +559,29 @@ describe('magnite analytics adapter', function () { ]); }); + [ + { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15', + expected: 'Safari' + }, + { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0', + expected: 'Firefox' + }, + { + ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/109.0.1518.78', + expected: 'Edge' + }, + { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 OPR/94.0.0.0', + expected: 'Opera' + } + ].forEach(testData => { + it(`should parse browser from ${testData.expected} user agent correctly`, function () { + expect(detectBrowserFromUa(testData.ua)).to.equal(testData.expected); + }); + }) + it('should pass along 1x1 size if no sizes in adUnit', function () { const auctionInit = utils.deepClone(MOCK.AUCTION_INIT); diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js index de38554f010..000ddb2778d 100644 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import { spec as adapter, SUPPORTED_ID_SYSTEMS, @@ -13,9 +13,10 @@ import { getUniqueDealId, } from 'modules/minutemediaplusBidAdapter.js'; import * as utils from 'src/utils.js'; -import { version } from 'package.json'; -import { useFakeTimers } from 'sinon'; -import { BANNER, VIDEO } from '../../../src/mediaTypes'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; const SUB_DOMAIN = 'exchange'; @@ -36,6 +37,10 @@ const BID = { 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'mediaTypes': [BANNER] @@ -45,6 +50,10 @@ const VIDEO_BID = { 'bidId': '2d52001cabd527', 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { @@ -78,11 +87,37 @@ const BIDDER_REQUEST = { 'consentString': 'consent_string', 'gdprApplies': true }, + 'gppString': 'gpp_string', + 'gppSid': [7], 'uspConsent': 'consent_string', 'refererInfo': { 'page': 'https://www.greatsite.com', 'ref': 'https://www.somereferrer.com' - } + }, + 'ortb2': { + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } + }, }; const SERVER_RESPONSE = { @@ -134,7 +169,7 @@ const REQUEST = { function getTopWindowQueryParams() { try { - const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -213,6 +248,9 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should build video request', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -223,17 +261,42 @@ describe('MinuteMediaPlus Bid Adapter', function () { bidFloor: 0.1, bidId: '2d52001cabd527', bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', cb: 1000, gdpr: 1, gdprConsent: 'consent_string', usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], prebidVersion: version, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, publisherId: '59ac17c192832d0011283fe3', url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', res: `${window.top.screen.width}x${window.top.screen.height}`, schain: VIDEO_BID.schain, sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -259,6 +322,9 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should build banner request for each size', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -267,8 +333,33 @@ describe('MinuteMediaPlus Bid Adapter', function () { data: { gdprConsent: 'consent_string', gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -296,7 +387,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); describe('getUserSyncs', function () { it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', @@ -305,7 +396,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should have valid user sync with cid on response', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -313,7 +404,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ 'url': 'https://sync.minutemedia-prebid.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -329,12 +420,12 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({ price: 1, ad: '' }); + const responses = adapter.interpretResponse({price: 1, ad: ''}); expect(responses).to.be.empty; }); it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); expect(responses).to.be.empty; }); @@ -394,11 +485,11 @@ describe('MinuteMediaPlus Bid Adapter', function () { const userId = (function () { switch (idSystemProvider) { case 'lipb': - return { lipbid: id }; + return {lipbid: id}; case 'parrableId': - return { eid: id }; + return {eid: id}; case 'id5id': - return { uid: id }; + return {uid: id}; default: return id; } @@ -417,18 +508,18 @@ describe('MinuteMediaPlus Bid Adapter', function () { describe('alternate param names extractors', function () { it('should return undefined when param not supported', function () { - const cid = extractCID({ 'c_id': '1' }); - const pid = extractPID({ 'p_id': '1' }); - const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); expect(cid).to.be.undefined; expect(pid).to.be.undefined; expect(subDomain).to.be.undefined; }); it('should return value when param supported', function () { - const cid = extractCID({ 'cID': '1' }); - const pid = extractPID({ 'Pid': '2' }); - const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); expect(cid).to.be.equal('1'); expect(pid).to.be.equal('2'); expect(subDomain).to.be.equal('prebid'); @@ -488,7 +579,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { now }); setStorageItem('myKey', 2020); - const { value, created } = getStorageItem('myKey'); + const {value, created} = getStorageItem('myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -504,8 +595,8 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should parse JSON value', function () { - const data = JSON.stringify({ event: 'send' }); - const { event } = tryParseJSON(data); + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); expect(event).to.be.equal('send'); }); diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index 157137b4730..f61987298e8 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -13,6 +13,8 @@ describe('Missena Adapter', function () { sizes: [[1, 1]], params: { apiKey: 'PA-34745704', + placement: 'sticky', + formats: ['sticky-banner'], }, }; @@ -70,6 +72,14 @@ describe('Missena Adapter', function () { expect(payload.request_id).to.equal(bidId); }); + it('should send placement', function () { + expect(payload.placement).to.equal('sticky'); + }); + + it('should send formats', function () { + expect(payload.formats).to.eql(['sticky-banner']); + }); + it('should send referer information to the request', function () { expect(payload.referer).to.equal('https://referer'); expect(payload.referer_canonical).to.equal('https://canonical'); diff --git a/test/spec/modules/neuwoRtdProvider_spec.js b/test/spec/modules/neuwoRtdProvider_spec.js index 7f47fba072b..aed9634cd95 100644 --- a/test/spec/modules/neuwoRtdProvider_spec.js +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -4,7 +4,8 @@ import * as neuwo from 'modules/neuwoRtdProvider'; const PUBLIC_TOKEN = 'public_key_0000'; const config = () => ({ params: { - publicToken: PUBLIC_TOKEN + publicToken: PUBLIC_TOKEN, + apiUrl: 'https://testing-requirement.neuwo.api' } }) diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 7645ee59f63..49807aa8b8b 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -1,9 +1,41 @@ -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'; +import { expect } from 'chai'; +import { + spec, storage, getNexx360LocalStorage, +} from 'modules/nexx360BidAdapter.js'; +import { sandbox } from 'sinon'; + +const instreamResponse = { + 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'id': '8275140264321181514', + 'impid': '263cba3b8bfb72', + 'price': 5, + 'adomain': [ + 'appnexus.com' + ], + 'crid': '97517771', + 'h': 1, + 'w': 1, + 'ext': { + 'mediaType': 'instream', + 'ssp': 'appnexus', + 'divId': 'video1', + 'adUnitCode': 'video1', + 'vastXml': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' + } + } + ], + 'seat': 'appnexus' + } + ], + 'ext': { + 'cookies': [] + } +}; describe('Nexx360 bid adapter tests', function () { const DISPLAY_BID_REQUEST = { @@ -110,6 +142,32 @@ describe('Nexx360 bid adapter tests', function () { 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', } }); + + it('We verify isBidRequestValid with unvalid adUnitName', function() { + bannerBid.params = { adUnitName: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with empty adUnitName', function() { + bannerBid.params = { adUnitName: '' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid adUnitPath', function() { + bannerBid.params = { adUnitPath: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid divId', function() { + bannerBid.params = { divId: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid unvalid allBids', function() { + bannerBid.params = { allBids: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + it('We verify isBidRequestValid with uncorrect tagid', function() { bannerBid.params = { 'tagid': 'luvxjvgn' }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); @@ -121,63 +179,87 @@ describe('Nexx360 bid adapter tests', function () { }); }); - describe('when request is for a multiformat ad', function () { - describe('and request config uses mediaTypes video and banner', () => { - const multiformatBid = { - 'bidder': 'nexx360', - 'params': {'tagId': 'luvxjvgn'}, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - }, - 'video': { - 'playerSize': [300, 250] - } - }, - 'adUnitCode': 'div-1', - 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4906582fc87d0c', - 'bidderRequestId': '332fda16002dbe', - 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', - } - it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); - }); + describe('getNexx360LocalStorage disabled', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => false); }); - }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(output).to.be.eql(false); + }); + after(function () { + sandbox.restore() + }); + }) - describe('when request is for a video ad', function () { - describe('and request config uses mediaTypes video and banner', () => { - const videoBid = { - 'bidder': 'nexx360', - 'params': {'tagId': 'luvxjvgn'}, - 'mediaTypes': { - 'video': { - 'playerSize': [300, 250] - } - }, - 'adUnitCode': 'div-1', - 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', - 'bidId': '4906582fc87d0c', - 'bidderRequestId': '332fda16002dbe', - 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', - } - it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - }); + describe('getNexx360LocalStorage enabled but nothing', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => null); }); - }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(typeof output.nexx360Id).to.be.eql('string'); + }); + after(function () { + sandbox.restore() + }); + }) + + describe('getNexx360LocalStorage enabled but wrong payload', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); + }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(output).to.be.eql(false); + }); + after(function () { + sandbox.restore() + }); + }) + + describe('getNexx360LocalStorage enabled', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); + }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(output.nexx360Id).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); + }); + after(function () { + sandbox.restore() + }); + }) describe('buildRequests()', function() { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs('div-1').returns({ + offsetWidth: 200, + offsetHeight: 250, + style: { + maxWidth: '400px', + maxHeight: '350px', + } + }); + }); describe('We test with a multiple display bids', function() { const sampleBids = [ { bidder: 'nexx360', params: { - tagId: 'luvxjvgn' + tagId: 'luvxjvgn', + divId: 'div-1', + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', }, - adUnitCode: 'div-1', + adUnitCode: 'header-ad-1234', transactionId: '469a570d-f187-488d-b1cb-48c1a2009be9', sizes: [[300, 250], [300, 600]], bidId: '44a2706ac3574', @@ -221,7 +303,7 @@ describe('Nexx360 bid adapter tests', function () { sizes: [[728, 90], [970, 250]] } }, - adUnitCode: 'div-2', + adUnitCode: 'div-2-abcd', transactionId: '6196885d-4e76-40dc-a09c-906ed232626b', sizes: [[728, 90], [970, 250]], bidId: '5ba94555219a03', @@ -276,17 +358,25 @@ describe('Nexx360 bid adapter tests', function () { expect(requestContent.cur[0]).to.be.eql('USD'); expect(requestContent.imp.length).to.be.eql(2); expect(requestContent.imp[0].id).to.be.eql('44a2706ac3574'); - expect(requestContent.imp[0].tagid).to.be.eql('div-1'); + expect(requestContent.imp[0].tagid).to.be.eql('header-ad'); expect(requestContent.imp[0].ext.divId).to.be.eql('div-1'); + expect(requestContent.imp[0].ext.adUnitCode).to.be.eql('header-ad-1234'); + expect(requestContent.imp[0].ext.adUnitName).to.be.eql('header-ad'); + expect(requestContent.imp[0].ext.adUnitPath).to.be.eql('/12345/nexx360/Homepage/HP/Header-Ad'); + expect(requestContent.imp[0].ext.dimensions.slotW).to.be.eql(200); + expect(requestContent.imp[0].ext.dimensions.slotH).to.be.eql(250); + expect(requestContent.imp[0].ext.dimensions.cssMaxW).to.be.eql('400px'); + expect(requestContent.imp[0].ext.dimensions.cssMaxH).to.be.eql('350px'); expect(requestContent.imp[0].ext.nexx360.tagId).to.be.eql('luvxjvgn'); - expect(requestContent.imp[0].ext.nexx360.allBids).to.be.eql(false); expect(requestContent.imp[0].banner.format.length).to.be.eql(2); expect(requestContent.imp[0].banner.format[0].w).to.be.eql(300); expect(requestContent.imp[0].banner.format[0].h).to.be.eql(250); expect(requestContent.imp[1].ext.nexx360.allBids).to.be.eql(true); - expect(requestContent.regs.ext.gdpr).to.be.eql(1); - expect(requestContent.user.ext.consent).to.be.eql(bidderRequest.gdprConsent.consentString); - expect(requestContent.user.ext.eids.length).to.be.eql(2); + expect(requestContent.imp[1].tagid).to.be.eql('div-2-abcd'); + expect(requestContent.imp[1].ext.adUnitCode).to.be.eql('div-2-abcd'); + expect(requestContent.imp[1].ext.divId).to.be.eql('div-2-abcd'); + expect(requestContent.ext.bidderVersion).to.be.eql('2.0'); + expect(requestContent.ext.source).to.be.eql('prebid.js'); }); it('We perform a test with a multiformat adunit', function() { @@ -307,11 +397,11 @@ describe('Nexx360 bid adapter tests', function () { }; const request = spec.buildRequests(multiformatBids, bidderRequest); const requestContent = request.data; - expect(requestContent.imp[0].video.context).to.be.eql('outstream'); + expect(requestContent.imp[0].video.ext.context).to.be.eql('outstream'); expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); }); - it('We perform a test with a video adunit', function() { + it('We perform a test with a instream adunit', function() { const videoBids = [sampleBids[0]]; videoBids[0].mediaTypes = { video: { @@ -326,13 +416,23 @@ describe('Nexx360 bid adapter tests', function () { const request = spec.buildRequests(videoBids, bidderRequest); const requestContent = request.data; expect(request).to.have.property('method').and.to.equal('POST'); - expect(requestContent.imp[0].video.context).to.be.eql('instream'); + expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); }) }); + after(function () { + sandbox.restore() + }); }); describe('interpretResponse()', function() { + it('empty response', function() { + const response = { + body: '' + }; + const output = spec.interpretResponse(response); + expect(output.length).to.be.eql(0); + }); it('banner responses', function() { const response = { body: { @@ -345,7 +445,6 @@ describe('Nexx360 bid adapter tests', function () { 'id': '4427551302944024629', 'impid': '226175918ebeda', 'price': 1.5, - 'type': 'banner', 'adomain': [ 'http://prebid.org' ], @@ -356,141 +455,153 @@ describe('Nexx360 bid adapter tests', function () { 'cat': [ 'IAB3-1' ], - 'creativeuuid': 'fdddcebc-1edf-489d-880d-1418d8bdc493', - 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', 'ext': { - 'dsp_id': 'ssp1', - 'buyer_id': 'foo', - 'brand_id': 'bar' + 'adUnitCode': 'div-1', + 'mediaType': 'banner', + 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + 'ssp': 'appnexus', } } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'id': 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', + 'cookies': [] + }, } }; const output = spec.interpretResponse(response); - expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); - expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); + expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].ext.adUrl); + expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].ext.mediaType); expect(output[0].currency).to.be.eql(response.body.cur); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); - expect(output[0].meta.networkId).to.be.eql(response.body.seatbid[0].bid[0].ext.dsp_id); - expect(output[0].meta.advertiserId).to.be.eql(response.body.seatbid[0].bid[0].ext.buyer_id); - expect(output[0].meta.brandId).to.be.eql(response.body.seatbid[0].bid[0].ext.brand_id); }); - it('video responses', function() { + it('instream responses', function() { const response = { body: { - 'id': '33894759-0ea2-41f1-84b3-75132eefedb6', + 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', 'cur': 'USD', 'seatbid': [ { 'bid': [ { - 'id': '294478680080716675', - 'impid': '2c835a6039e65f', + 'id': '8275140264321181514', + 'impid': '263cba3b8bfb72', 'price': 5, - 'type': 'instream', 'adomain': [ - '' + 'appnexus.com' ], 'crid': '97517771', - 'ssp': 'appnexus', 'h': 1, 'w': 1, - 'vastXml': '\n \n \n prebid.org wrapper\n \n \n \n \n \n ' + 'ext': { + 'mediaType': 'instream', + 'ssp': 'appnexus', + 'adUnitCode': 'video1', + 'vastXml': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' + } } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'cookies': [] + } } }; const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].ext.vastXml); expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); }); - }); - describe('interpretResponse()', function() { - it('banner responses', function() { + it('outstream responses', function() { const response = { body: { - 'id': 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + 'id': '40c23932-135e-4602-9701-ca36f8d80c07', 'cur': 'USD', 'seatbid': [ { 'bid': [ { - 'id': '4427551302944024629', - 'impid': '226175918ebeda', - 'price': 1.5, - 'type': 'banner', + 'id': '1186971142548769361', + 'impid': '4ce809b61a3928', + 'price': 5, 'adomain': [ - 'http://prebid.org' + 'appnexus.com' ], - 'crid': '98493581', - 'ssp': 'appnexus', - 'h': 600, - 'w': 300, - 'cat': [ - 'IAB3-1' - ], - 'creativeuuid': 'fdddcebc-1edf-489d-880d-1418d8bdc493', - 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493' + 'crid': '97517771', + 'h': 1, + 'w': 1, + 'ext': { + 'mediaType': 'outstream', + 'ssp': 'appnexus', + 'adUnitCode': 'div-1', + 'vastXml': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' + } } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'cookies': [] + } } }; const output = spec.interpretResponse(response); - expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); - expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].ext.vastXml); + expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); + expect(typeof output[0].renderer).to.be.eql('object'); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); }); - it('video responses', function() { + + it('native responses', function() { const response = { body: { - 'id': '33894759-0ea2-41f1-84b3-75132eefedb6', + 'id': '3c0290c1-6e75-4ef7-9e37-17f5ebf3bfa3', 'cur': 'USD', 'seatbid': [ { 'bid': [ { - 'id': '294478680080716675', - 'impid': '2c835a6039e65f', - 'price': 5, - 'type': 'instream', + 'id': '6624930625245272225', + 'impid': '23e11d845514bb', + 'price': 10, 'adomain': [ - '' + 'prebid.org' ], - 'crid': '97517771', - 'ssp': 'appnexus', + 'crid': '97494204', 'h': 1, 'w': 1, - 'vastXml': '\n \n \n prebid.org wrapper\n \n \n \n \n \n ' + 'cat': [ + 'IAB3-1' + ], + 'ext': { + 'mediaType': 'native', + 'ssp': 'appnexus', + 'adUnitCode': '/19968336/prebid_native_example_1' + }, + 'adm': '{"ver":"1.2","assets":[{"id":1,"img":{"url":"https:\\/\\/vcdn.adnxs.com\\/p\\/creative-image\\/f8\\/7f\\/0f\\/13\\/f87f0f13-230c-4f05-8087-db9216e393de.jpg","w":989,"h":742,"ext":{"appnexus":{"prevent_crop":0}}}},{"id":0,"title":{"text":"This is a Prebid Native Creative"}},{"id":2,"data":{"value":"Prebid.org"}}],"link":{"url":"https:\\/\\/ams3-ib.adnxs.com\\/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA.\\/bcr=AAAAAAAA8D8=\\/pp=${AUCTION_PRICE}\\/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA\\/cca=OTMyNSNBTVMzOjYxMzU=\\/bn=97062\\/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/ams3-ib.adnxs.com\\/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}"}]}' } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'cookies': [], + } } }; const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); - expect(output[0].mediaType).to.be.eql('video'); - expect(output[0].currency).to.be.eql(response.body.cur); - expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + expect(output[0].native.ortb.ver).to.be.eql('1.2'); + expect(output[0].native.ortb.assets[0].id).to.be.eql(1); + expect(output[0].mediaType).to.be.eql('native'); }); }); @@ -501,7 +612,9 @@ describe('Nexx360 bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); it('Verifies user sync with cookies in bid response', function () { - response.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; + response.body.ext = { + cookies: [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}] + }; var syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); expect(syncs).to.have.lengthOf(1); expect(syncs[0]).to.have.property('type').and.to.equal('image'); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index 7ea89f7dd3f..8328aae33d8 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -14,6 +14,38 @@ describe('Nobid Adapter', function () { }); }); + describe('buildRequestsWithFloor', function () { + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + let bidRequests = [ + { + 'bidder': 'nobid', + 'params': { + 'siteId': SITE_ID + }, + 'getFloor': () => { return { currency: 'USD', floor: 1.00 } }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + } + ]; + + let bidderRequest = { + refererInfo: {page: REFERER} + } + + it('should FLoor = 1', function () { + spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequest); + /* eslint-disable no-console */ + console.log('request.data:', request.data); + const payload = JSON.parse(request.data); + expect(payload.a[0].floor).to.equal(1); + }); + }); + describe('isBidRequestValid', function () { let bid = { 'bidder': 'nobid', diff --git a/test/spec/modules/novatiqIdSystem_spec.js b/test/spec/modules/novatiqIdSystem_spec.js index b92fb0d219a..6d25601d958 100644 --- a/test/spec/modules/novatiqIdSystem_spec.js +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -138,16 +138,31 @@ describe('novatiqIdSystem', function () { }); describe('decode', function() { - it('should log message if novatiqId has wrong format', function() { + it('should return the same novatiqId as passed in if not async', function() { const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; const response = novatiqIdSubmodule.decode(novatiqId); expect(response.novatiq.snowflake).to.have.length(40); }); - it('should log message if novatiqId has wrong format', function() { - const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + it('should change the result format if async', function() { + let novatiqId = {}; + novatiqId.id = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + novatiqId.syncResponse = 2; const response = novatiqIdSubmodule.decode(novatiqId); - expect(response.novatiq.snowflake).should.be.not.empty; + expect(response.novatiq.ext.syncResponse).should.be.not.empty; + expect(response.novatiq.snowflake.id).should.be.not.empty; + expect(response.novatiq.snowflake.syncResponse).should.be.not.empty; + }); + + it('should remove syncResponse if removeAdditionalInfo true', function() { + let novatiqId = {}; + novatiqId.id = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + novatiqId.syncResponse = 2; + var config = {params: {removeAdditionalInfo: true}}; + const response = novatiqIdSubmodule.decode(novatiqId, config); + expect(response.novatiq.ext.syncResponse).should.be.not.empty; + expect(response.novatiq.snowflake.id).should.be.not.empty; + should.equal(response.novatiq.snowflake.syncResponse, undefined); }); }); }) diff --git a/test/spec/modules/optidigitalBidAdapter_spec.js b/test/spec/modules/optidigitalBidAdapter_spec.js new file mode 100755 index 00000000000..8c222650f7e --- /dev/null +++ b/test/spec/modules/optidigitalBidAdapter_spec.js @@ -0,0 +1,604 @@ +import { expect } from 'chai'; +import { spec } from 'modules/optidigitalBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT = 'https://pbs.optidigital.com/bidder'; + +describe('optidigitalAdapterTests', function () { + describe('isBidRequestValid', function () { + it('bidRequest with publisherId and placementId', function () { + expect(spec.isBidRequestValid({ + bidder: 'optidigital', + params: { + publisherId: 's123', + placementId: 'Billboard_Top' + } + })).to.equal(true); + }); + it('bidRequest without publisherId', function () { + expect(spec.isBidRequestValid({ + bidder: 'optidigital', + params: { + placementId: 'Billboard_Top' + } + })).to.equal(false); + }); + it('bidRequest without placementId', function () { + expect(spec.isBidRequestValid({ + bidder: 'optidigital', + params: { + publisherId: 's123' + } + })).to.equal(false); + }); + it('bidRequest without required parameters', function () { + expect(spec.isBidRequestValid({ + bidder: 'optidigital', + params: {} + })).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidderRequest = { + bids: [ + { + 'bidder': 'optidigital', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'divId': 'Billboard_Top_3c5425', + 'badv': ['example.com'], + 'bcat': ['IAB1-1'], + 'bapp': ['com.blocked'], + 'battr': [1, 2] + }, + 'crumbs': { + 'pubcid': '7769fd03-574c-48fe-b512-8147f7c4023a' + }, + 'ortb2Imp': { + 'ext': { + 'tid': '0cb56262-9637-474d-a572-86fa860fd8b7', + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/19968336/header-bid-tag-0' + }, + 'pbadslot': '/19968336/header-bid-tag-0' + }, + 'gpid': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '0cb56262-9637-474d-a572-86fa860fd8b7', + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], + 'bidId': '245d89f17f289f', + 'bidderRequestId': '199d7ffafa1e91', + 'auctionId': 'b66f01cd-3441-4403-99fa-d8062e795933', + 'src': 'client', + 'metrics': { + 'requestBids.usp': 0.5, + 'requestBids.pubCommonId': 0.29999999701976776, + 'requestBids.fpd': 3.1000000089406967, + 'requestBids.validate': 0.5, + 'requestBids.makeRequests': 2.2000000029802322, + 'requestBids.total': 570, + 'requestBids.callBids': 320.5, + 'adapter.client.net': [ + 317.30000001192093 + ], + 'adapters.client.optidigital.net': [ + 317.30000001192093 + ], + 'adapter.client.interpretResponse': [ + 0 + ], + 'adapters.client.optidigital.interpretResponse': [ + 0 + ], + 'adapter.client.validate': 0, + 'adapters.client.optidigital.validate': 0, + 'adapter.client.buildRequests': 1, + 'adapters.client.optidigital.buildRequests': 1, + 'adapter.client.total': 318.59999999403954, + 'adapters.client.optidigital.total': 318.59999999403954 + }, + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'page': 'https://example.com', + 'ref': 'https://example.com', + 'domain': 'example.com', + 'publisher': { + 'domain': 'example.com' + } + }, + 'device': { + 'w': 1605, + 'h': 1329, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', + 'language': 'pl', + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Windows', + 'version': [ + '10', + '0', + '0' + ] + }, + 'browsers': [ + { + 'brand': 'Not_A Brand', + 'version': [ + '99', + '0', + '0', + '0' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '109', + '0', + '5414', + '75' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '109', + '0', + '5414', + '75' + ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } + } + ], + 'refererInfo': { + 'canonicalUrl': 'https://www.prebid.org/the/link/to/the/page' + } + }; + + let validBidRequests = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'badv': ['example.com'], + 'bcat': ['IAB1-1'], + 'bapp': ['com.blocked'], + 'battr': [1, 2] + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ] + + it('should return an empty array if there are no bid requests', () => { + const emptyBidRequests = []; + const request = spec.buildRequests(emptyBidRequests, emptyBidRequests); + expect(request).to.be.an('array').that.is.empty; + }); + + it('should send bid request via POST', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.method).to.equal('POST'); + }); + + it('should send bid request to given endpoint', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + }); + + it('should be bidRequest data', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.exist; + }); + + it('should add schain object to payload if exists', function () { + const bidRequest = Object.assign({}, validBidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'examplewebsite.com', + sid: '00001', + hp: 1 + }] + } + }); + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data) + expect(payload.schain).to.exist; + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'examplewebsite.com', + sid: '00001', + hp: 1 + }] + }); + }); + + it('should add adContainerWidth and adContainerHeight to payload if divId exsists in parameter', function () { + let validBidRequestsWithDivId = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'divId': 'div-gpt-ad-1460505748561-0' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ] + const request = spec.buildRequests(validBidRequestsWithDivId, bidderRequest); + const payload = JSON.parse(request.data) + payload.imp[0].adContainerWidth = 1920 + payload.imp[0].adContainerHeight = 1080 + expect(payload.imp[0].adContainerWidth).to.exist; + expect(payload.imp[0].adContainerHeight).to.exist; + }); + + it('should add pageTemplate to payload if pageTemplate exsists in parameter', function () { + let validBidRequestsWithDivId = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'pageTemplate': 'home' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ] + const request = spec.buildRequests(validBidRequestsWithDivId, bidderRequest); + const payload = JSON.parse(request.data) + payload.imp[0].pageTemplate = 'home' + expect(payload.imp[0].pageTemplate).to.exist; + }); + + it('should add referrer to payload if it exsists in bidderRequest', function () { + bidderRequest.refererInfo.page = 'https://www.prebid.org'; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data) + expect(payload.referrer).to.equal('https://www.prebid.org'); + }); + + it('should use value for badv, bcat, bapp from params', function () { + bidderRequest.ortb2 = { + 'site': { + 'page': 'https://example.com', + 'ref': 'https://example.com', + 'domain': 'example.com', + 'publisher': { + 'domain': 'example.com' + } + }, + 'device': { + 'w': 1507, + 'h': 1329, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', + 'language': 'pl', + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Windows', + 'version': [ + '10', + '0', + '0' + ] + }, + 'browsers': [ + { + 'brand': 'Not_A Brand', + 'version': [ + '99', + '0', + '0', + '0' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '109', + '0', + '5414', + '120' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '109', + '0', + '5414', + '120' + ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.badv).to.deep.equal(validBidRequests[0].params.badv); + expect(payload.bcat).to.deep.equal(validBidRequests[0].params.bcat); + expect(payload.bapp).to.deep.equal(validBidRequests[0].params.bapp); + }); + + it('should send empty GDPR consent and required set to false', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr.consent).to.equal(''); + expect(payload.gdpr.required).to.equal(false); + }); + + it('should send GDPR to given endpoint', function() { + let consentString = 'DFR8KRePoQNsRREZCADBG+A=='; + bidderRequest.gdprConsent = { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'hasGlobalConsent': false + }, + 'apiVersion': 1 + } + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consent).to.equal(consentString); + expect(payload.gdpr.required).to.exist.and.to.be.true; + }); + + it('should send empty GDPR consent to endpoint', function() { + let consentString = false; + bidderRequest.gdprConsent = { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'hasGlobalConsent': false + }, + 'apiVersion': 1 + } + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr.consent).to.equal(''); + }); + + it('should send uspConsent to given endpoint', function() { + bidderRequest.uspConsent = '1YYY'; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.uspConsent).to.exist; + }); + + it('should use appropriate mediaTypes banner sizes', function() { + const mediaTypesBannerSize = { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 600] + } + } + }; + returnBannerSizes(mediaTypesBannerSize, '300x600'); + }); + + it('should use appropriate mediaTypes banner sizes as array', function() { + const mediaTypesBannerSize = { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 600] + } + } + }; + returnBannerSizes(mediaTypesBannerSize, ['300x600']); + }); + + it('should fetch floor from floor module if it is available', function() { + let validBidRequestsWithCurrency = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'pageTemplate': 'home', + 'currency': 'USD' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ] + let floorInfo; + validBidRequestsWithCurrency[0].getFloor = () => floorInfo; + floorInfo = { currency: 'USD', floor: 1.99 }; + let request = spec.buildRequests(validBidRequestsWithCurrency, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidFloor).to.exist; + }); + + function returnBannerSizes(mediaTypes, expectedSizes) { + const bidRequest = Object.assign(validBidRequests[0], mediaTypes); + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + return payload.imp.forEach(bid => { + if (Array.isArray(expectedSizes)) { + expect(JSON.stringify(bid.sizes)).to.equal(JSON.stringify(expectedSizes)); + } else { + expect(bid.sizes[0]).to.equal(expectedSizes); + } + }); + } + }); + describe('getUserSyncs', function() { + const syncurlIframe = 'https://scripts.opti-digital.com/js/presync.html?endpoint=optidigital'; + let test; + beforeEach(function () { + test = sinon.sandbox.create(); + }); + afterEach(function() { + test.restore(); + }); + + it('should be executed as in config', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurlIframe + }]); + }); + + it('should return appropriate URL', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurlIframe}&gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurlIframe}&gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurlIframe}&gdpr=1&gdpr_consent=` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, {consentString: 'fooUsp'})).to.deep.equal([{ + type: 'iframe', url: `${syncurlIframe}&gdpr=1&gdpr_consent=foo&ccpa_consent=fooUsp` + }]); + }); + }); + describe('interpretResponse', function () { + it('should get bids', function() { + let bids = { + 'body': { + 'bids': [{ + 'transactionId': 'cf5faec3-fcee-4f26-80ae-fc8b6cf23b7d', + 'placementId': 'Billboard_Top', + 'bidId': '83fb53a5e67f49', + 'ttl': 150, + 'creativeId': 'mobile_pos_2', + 'cur': 'USD', + 'cpm': 0.445455, + 'w': '300', + 'h': '600', + 'adm': '', + 'adomain': [] + }] + } + }; + let expectedResponse = [ + { + 'placementId': 'Billboard_Top', + 'transactionId': 'cf5faec3-fcee-4f26-80ae-fc8b6cf23b7d', + 'requestId': '83fb53a5e67f49', + 'ttl': 150, + 'creativeId': 'mobile_pos_2', + 'currency': 'USD', + 'cpm': 0.445455, + 'width': '300', + 'height': '600', + 'ad': '', + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [] + } + } + ]; + let result = spec.interpretResponse(bids); + expect(result).to.eql(expectedResponse); + }); + + it('should handle empty array bid response', function() { + let bids = { + 'body': { + 'bids': [] + } + }; + let result = spec.interpretResponse(bids); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 5030e662ea9..1f39d1a2cda 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -2,12 +2,15 @@ import { permutiveSubmodule, storage, getSegments, - initSegments, isAcEnabled, isPermutiveOnPage, setBidderRtb, getModuleConfig, PERMUTIVE_SUBMODULE_CONFIG_KEY, + readAndSetCohorts, + PERMUTIVE_STANDARD_KEYWORD, + PERMUTIVE_STANDARD_AUD_KEYWORD, + PERMUTIVE_CUSTOM_COHORTS_KEYWORD, } from 'modules/permutiveRtdProvider.js' import { deepAccess, deepSetValue, mergeDeep } from '../../../src/utils.js' import { config } from 'src/config.js' @@ -188,24 +191,81 @@ describe('permutiveRtdProvider', function () { const moduleConfig = getConfig() const bidderConfig = {}; const acBidders = moduleConfig.params.acBidders - const expectedTargetingData = transformedTargeting().ac.map(seg => { + const segmentsData = transformedTargeting() + const expectedTargetingData = segmentsData.ac.map(seg => { return { id: seg } }) - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { - expect(bidderConfig[bidder].user.data).to.deep.include.members([{ - name: 'permutive.com', - segment: expectedTargetingData - }]) + const customCohorts = segmentsData[bidder] || [] + expect(bidderConfig[bidder].user.data).to.deep.include.members([ + { + name: 'permutive.com', + segment: expectedTargetingData, + }, + // Should have custom cohorts specific for that bidder + { + name: 'permutive', + segment: customCohorts.map(seg => { + return { id: seg } + }), + }, + ]) }) }) + + it('should override existing ortb2.user.data reserved by permutive RTD', function () { + const reservedPermutiveStandardName = 'permutive.com' + const reservedPermutiveCustomCohortName = 'permutive' + + const moduleConfig = getConfig() + const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + + const sampleOrtbConfig = { + user: { + data: [ + { + name: reservedPermutiveCustomCohortName, + segment: [{ id: 'remove-me' }, { id: 'remove-me-also' }] + }, + { + name: reservedPermutiveStandardName, + segment: [{ id: 'remove-me-also-also' }, { id: 'remove-me-also-also-also' }] + } + ] + } + } + + const bidderConfig = Object.fromEntries(acBidders.map(bidder => [bidder, sampleOrtbConfig])) + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + acBidders.forEach(bidder => { + const customCohorts = segmentsData[bidder] || [] + + expect(bidderConfig[bidder].user.data).to.not.deep.include.members([...sampleOrtbConfig.user.data]) + expect(bidderConfig[bidder].user.data).to.deep.include.members([ + { + name: reservedPermutiveCustomCohortName, + segment: customCohorts.map(id => ({ id })), + }, + { + name: reservedPermutiveStandardName, + segment: segmentsData.ac.map(id => ({ id })), + }, + ]) + }) + }) + it('should include ortb2 user data transformation for IAB audience taxonomy', function() { const moduleConfig = getConfig() const bidderConfig = {} const acBidders = moduleConfig.params.acBidders - const expectedTargetingData = transformedTargeting().ac.map(seg => { + const segmentsData = transformedTargeting() + const expectedTargetingData = segmentsData.ac.map(seg => { return { id: seg } }) @@ -225,7 +285,7 @@ describe('permutiveRtdProvider', function () { } ) - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].user.data).to.deep.include.members([ @@ -244,6 +304,8 @@ describe('permutiveRtdProvider', function () { it('should not overwrite ortb2 config', function () { const moduleConfig = getConfig() const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + const sampleOrtbConfig = { site: { name: 'example' @@ -267,10 +329,7 @@ describe('permutiveRtdProvider', function () { segment: [1, 2, 3] } - setBidderRtb(bidderConfig, moduleConfig, { - // TODO: this argument is unused, is the test still valid / needed? - testTransformation: userData => transformedUserData - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) @@ -280,6 +339,8 @@ describe('permutiveRtdProvider', function () { it('should update user.keywords and not override existing values', function () { const moduleConfig = getConfig() const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + const sampleOrtbConfig = { site: { name: 'example' @@ -304,19 +365,31 @@ describe('permutiveRtdProvider', function () { segment: [1, 2, 3] } - setBidderRtb(bidderConfig, moduleConfig, { - // TODO: this argument is unused, is the test still valid / needed? - testTransformation: userData => transformedUserData - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { + const customCohortsData = segmentsData[bidder] || [] + const keywordGroups = { + [PERMUTIVE_STANDARD_KEYWORD]: segmentsData.ac, + [PERMUTIVE_STANDARD_AUD_KEYWORD]: segmentsData.ssp.cohorts, + [PERMUTIVE_CUSTOM_COHORTS_KEYWORD]: customCohortsData + } + + // Transform groups of key-values into a single array of strings + // i.e { permutive: ['1', '2'], p_standard: ['3', '4'] } => ['permutive=1', 'permutive=2', 'p_standard=3',' p_standard=4'] + const transformedKeywordGroups = Object.entries(keywordGroups) + .flatMap(([keyword, ids]) => ids.map(id => `${keyword}=${id}`)) + + const keywords = `${sampleOrtbConfig.user.keywords},${transformedKeywordGroups.join(',')}` + expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) expect(bidderConfig[bidder].user.data).to.deep.include.members([sampleOrtbConfig.user.data[0]]) - expect(bidderConfig[bidder].user.keywords).to.deep.equal('a,b,p_standard_aud=123,p_standard_aud=abc') + expect(bidderConfig[bidder].user.keywords).to.deep.equal(keywords) }) }) it('should merge ortb2 correctly for ac and ssps', function () { - setLocalStorage({ + const customTargetingData = { + ...getTargetingData(), '_ppam': [], '_psegs': [], '_pcrprs': ['abc', 'def', 'xyz'], @@ -324,7 +397,10 @@ describe('permutiveRtdProvider', function () { ssps: ['foo', 'bar'], cohorts: ['xyz', 'uvw'], } - }) + } + const segmentsData = transformedTargeting(customTargetingData) + setLocalStorage(customTargetingData) + const moduleConfig = { name: 'permutive', waitForIt: true, @@ -335,7 +411,7 @@ describe('permutiveRtdProvider', function () { } const bidderConfig = {}; - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) // include both ac and ssp cohorts, as foo is both in ac bidders and ssps const expectedFooTargetingData = [ @@ -370,153 +446,137 @@ describe('permutiveRtdProvider', function () { segment: expectedOtherTargetingData }]) }) - }) - describe('Getting segments', function () { - it('should retrieve segments in the expected structure', function () { - const data = transformedTargeting() - expect(getSegments(250)).to.deep.equal(data) - }) - it('should enforce max segments', function () { - const max = 1 - const segments = getSegments(max) + describe('ortb2.user.ext tests', function () { + it('should add nothing if there are no cohorts data', function () { + // Empty module config means we default + const moduleConfig = getConfig() - for (const key in segments) { - if (key === 'ssp') { - expect(segments[key].cohorts).to.have.length(max) - } else { - expect(segments[key]).to.have.length(max) - } - } - }) - }) + const bidderConfig = {} - describe('Default segment targeting', function () { - it('sets segment targeting for Xandr', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() - - initSegments({ adUnits }, callback, config) + // Passing empty values means there is no segment data + const segmentsData = transformedTargeting({ + _pdfps: [], + _prubicons: [], + _papns: [], + _psegs: [], + _ppam: [], + _pcrprs: [], + _pssps: { ssps: [], cohorts: [] } + }) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + setBidderRtb(bidderConfig, moduleConfig, segmentsData) - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.permutive')).to.eql(data.appnexus) - expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + moduleConfig.params.acBidders.forEach(bidder => { + expect(bidderConfig[bidder].user).to.not.have.property('ext') }) - } - }) + }) - it('sets segment targeting for Magnite', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + it('should add standard and custom cohorts', function () { + const moduleConfig = getConfig() - initSegments({ adUnits }, callback, config) + const bidderConfig = {} - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + const segmentsData = transformedTargeting() - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.rubicon) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - } - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) - it('sets segment targeting for Magnite video', function () { - const targetingData = getTargetingData() - targetingData._prubicons.push(321) + moduleConfig.params.acBidders.forEach(bidder => { + const userExtData = { + // Default targeting + p_standard: segmentsData.ac, + } - setLocalStorage(targetingData) + const customCohorts = segmentsData[bidder] || [] + if (customCohorts.length > 0) { + deepSetValue(userExtData, 'permutive', customCohorts) + } - const data = transformedTargeting(targetingData) - const config = getConfig() + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq(userExtData) + }) + }) - const adUnits = getAdUnits().filter(adUnit => adUnit.mediaTypes.video) - expect(adUnits).to.have.lengthOf(1) + it('should add ac cohorts ONLY', function () { + const moduleConfig = getConfig() - initSegments({ adUnits }, callback, config) + const bidderConfig = {} - function callback() { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + const segmentsData = transformedTargeting() + moduleConfig.params.acBidders.forEach((bidder) => { + // Remove custom cohorts + delete segmentsData[bidder] + }) - if (bidder === 'rubicon') { - expect( - deepAccess(params, 'visitor.permutive'), - 'Should map all targeting values to a string', - ).to.eql(data.rubicon.map(String)) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach((bidder) => { + expect(bidderConfig[bidder].user.ext.data).to.deep.equal({ + p_standard: segmentsData.ac }) }) - } - }) + }) - it('sets segment targeting for Ozone', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + it('should add custom cohorts ONLY', function () { + const moduleConfig = getConfig() - initSegments({ adUnits }, callback, config) + const bidderConfig = {} - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + const segmentsData = transformedTargeting() + // Empty the AC cohorts + segmentsData['ac'] = [] - if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach(bidder => { + const customCohorts = segmentsData[bidder] || [] + if (customCohorts.length > 0) { + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq({ permutive: customCohorts }) + } else { + expect(bidderConfig[bidder].user).to.not.have.property('ext') + } }) - } + }) }) }) - describe('Custom segment targeting', function () { - it('sets custom segment targeting for Magnite', function () { + describe('Getting segments', function () { + it('should retrieve segments in the expected structure', function () { const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + expect(getSegments(250)).to.deep.equal(data) + }) + it('should enforce max segments', function () { + const max = 1 + const segments = getSegments(max) - config.params.overwrites = { - rubicon: function (bid, data, acEnabled, utils, defaultFn) { - if (defaultFn) { - bid = defaultFn(bid, data, acEnabled) - } - if (data.gam && data.gam.length) { - utils.deepSetValue(bid, 'params.visitor.permutive', data.gam) - } + for (const key in segments) { + if (key === 'ssp') { + expect(segments[key].cohorts).to.have.length(max) + } else { + expect(segments[key]).to.have.length(max) } } + }) + }) - initSegments({ adUnits }, callback, config) + describe('Default segment targeting', function () { + it('sets segment targeting for Ozone', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + readAndSetCohorts({ adUnits }, config) - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.gam) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) + } }) - } + }) }) }) @@ -525,73 +585,65 @@ describe('permutiveRtdProvider', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for Magnite', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for Ozone', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for TrustX', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'trustx') { - expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } }) - } + }) }) }) @@ -648,6 +700,7 @@ function transformedTargeting (data = getTargetingData()) { return { ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)], appnexus: data._papns, + ix: data._pindexs, rubicon: data._prubicons, gam: data._pdfps, ssp: data._pssps, @@ -661,6 +714,7 @@ function getTargetingData () { _papns: ['appnexus1', 'appnexus2'], _psegs: ['1234', '1000001', '1000002'], _ppam: ['ppam1', 'ppam2'], + _pindexs: ['pindex1', 'pindex2'], _pcrprs: ['pcrprs1', 'pcrprs2', 'dup'], _pssps: { ssps: ['xyz', 'abc', 'dup'], cohorts: ['123', 'abc'] } } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 0ce060b9904..c9c18c49eb0 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -994,7 +994,7 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - expect(requestBid.app).to.deep.equal({ + sinon.assert.match(requestBid.app, { bundle: 'com.test.app', publisher: { 'id': '1' } }); @@ -1021,7 +1021,7 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - expect(requestBid.app).to.deep.equal({ + sinon.assert.match(requestBid.app, { bundle: 'com.test.app', publisher: { 'id': '1' } }); @@ -1383,7 +1383,7 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - expect(requestBid.app).to.deep.equal({ + sinon.assert.match(requestBid.app, { bundle: 'com.test.app', publisher: { 'id': '1' } }); @@ -1522,7 +1522,7 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.not.exist; expect(requestBid.app).to.exist.and.to.be.a('object'); @@ -1732,9 +1732,9 @@ describe('S2S Adapter', function () { const s2sBidRequest = utils.deepClone(REQUEST); cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; s2sBidRequest.s2sConfig = cookieSyncConfig; - + config.setConfig({ userSync, s2sConfig: cookieSyncConfig }); - + let bidRequest = utils.deepClone(BID_REQUESTS); adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); return JSON.parse(server.requests[0].requestBody); @@ -1762,7 +1762,7 @@ describe('S2S Adapter', function () { } }); }); - + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the iframe key is present in userSync.filterSettings', function () { const userSync = { filterSettings: { @@ -1944,7 +1944,7 @@ describe('S2S Adapter', function () { device: device }); - adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(s2sBidRequest, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -2928,6 +2928,21 @@ describe('S2S Adapter', function () { expect(response).to.have.property('ttl', 60); }); + // it('handles seatnonbid responses and calls SEAT_NON_BID', function () { + // const original = CONFIG; + // CONFIG.extPrebid = { returnallbidstatus: true }; + // const nonbidResponse = {...RESPONSE_OPENRTB, ext: {seatnonbid: [{}]}}; + // config.setConfig({ CONFIG }); + // CONFIG = original; + // adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + // const responding = deepClone(nonbidResponse); + // Object.assign(responding.ext.seatnonbid, [{auctionId: 2}]) + // server.requests[0].respond(200, {}, JSON.stringify(responding)); + // const event = events.emit.secondCall.args; + // expect(event[0]).to.equal(CONSTANTS.EVENTS.SEAT_NON_BID); + // expect(event[1].seatnonbid[0]).to.have.property('auctionId', 2); + // }); + it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { defaultTtl: 30 diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js index 522a78627d7..25834e8574d 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -70,7 +70,7 @@ describe('Prebid Manager Analytics Adapter', function () { prebidmanagerAnalytics.flush(); expect(server.requests.length).to.equal(1); - expect(server.requests[0].url).to.equal('https://endpoint.prebidmanager.com/endpoint'); + expect(server.requests[0].url).to.equal('https://endpt.prebidmanager.com/endpoint'); expect(server.requests[0].requestBody.substring(0, 2)).to.equal('1:'); const pmEvents = JSON.parse(server.requests[0].requestBody.substring(2)); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 4ad048fef9a..bd35297b027 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import pubmaticAnalyticsAdapter from 'modules/pubmaticAnalyticsAdapter.js'; +import pubmaticAnalyticsAdapter, { getMetadata } from 'modules/pubmaticAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; @@ -1219,4 +1219,59 @@ describe('pubmatic analytics adapter', function () { expect(data.piid).to.equal('partnerImpressionID-1'); }); }); + + describe('Get Metadata function', function () { + it('should get the metadata object', function () { + const meta = { + networkId: 'nwid', + advertiserId: 'adid', + networkName: 'nwnm', + primaryCatId: 'pcid', + advertiserName: 'adnm', + agencyId: 'agid', + agencyName: 'agnm', + brandId: 'brid', + brandName: 'brnm', + dchain: 'dc', + demandSource: 'ds', + secondaryCatIds: ['secondaryCatIds'] + }; + const metadataObj = getMetadata(meta); + + expect(metadataObj.nwid).to.equal('nwid'); + expect(metadataObj.adid).to.equal('adid'); + expect(metadataObj.nwnm).to.equal('nwnm'); + expect(metadataObj.pcid).to.equal('pcid'); + expect(metadataObj.adnm).to.equal('adnm'); + expect(metadataObj.agid).to.equal('agid'); + expect(metadataObj.agnm).to.equal('agnm'); + expect(metadataObj.brid).to.equal('brid'); + expect(metadataObj.brnm).to.equal('brnm'); + expect(metadataObj.dc).to.equal('dc'); + expect(metadataObj.ds).to.equal('ds'); + expect(metadataObj.scids).to.be.an('array').with.length.above(0); + expect(metadataObj.scids[0]).to.equal('secondaryCatIds'); + }); + + it('should return undefined if meta is null', function () { + const meta = null; + const metadataObj = getMetadata(meta); + expect(metadataObj).to.equal(undefined); + }); + + it('should return undefined if meta is a empty object', function () { + const meta = {}; + const metadataObj = getMetadata(meta); + expect(metadataObj).to.equal(undefined); + }); + + it('should return undefined if meta object has different properties', function () { + const meta = { + a: 123, + b: 456 + }; + const metadataObj = getMetadata(meta); + expect(metadataObj).to.equal(undefined); + }); + }); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index fa8f809e8a2..ed74792e1cd 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier } from 'modules/pubmaticBidAdapter.js'; +import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject } from 'modules/pubmaticBidAdapter.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; @@ -553,7 +553,8 @@ describe('PubMatic adapter', function () { 'ext': { 'deal_channel': 6, 'advid': 976, - 'dspid': 123 + 'dspid': 123, + 'dchain': 'dchain' } }] }, { @@ -2654,13 +2655,6 @@ describe('PubMatic adapter', function () { expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpressionWithRequiredParam.native.request); }); - it('should not have valid native request if assets are not defined with minimum required params and only native is the slot', function () { - let request = spec.buildRequests(nativeBidRequestsWithoutAsset, { - auctionId: 'new-auction-id' - }); - expect(request).to.deep.equal(undefined); - }); - it('Request params should have valid native bid request for all native params', function () { let request = spec.buildRequests(nativeBidRequestsWithAllParams, { auctionId: 'new-auction-id' @@ -2863,7 +2857,7 @@ describe('PubMatic adapter', function () { expect(data.native.request).to.exist; }); - it('Request params - banner and native multiformat request - should not have native object incase of invalid config present', function() { + it('Request params - banner and native multiformat request - should have native object incase of invalid config present', function() { bannerAndNativeBidRequests[0].mediaTypes.native = { title: { required: true }, image: { required: true }, @@ -2883,10 +2877,10 @@ describe('PubMatic adapter', function () { data = data.imp[0]; expect(data.banner).to.exist; - expect(data.native).to.not.exist; + expect(data.native).to.exist; }); - it('Request params - video and native multiformat request - should not have native object incase of invalid config present', function() { + it('Request params - video and native multiformat request - should have native object incase of invalid config present', function() { videoAndNativeBidRequests[0].mediaTypes.native = { title: { required: true }, image: { required: true }, @@ -2906,7 +2900,7 @@ describe('PubMatic adapter', function () { data = data.imp[0]; expect(data.video).to.exist; - expect(data.native).to.not.exist; + expect(data.native).to.exist; }); it('should build video impression if video params are present in adunit.mediaTypes instead of bid.params', function() { @@ -3435,7 +3429,8 @@ describe('PubMatic adapter', function () { expect(response[0].ttl).to.equal(300); expect(response[0].meta.networkId).to.equal(123); expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); - expect(response[0].meta.buyerId).to.equal(976); + expect(response[0].meta.buyerId).to.equal('seat-id'); + expect(response[0].meta.dchain).to.equal('dchain'); expect(response[0].meta.clickUrl).to.equal('blackrock.com'); expect(response[0].meta.advertiserDomains[0]).to.equal('blackrock.com'); expect(response[0].referrer).to.include(data.site.ref); @@ -3723,7 +3718,87 @@ describe('PubMatic adapter', function () { }); let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); expect(newresponse[0].mediaType).to.equal('video') - }) + }); + }); + + describe('Preapare metadata', function () { + it('Should copy all fields from ext to meta', function () { + const bid = { + 'adomain': [ + 'mystartab.com' + ], + cat: ['IAB_CATEGORY'], + ext: { + advid: '12', + 'dspid': 6, + 'deal_channel': 1, + 'bidtype': 0, + advertiserId: 'adid', + // networkName: 'nwnm', + // primaryCatId: 'pcid', + // advertiserName: 'adnm', + // agencyId: 'agid', + // agencyName: 'agnm', + // brandId: 'brid', + // brandName: 'brnm', + // dchain: 'dc', + // demandSource: 'ds', + // secondaryCatIds: ['secondaryCatIds'] + } + }; + + const br = {}; + prepareMetaObject(br, bid, null); + expect(br.meta.networkId).to.equal(6); // dspid + expect(br.meta.buyerId).to.equal('12'); // adid + expect(br.meta.advertiserId).to.equal('12'); + // expect(br.meta.networkName).to.equal('nwnm'); + expect(br.meta.primaryCatId).to.equal('IAB_CATEGORY'); + // expect(br.meta.advertiserName).to.equal('adnm'); + expect(br.meta.agencyId).to.equal('12'); + // expect(br.meta.agencyName).to.equal('agnm'); + expect(br.meta.brandId).to.equal('mystartab.com'); + // expect(br.meta.brandName).to.equal('brnm'); + // expect(br.meta.dchain).to.equal('dc'); + expect(br.meta.demandSource).to.equal(6); + expect(br.meta.secondaryCatIds).to.be.an('array').with.length.above(0); + expect(br.meta.secondaryCatIds[0]).to.equal('IAB_CATEGORY'); + expect(br.meta.advertiserDomains).to.be.an('array').with.length.above(0); // adomain + expect(br.meta.clickUrl).to.equal('mystartab.com'); // adomain + }); + + it('Should be empty, when ext and adomain is absent in bid object', function () { + const bid = {}; + const br = {}; + prepareMetaObject(br, bid, null); + expect(Object.keys(br.meta).length).to.equal(0); + }); + + it('Should be empty, when ext and adomain will not have properties', function () { + const bid = { + 'adomain': [], + ext: {} + }; + const br = {}; + prepareMetaObject(br, bid, null); + expect(Object.keys(br.meta).length).to.equal(0); + expect(br.meta.advertiserDomains).to.equal(undefined); // adomain + expect(br.meta.clickUrl).to.equal(undefined); // adomain + }); + + it('Should have buyerId,advertiserId, agencyId value of site ', function () { + const bid = { + 'adomain': [], + ext: { + advid: '12', + } + }; + const br = {}; + prepareMetaObject(br, bid, '5100'); + expect(br.meta.buyerId).to.equal('5100'); // adid + expect(br.meta.advertiserId).to.equal('5100'); + expect(br.meta.agencyId).to.equal('5100'); + }); }); describe('getUserSyncs', function() { @@ -3823,184 +3898,6 @@ describe('PubMatic adapter', function () { }); }); - describe('JW player segment data for S2S', function() { - let sandbox = sinon.sandbox.create(); - beforeEach(function () { - sandbox = sinon.sandbox.create(); - }); - afterEach(function() { - sandbox.restore(); - }); - it('Should append JW player segment data to dctr values in auction endpoint', function() { - var videoAdUnit = { - 'bidderCode': 'pubmatic', - 'bids': [ - { - 'bidder': 'pubmatic', - 'params': { - 'publisherId': '156276', - 'adSlot': 'pubmatic_video2', - 'dctr': 'key1=123|key2=345', - 'pmzoneid': '1243', - 'video': { - 'mimes': ['video/mp4', 'video/x-flv'], - 'skippable': true, - 'minduration': 5, - 'maxduration': 30, - 'startdelay': 5, - 'playbackmethod': [1, 3], - 'api': [1, 2], - 'protocols': [2, 3], - 'battr': [13, 14], - 'linearity': 1, - 'placement': 2, - 'minbitrate': 10, - 'maxbitrate': 10 - } - }, - 'rtd': { - 'jwplayer': { - 'targeting': { - 'segments': ['80011026', '80011035'], - 'content': { - 'id': 'jw_d9J2zcaA' - } - } - } - }, - 'bid_id': '17a6771be26cc4', - 'ortb2Imp': { - 'ext': { - 'data': { - 'pbadslot': 'abcd', - 'jwTargeting': { - 'playerID': 'myElement1', - 'mediaID': 'd9J2zcaA' - } - } - } - } - } - ], - 'auctionStart': 1630923178417, - 'timeout': 1000, - 'src': 's2s' - } - - spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); - expect(bidRequests[0].params.dctr).to.equal('key1:val1,val2|key2:val1|jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1'); - }); - it('Should send only JW player segment data in auction endpoint, if dctr is missing', function() { - var videoAdUnit = { - 'bidderCode': 'pubmatic', - 'bids': [ - { - 'bidder': 'pubmatic', - 'params': { - 'publisherId': '156276', - 'adSlot': 'pubmatic_video2', - 'dctr': 'key1=123|key2=345', - 'pmzoneid': '1243', - 'video': { - 'mimes': ['video/mp4', 'video/x-flv'], - 'skippable': true, - 'minduration': 5, - 'maxduration': 30, - 'startdelay': 5, - 'playbackmethod': [1, 3], - 'api': [1, 2], - 'protocols': [2, 3], - 'battr': [13, 14], - 'linearity': 1, - 'placement': 2, - 'minbitrate': 10, - 'maxbitrate': 10 - } - }, - 'rtd': { - 'jwplayer': { - 'targeting': { - 'segments': ['80011026', '80011035'], - 'content': { - 'id': 'jw_d9J2zcaA' - } - } - } - }, - 'bid_id': '17a6771be26cc4', - 'ortb2Imp': { - 'ext': { - 'data': { - 'pbadslot': 'abcd', - 'jwTargeting': { - 'playerID': 'myElement1', - 'mediaID': 'd9J2zcaA' - } - } - } - } - } - ], - 'auctionStart': 1630923178417, - 'timeout': 1000, - 'src': 's2s' - } - - delete bidRequests[0].params.dctr; - spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); - expect(bidRequests[0].params.dctr).to.equal('jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1'); - }); - - it('Should not send any JW player segment data in auction endpoint, if it is not available', function() { - var videoAdUnit = { - 'bidderCode': 'pubmatic', - 'bids': [ - { - 'bidder': 'pubmatic', - 'params': { - 'publisherId': '156276', - 'adSlot': 'pubmatic_video2', - 'dctr': 'key1=123|key2=345', - 'pmzoneid': '1243', - 'video': { - 'mimes': ['video/mp4', 'video/x-flv'], - 'skippable': true, - 'minduration': 5, - 'maxduration': 30, - 'startdelay': 5, - 'playbackmethod': [1, 3], - 'api': [1, 2], - 'protocols': [2, 3], - 'battr': [13, 14], - 'linearity': 1, - 'placement': 2, - 'minbitrate': 10, - 'maxbitrate': 10 - } - }, - 'bid_id': '17a6771be26cc4', - 'ortb2Imp': { - 'ext': { - 'data': { - 'pbadslot': 'abcd', - 'jwTargeting': { - 'playerID': 'myElement1', - 'mediaID': 'd9J2zcaA' - } - } - } - } - } - ], - 'auctionStart': 1630923178417, - 'timeout': 1000, - 'src': 's2s' - } - spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); - expect(bidRequests[0].params.dctr).to.equal('key1:val1,val2|key2:val1'); - }); - }) - describe('Checking for Video.Placement property', function() { let sandbox, utilsMock; const adUnit = 'Div1'; diff --git a/test/spec/modules/pubwiseBidAdapter_spec.js b/test/spec/modules/pubwiseBidAdapter_spec.js index d7b7a527485..780cc8b8fdb 100644 --- a/test/spec/modules/pubwiseBidAdapter_spec.js +++ b/test/spec/modules/pubwiseBidAdapter_spec.js @@ -2,7 +2,7 @@ import {expect} from 'chai'; import {spec} from 'modules/pubwiseBidAdapter.js'; -import {_checkMediaType} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent +import {_checkVideoPlacement, _checkMediaType} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent import {_parseAdSlot} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent import * as utils from 'src/utils.js'; @@ -486,6 +486,28 @@ const samplePBBidObjects = [ ]; describe('PubWiseAdapter', function () { + describe('Handles Params Properly', function () { + it('properly sets the default endpoint', function () { + const referenceEndpoint = 'https://bid.pubwise.io/prebid'; + let endpointBidRequest = utils.deepClone(sampleValidBidRequests); + // endpointBidRequest.forEach((bidRequest) => { + // bidRequest.params.endpoint_url = newEndpoint; + // }); + let result = spec.buildRequests(endpointBidRequest, {auctionId: 'placeholder'}); + expect(result.url).to.equal(referenceEndpoint); + }); + + it('allows endpoint to be reset', function () { + const newEndpoint = 'http://www.pubwise.io/endpointtest'; + let endpointBidRequest = utils.deepClone(sampleValidBidRequests); + endpointBidRequest.forEach((bidRequest) => { + bidRequest.params.endpoint_url = newEndpoint; + }); + let result = spec.buildRequests(endpointBidRequest, {auctionId: 'placeholder'}); + expect(result.url).to.equal(newEndpoint); + }); + }); + describe('Properly Validates Bids', function () { it('valid bid', function () { let validBid = { @@ -555,14 +577,14 @@ describe('PubWiseAdapter', function () { it('identifies native adm type', function() { let adm = '{"ver":"1.2","assets":[{"title":{"text":"PubWise Test"}},{"img":{"type":3,"url":"http://www.pubwise.io"}},{"img":{"type":1,"url":"http://www.pubwise.io"}},{"data":{"type":2,"value":"PubWise Test Desc"}},{"data":{"type":1,"value":"PubWise.io"}}],"link":{"url":""}}'; let newBid = {mediaType: 'unknown'}; - _checkMediaType(adm, newBid); + _checkMediaType({adm}, newBid); expect(newBid.mediaType).to.equal('native', adm + ' Is a Native adm'); }); it('identifies banner adm type', function() { let adm = '

PubWise Test Bid

'; let newBid = {mediaType: 'unknown'}; - _checkMediaType(adm, newBid); + _checkMediaType({adm}, newBid); expect(newBid.mediaType).to.equal('banner', adm + ' Is a Banner adm'); }); }); @@ -582,4 +604,292 @@ describe('PubWiseAdapter', function () { expect(pbResponse).to.deep.equal(samplePBBidObjects); }); }); + + describe('Video Testing', function () { + /** + * Video Testing + */ + + const videoBidRequests = + [ + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'pwbid', + bidId: '22bddb28db77d', + adUnitCode: 'Div1', + params: { + siteId: 'xxxxxx', + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30, + startdelay: 5, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + battr: [13, 14], + linearity: 1, + placement: 2, + minbitrate: 10, + maxbitrate: 10 + } + } + } + ]; + + let newvideoRequests = [{ + 'bidder': 'pwbid', + 'params': { + 'siteId': 'xxxxx', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + + let newvideoBidResponses = { + 'body': { + 'id': '1621441141473', + 'cur': 'USD', + 'customdata': 'openrtb1', + 'ext': { + 'buyid': 'myBuyId' + }, + 'seatbid': [{ + 'bid': [{ + 'id': '2c95df014cfe97', + 'impid': '2c95df014cfe97', + 'price': 4.2, + 'cid': 'test1', + 'crid': 'test2', + 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", + 'w': 0, + 'h': 0 + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + }; + + let videoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': '', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6 + } + }] + }] + } + }; + + it('Request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = request.data; + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('Div1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['startdelay']).to.equal(videoBidRequests[0].params.video['startdelay']); + + expect(data.imp[0]['video']['playbackmethod']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['playbackmethod'][0]).to.equal(videoBidRequests[0].params.video['playbackmethod'][0]); + expect(data.imp[0]['video']['playbackmethod'][1]).to.equal(videoBidRequests[0].params.video['playbackmethod'][1]); + + expect(data.imp[0]['video']['api']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['api'][0]).to.equal(videoBidRequests[0].params.video['api'][0]); + expect(data.imp[0]['video']['api'][1]).to.equal(videoBidRequests[0].params.video['api'][1]); + + expect(data.imp[0]['video']['protocols']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['protocols'][0]).to.equal(videoBidRequests[0].params.video['protocols'][0]); + expect(data.imp[0]['video']['protocols'][1]).to.equal(videoBidRequests[0].params.video['protocols'][1]); + + expect(data.imp[0]['video']['battr']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['battr'][0]).to.equal(videoBidRequests[0].params.video['battr'][0]); + expect(data.imp[0]['video']['battr'][1]).to.equal(videoBidRequests[0].params.video['battr'][1]); + + expect(data.imp[0]['video']['linearity']).to.equal(videoBidRequests[0].params.video['linearity']); + expect(data.imp[0]['video']['placement']).to.equal(videoBidRequests[0].params.video['placement']); + expect(data.imp[0]['video']['minbitrate']).to.equal(videoBidRequests[0].params.video['minbitrate']); + expect(data.imp[0]['video']['maxbitrate']).to.equal(videoBidRequests[0].params.video['maxbitrate']); + + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + + it('should assign mediaType even if bid.ext.mediaType does not exists', function() { + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video'); + }); + + it('should not assign renderer if bid is video and request is for instream', function() { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should process instream and outstream', function() { + let validOutstreamRequest = + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + }, + bidder: 'pwbid', + bidId: '47acc48ad47af5', + requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + params: { + siteId: 'xxxxx', + adSlot: 'Div1', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }; + + let outstreamBidRequest = + [ + validOutstreamRequest + ]; + + let validInstreamRequest = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'pwbid', + bidId: '47acc48ad47af5', + requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + params: { + siteId: 'xxxxx', + adSlot: 'Div1', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }; + + let instreamBidRequest = + [ + validInstreamRequest + ]; + + let outstreamRequest = spec.isBidRequestValid(validOutstreamRequest); + expect(outstreamRequest).to.equal(false); + + let instreamRequest = spec.isBidRequestValid(validInstreamRequest); + expect(instreamRequest).to.equal(true); + }); + + describe('Checking for Video.Placement property', function() { + let sandbox, utilsMock; + const adUnit = 'DivCheckPlacement'; + const msg_placement_missing = 'PubWise: Video.Placement param missing for DivCheckPlacement'; + let videoData = { + battr: [6, 7], + skipafter: 15, + maxduration: 50, + context: 'instream', + playerSize: [640, 480], + skip: 0, + connectiontype: [1, 2, 6], + skipmin: 10, + minduration: 10, + mimes: ['video/mp4', 'video/x-flv'], + } + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }) + + it('should log Video.Placement param missing', function() { + _checkVideoPlacement(videoData, adUnit); + // when failing this gives an odd message about "AssertError: expected logWarn to be called with arguments" it means the specific message expected + sinon.assert.calledWith(utils.logWarn, msg_placement_missing); + }) + it('shoud not log Video.Placement param missing', function() { + videoData['placement'] = 1; + _checkVideoPlacement(videoData, adUnit); + sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); + }) + }); + // end video testing + }); }); diff --git a/test/spec/modules/relevadRtdProvider_spec.js b/test/spec/modules/relevadRtdProvider_spec.js new file mode 100644 index 00000000000..678ea26eed6 --- /dev/null +++ b/test/spec/modules/relevadRtdProvider_spec.js @@ -0,0 +1,412 @@ +import { addRtdData, getBidRequestData, relevadSubmodule, serverData } from 'modules/relevadRtdProvider.js'; +import { server } from 'test/mocks/xhr.js'; +import {config} from 'src/config.js'; +import { deepClone, deepAccess, deepSetValue } from '../../../src/utils.js'; + +const responseHeader = {'Content-Type': 'application/json'}; + +const moduleConfigCommon = { + 'dryrun': true, + params: { + setgpt: true, + minscore: 50, + partnerid: 12345, + bidders: [{ bidder: 'appnexus' }, + { bidder: 'rubicon', }, + { bidder: 'smart', }, + { bidder: 'ix', }, + { bidder: 'proxistore', }, + { bidder: 'other' }] + } +}; + +const reqBidsCommon = { + 'timeout': 10000, + 'adUnitCodes': ['/19968336/header-bid-tag-0'], + 'ortb2Fragments': { + 'global': { + 'site': { + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html?pbjs_debug=true', + 'domain': 'localhost.localdomain:8888', + 'publisher': { + 'domain': 'localhost.localdomain:8888' + } + }, + 'device': { + 'w': 355, + 'h': 682, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + 'language': 'en' + } + }, + 'bidder': {} + } +}; + +const adUnitsCommon = [ + { + 'code': '/19968336/header-bid-tag-0', + 'mediaTypes': { + 'banner': { 'sizes': [[728, 90]] } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { 'placementId': '13144370' } + }, + { bidder: 'other' }, + { bidder: 'rubicon', 'params': { id: 1 } }, + { bidder: 'smart', }, + { bidder: 'ix', }, + { bidder: 'proxistore', } + ] + } +]; + +describe('relevadRtdProvider', function() { + describe('relevadSubmodule', function() { + it('successfully instantiates', function () { + expect(relevadSubmodule.init()).to.equal(true); + }); + }); + + describe('Add segments and categories test 1', function() { + it('adds contextual categories and segments', function() { + let moduleConfig = { ...deepClone(moduleConfigCommon) }; + let reqBids = { + ...deepClone(reqBidsCommon), + 'adUnits': deepClone(adUnitsCommon), + }; + + let data = { + segments: ['segment1', 'segment2'], + cats: { 'category3': 100 }, + }; + + (config.getConfig('ix') || {}).firstPartyData = null; + addRtdData(reqBids, data, moduleConfig, () => {}); + expect(reqBids.adUnits[0].bids[0].params.keywords).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + expect(reqBids.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('relevad_rtd', ['category3']); + expect(reqBids.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('relevad_rtd', ['segment1', 'segment2']); + expect(reqBids.adUnits[0].bids[3].params).to.have.deep.property('target', 'relevad_rtd=segment1;relevad_rtd=segment2;relevad_rtd=category3'); + expect(reqBids.adUnits[0].bids[5].ortb2.user.ext.data).to.have.deep.property('segments', ['segment1', 'segment2']); + expect(reqBids.adUnits[0].bids[5].ortb2.user.ext.data).to.have.deep.property('contextual_categories', ['category3']); + expect(reqBids.ortb2Fragments.bidder.rubicon.user.ext.data).to.have.deep.property('relevad_rtd', ['segment1', 'segment2']); + expect(config.getConfig('ix.firstPartyData')).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + }); + }); + + describe('Add segments and categories test 2 to one bidder out of many', function() { + it('adds contextual categories and segments', function() { + let moduleConfig = { ...deepClone(moduleConfigCommon) }; + let reqBids = { + ...deepClone(reqBidsCommon), + 'adUnits': deepClone(adUnitsCommon), + }; + + let data = { + segments: ['segment1', 'segment2'], + cats: { 'category3': 100 }, + wl: { 'appnexus': { 'placementId': '13144370' } }, + }; + + (config.getConfig('ix') || {}).firstPartyData = null; + addRtdData(reqBids, data, moduleConfig, () => { }); + expect(reqBids.adUnits[0].bids[0].params.keywords).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + expect(reqBids.adUnits[0].bids[1].ortb2?.site?.ext?.data || {}).to.not.have.property('relevad_rtd'); + expect(reqBids.adUnits[0].bids[1].ortb2?.user?.ext?.data || {}).to.not.have.property('relevad_rtd'); + expect(reqBids.adUnits[0].bids[3].params || {}).to.not.have.deep.property('target', 'relevad_rtd=segment1;relevad_rtd=segment2;relevad_rtd=category3'); + expect(reqBids.adUnits[0].bids[5].ortb2?.user?.ext?.data || {}).to.not.have.deep.property('segments', ['segment1', 'segment2']); + expect(reqBids.adUnits[0].bids[5].ortb2?.user?.ext?.data || {}).to.not.have.deep.property('contextual_categories', ['category3']); + expect(reqBids.adUnits[0].bids[5].ortb2?.user?.ext?.data || {}).to.not.have.deep.property('contextual_categories', {'0': 'category3'}); + expect(reqBids.ortb2Fragments?.bidder?.rubicon?.user?.ext?.data || {}).to.not.have.deep.property('relevad_rtd', ['segment1', 'segment2']); + expect(config.getConfig('ix.firstPartyData') || {}).to.not.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + }); + }); + + describe('Add segments and categories test 4', function() { + it('adds contextual categories and segments', function() { + let moduleConfig = { + 'dryrun': true, + params: { + setgpt: true, + minscore: 50, + partnerid: 12345, + } + }; + + let reqBids = { + 'timeout': 10000, + 'adUnits': deepClone(adUnitsCommon), + 'adUnitCodes': [ '/19968336/header-bid-tag-0' ], + 'ortb2Fragments': { + 'global': { + 'site': { + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html?pbjs_debug=true', + 'domain': 'localhost.localdomain:8888', + 'publisher': { + 'domain': 'localhost.localdomain:8888' + } + }, + 'device': { + 'w': 355, + 'h': 682, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + 'language': 'en' + } + }, + 'bidder': {} + }, + 'metrics': {}, + 'defer': { 'promise': {} } + } + + let data = { + segments: ['segment1', 'segment2'], + cats: {'category3': 100} + }; + (config.getConfig('ix') || {}).firstPartyData = null; + addRtdData(reqBids, data, moduleConfig, () => {}); + expect(reqBids.adUnits[0].bids[0].params.keywords).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + }); + }); + + describe('Get Segments And Categories', function() { + it('gets data from async request and adds contextual categories and segments', function() { + const moduleConfig = { + params: { + 'dryrun': true, + sdtgpt: false, + minscore: 50, + bidders: [{ bidder: 'appnexus' }, + { bidder: 'other' }] + } + }; + + let reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }, { + bidder: 'other' + }] + }] + }; + + let data = { + segments: ['segment1', 'segment2'], + cats: {'category3': 100} + }; + + getBidRequestData(reqBidsConfigObj, () => {}, moduleConfig, {}); + + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(reqBidsConfigObj.adUnits[0].bids[0].params.keywords).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('relevad_rtd', ['category3']); + expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('relevad_rtd', ['segment1', 'segment2']); + }); + }); +}); + +describe('Process auction end data', function() { + it('Collects bid data on auction end event', function() { + const auctionEndData = { + 'auctionDetails': { + 'auctionId': 'f7ec9895-5809-475e-8fef-49cbc221921a', + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': '/19968336/header-bid-tag-0', + 'mediaTypes': { + 'banner': { 'sizes': [ [ 728, 90 ] ] } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '13144370', + 'keywords': { + 'relevad_rtd': [ 'IAB410-391', 'IAB63-53' ] + } + } + } + ], + 'ortb2Imp': { 'ext': { 'data': { 'relevad_rtd': [ 'IAB410-391', 'IAB63-53' ] }, } }, + 'sizes': [ [ 728, 90 ] ], + } + ], + 'adUnitCodes': [ '/19968336/header-bid-tag-0' ], + 'bidderRequests': [ + { + 'bidderCode': 'appnexus', + 'auctionId': 'f7ec9895-5809-475e-8fef-49cbc221921a', + 'bidderRequestId': '1d917281b2bf6c', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '13144370', + 'keywords': { + 'relevad_rtd': [ + 'IAB410-391', + 'IAB63-53' + ] + } + }, + 'ortb2Imp': { + 'ext': { 'data': { 'relevad_rtd': [ 'IAB410-391', 'IAB63-53' ] }, } + }, + 'mediaTypes': { 'banner': { 'sizes': [ [ 728, 90 ] ] } }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ [ 728, 90 ] ], + 'bidId': '20f0b347b07f94', + 'bidderRequestId': '1d917281b2bf6c', + 'auctionId': 'f7ec9895-5809-475e-8fef-49cbc221921a', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'domain': 'localhost.localdomain:8888', + 'publisher': { 'domain': 'localhost.localdomain:8888' }, + 'cat': [ 'IAB410-391', 'IAB63-53' ], + 'pagecat': [ 'IAB410-391', 'IAB63-53' ], + 'sectioncat': [ 'IAB410-391', 'IAB63-53' ] + }, + 'device': { + 'w': 326, + 'h': 649, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + 'language': 'en' + } + } + } + ], + 'timeout': 10000, + 'refererInfo': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html' + ], + 'topmostLocation': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'location': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'canonicalUrl': null, + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'domain': 'www.localhost.localdomain:8888', + 'ref': null, + 'legacy': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html' ], + 'referer': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'canonicalUrl': null + } + }, + 'ortb2': { + 'site': { + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'domain': 'localhost.localdomain:8888', + 'publisher': { 'domain': 'localhost.localdomain:8888' }, + 'cat': [ 'IAB410-391', 'IAB63-53' ], + 'pagecat': [ 'IAB410-391', 'IAB63-53' ], + 'sectioncat': [ 'IAB410-391', 'IAB63-53' ] + }, + 'device': { + 'w': 326, + 'h': 649, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + 'language': 'en' + } + }, + 'start': 1674132848498 + } + ], + 'noBids': [], + 'bidsReceived': [ + { + 'bidderCode': 'appnexus', + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '3222e6ead116f3', + 'requestId': '20f0b347b07f94', + 'transactionId': 'df8586ac-6476-4fbf-a727-eda99996dc39', + 'auctionId': 'f7ec9895-5809-475e-8fef-49cbc221921a', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 1.5, + 'creativeId': 98493734, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'adapterCode': 'appnexus', + 'originalCpm': 1.5, + 'originalCurrency': 'USD', + 'responseTimestamp': 1674132848649, + 'requestTimestamp': 1674132848498, + 'bidder': 'appnexus', + 'size': '728x90', + } + ], + }, + 'config': { + 'name': 'RelevadRTDModule', + 'waitForIt': true, + 'dryrun': true, + 'params': { + 'partnerid': 12345, + 'setgpt': true + } + }, + 'userConsent': { 'gdpr': null, 'usp': null, 'gpp': null, 'coppa': false } + }; + + let auctionDetails = auctionEndData['auctionDetails']; + let userConsent = auctionEndData['userConsent']; + let moduleConfig = auctionEndData['config']; + + relevadSubmodule.onAuctionEndEvent(auctionDetails, moduleConfig, userConsent); + expect(serverData.clientdata).to.deep.equal( + { + 'event': 'bids', + 'adunits': [ + { + 'code': '/19968336/header-bid-tag-0', + 'bids': [ + { + 'bidder': 'appnexus', + 'cpm': 1.5, + 'currency': 'USD', + 'type': 'banner', + 'ttr': undefined, + 'dealId': undefined, + 'size': '728x90' + } + ] + } + ], + 'reledata': { segments: ['segment1', 'segment2'], cats: { 'category3': 100 }, }, + 'gdpra': '', + 'gdprc': '', + 'aid': '', + 'cid': '12345', + 'pid': '', + } + ); + }); +}); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index e81ef1c805f..592fdbc6f9c 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -5,13 +5,22 @@ import { masSizeOrdering, resetUserSync, classifiedAsVideo, - resetRubiConf + resetRubiConf, + converter } from 'modules/rubiconBidAdapter.js'; import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {find} from 'src/polyfill.js'; import {createEidsArray} from 'modules/userId/eids.js'; +import 'modules/schain.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/userId/index.js'; +import 'modules/priceFloors.js'; +import 'modules/multibid/index.js'; +import adapterManager from 'src/adapterManager.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid const PBS_INTEGRATION = 'pbjs'; @@ -102,6 +111,9 @@ describe('the rubicon adapter', function () { referrer: 'localhost', latLong: [40.7607823, '111.8910325'] }, + mediaTypes: { + banner: [[300, 250]] + }, adUnitCode: '/19968336/header-bid-tag-0', code: 'div-1', sizes: [[300, 250], [320, 50]], @@ -307,7 +319,6 @@ describe('the rubicon adapter', function () { accountId: '14062', siteId: '70608', zoneId: '335918', - pchain: 'GAM:11111-reseller1:22222', userId: '12346', keywords: ['a', 'b', 'c'], inventory: { @@ -323,6 +334,9 @@ describe('the rubicon adapter', function () { referrer: 'localhost', latLong: [40.7607823, '111.8910325'] }, + mediaTypes: { + banner: [[300, 250]] + }, adUnitCode: '/19968336/header-bid-tag-0', code: 'div-1', sizes: [[300, 250], [320, 50]], @@ -415,7 +429,6 @@ describe('the rubicon adapter', function () { 'rand': '0.1', 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', - 'x_source.pchain': 'GAM:11111-reseller1:22222', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -583,20 +596,11 @@ describe('the rubicon adapter', function () { expect(data['p_pos']).to.equal('atf;;btf;;'); }); - it('should not send x_source.pchain to AE if params.pchain is not specified', function () { - var noPchainRequest = utils.deepClone(bidderRequest); - delete noPchainRequest.bids[0].params.pchain; - - let [request] = spec.buildRequests(noPchainRequest.bids, noPchainRequest); - expect(request.data).to.contain('&site_id=70608&'); - expect(request.data).to.not.contain('x_source.pchain'); - }); - it('ad engine query params should be ordered correctly', 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', 'l_pb_bid_id', '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', '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]); @@ -1542,7 +1546,7 @@ describe('the rubicon adapter', function () { bidderRequest.auctionStart + 100 ); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); let post = request.data; expect(post).to.have.property('imp'); @@ -1553,23 +1557,21 @@ describe('the rubicon adapter', function () { expect(imp.video.w).to.equal(640); expect(imp.video.h).to.equal(480); expect(imp.video.pos).to.equal(1); - expect(imp.video.context).to.equal('instream'); expect(imp.video.minduration).to.equal(15); expect(imp.video.maxduration).to.equal(30); expect(imp.video.startdelay).to.equal(0); expect(imp.video.skip).to.equal(1); expect(imp.video.skipafter).to.equal(15); - expect(imp.ext.rubicon.video.playerWidth).to.equal(640); - expect(imp.ext.rubicon.video.playerHeight).to.equal(480); - expect(imp.ext.rubicon.video.size_id).to.equal(201); - expect(imp.ext.rubicon.video.language).to.equal('en'); + expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.prebid.bidder.rubicon.video.size_id).to.equal(201); + expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); // Also want it to be in post.site.content.language - expect(post.site.content.language).to.equal('en'); - expect(imp.ext.rubicon.video.skip).to.equal(1); - expect(imp.ext.rubicon.video.skipafter).to.equal(15); - expect(imp.ext.prebid.auctiontimestamp).to.equal(1472239426000); + expect(imp.ext.prebid.bidder.rubicon.video.skip).to.equal(1); + expect(imp.ext.prebid.bidder.rubicon.video.skipafter).to.equal(15); + expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); // should contain version - expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.version$'}); + expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: $$PREBID_GLOBAL$$.version}); expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); // EIDs should exist expect(post.user.ext).to.have.property('eids').that.is.an('array'); @@ -1672,8 +1674,8 @@ describe('the rubicon adapter', function () { expect( bidderRequest.bids[0].getFloor.calledWith({ currency: 'USD', - mediaType: 'video', - size: [640, 480] + mediaType: '*', + size: '*' }) ).to.be.true; @@ -1701,7 +1703,7 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].bidfloor).to.equal(1.23); }); - it('should continue with auction and log error if getFloor throws one', function () { + it('should continue with auction if getFloor throws error', function () { createVideoBidderRequest(); // default getFloor response is empty object so should not break and not send hard_floor bidderRequest.bids[0].getFloor = () => { @@ -1713,20 +1715,18 @@ describe('the rubicon adapter', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // log error called - expect(logErrorSpy.calledOnce).to.equal(true); - // should have an imp expect(request.data.imp).to.exist.and.to.be.a('array'); expect(request.data.imp).to.have.lengthOf(1); // should be NO bidFloor expect(request.data.imp[0].bidfloor).to.be.undefined; + expect(request.data.imp[0].bidfloorcur).to.be.undefined; }); it('should add alias name to PBS Request', function () { createVideoBidderRequest(); - + adapterManager.aliasRegistry['superRubicon'] = 'rubicon'; bidderRequest.bidderCode = 'superRubicon'; bidderRequest.bids[0].bidder = 'superRubicon'; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); @@ -1736,8 +1736,8 @@ describe('the rubicon adapter', function () { expect(request.data.ext.prebid.aliases).to.deep.equal({superRubicon: 'rubicon'}); // should have the imp ext bidder params be under the alias name not rubicon superRubicon - expect(request.data.imp[0].ext).to.have.property('superRubicon').that.is.an('object'); - expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); + expect(request.data.imp[0].ext.prebid.bidder).to.have.property('superRubicon').that.is.an('object'); + expect(request.data.imp[0].ext.prebid.bidder).to.not.haveOwnProperty('rubicon'); }); it('should add floors flag correctly to PBS Request', function () { @@ -2000,7 +2000,7 @@ describe('the rubicon adapter', function () { let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - expect(request.data.imp[0].ext.rubicon.video.size_id).to.equal(203); + expect(request.data.imp[0].ext.prebid.bidder.rubicon.video.size_id).to.equal(203); }); it('should send banner request when outstream or instream video included but no rubicon video obect is present', function () { @@ -2064,8 +2064,7 @@ describe('the rubicon adapter', function () { }; return config[key]; }); - - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); expect(request.data.regs.coppa).to.equal(1); }); @@ -2203,7 +2202,7 @@ describe('the rubicon adapter', function () { bidderRequest.auctionStart + 100 ); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); let post = request.data; expect(post).to.have.property('imp') @@ -2214,21 +2213,18 @@ describe('the rubicon adapter', function () { expect(imp.video.w).to.equal(640); expect(imp.video.h).to.equal(480); expect(imp.video.pos).to.equal(1); - expect(imp.video.context).to.equal('instream'); expect(imp.video.minduration).to.equal(15); expect(imp.video.maxduration).to.equal(30); expect(imp.video.startdelay).to.equal(0); expect(imp.video.skip).to.equal(1); expect(imp.video.skipafter).to.equal(15); - expect(imp.ext.rubicon.video.playerWidth).to.equal(640); - expect(imp.ext.rubicon.video.playerHeight).to.equal(480); - expect(imp.ext.rubicon.video.size_id).to.equal(201); - expect(imp.ext.rubicon.video.language).to.equal('en'); + expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); + // Also want it to be in post.site.content.language expect(post.site.content.language).to.equal('en'); - expect(imp.ext.rubicon.video.skip).to.equal(1); - expect(imp.ext.rubicon.video.skipafter).to.equal(15); - expect(imp.ext.prebid.auctiontimestamp).to.equal(1472239426000); + expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); // Config user.id @@ -2366,6 +2362,110 @@ describe('the rubicon adapter', function () { expect(bid.params.video).to.not.be.undefined; }); }); + + if (FEATURES.NATIVE) { + describe('when there is a native request', function () { + describe('and bidonmultiformat = undefined (false)', () => { + it('should send only one native bid to PBS endpoint', function () { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].params = { + video: {} + } + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request.data.imp).to.have.nested.property('[0].native'); + }); + + describe('that contains also a banner mediaType', function () { + it('should send the banner to fastlane BUT NOT the native bid because missing params.video', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].mediaTypes.banner = { + sizes: [[300, 250]] + } + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('GET'); + expect(request.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }); + describe('with another banner request', () => { + it('should send the native bid to PBS and the banner to fastlane', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].params = { video: {} }; + // add second bidRqeuest + bidReq.bids.push({ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: bidReq.bids[0].params + }) + let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); + expect(request1.method).to.equal('POST'); + expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request1.data.imp).to.have.nested.property('[0].native'); + expect(request2.method).to.equal('GET'); + expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }); + }); + + describe('with bidonmultiformat === true', () => { + it('should send two requests, to PBS with 2 imps', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second mediaType + bidReq.bids[0].mediaTypes = { + ...bidReq.bids[0].mediaTypes, + banner: { + sizes: [[300, 250]] + } + }; + bidReq.bids[0].params.bidonmultiformat = true; + let [pbsRequest, fastlanteRequest] = spec.buildRequests(bidReq.bids, bidReq); + expect(pbsRequest.method).to.equal('POST'); + expect(pbsRequest.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(pbsRequest.data.imp).to.have.nested.property('[0].native'); + expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }); + describe('with bidonmultiformat === false', () => { + it('should send only banner request because there\'s no params.video', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second mediaType + bidReq.bids[0].mediaTypes = { + ...bidReq.bids[0].mediaTypes, + banner: { + sizes: [[300, 250]] + } + }; + + let [fastlanteRequest, ...others] = spec.buildRequests(bidReq.bids, bidReq); + expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + expect(others).to.be.empty; + }); + + it('should not send native to PBS even if there\'s param.video', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second mediaType + bidReq.bids[0].mediaTypes = { + ...bidReq.bids[0].mediaTypes, + banner: { + sizes: [[300, 250]] + } + }; + // by adding this, when bidonmultiformat is false, the native request will be sent to pbs + bidReq.bids[0].params = { + video: {} + } + let [fastlaneRequest, ...other] = spec.buildRequests(bidReq.bids, bidReq); + expect(fastlaneRequest.method).to.equal('GET'); + expect(fastlaneRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + expect(other).to.be.empty; + }); + }); + }); + } }); describe('interpretResponse', function () { @@ -3119,7 +3219,7 @@ describe('the rubicon adapter', function () { seatbid: [{ bid: [{ id: '0', - impid: 'instream_video1', + impid: '/19968336/header-bid-tag-0', adomain: ['test.com'], price: 2, crid: '4259970', @@ -3144,9 +3244,9 @@ describe('the rubicon adapter', function () { }], }; - let bids = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + let bids = spec.interpretResponse({body: response}, {data: request}); expect(bids).to.be.lengthOf(1); @@ -3167,6 +3267,18 @@ describe('the rubicon adapter', function () { }); }); + if (FEATURES.NATIVE) { + describe('for native', () => { + it('should get a native bid', () => { + const nativeBidderRequest = addNativeToBidRequest(bidderRequest); + const request = converter.toORTB({bidderRequest: nativeBidderRequest, bidRequests: nativeBidderRequest.bids}); + let response = getNativeResponse({impid: request.imp[0].id}); + let bids = spec.interpretResponse({body: response}, {data: request}); + expect(bids).to.have.nested.property('[0].native'); + }); + }); + } + describe('for outstream video', function () { const sandbox = sinon.createSandbox(); beforeEach(function () { @@ -3196,7 +3308,7 @@ describe('the rubicon adapter', function () { seatbid: [{ bid: [{ id: '0', - impid: 'outstream_video1', + impid: '/19968336/header-bid-tag-0', adomain: ['test.com'], price: 2, crid: '4259970', @@ -3221,9 +3333,9 @@ describe('the rubicon adapter', function () { }], }; - let bids = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + let bids = spec.interpretResponse({body: response}, { data: request }); expect(bids).to.be.lengthOf(1); @@ -3256,7 +3368,7 @@ describe('the rubicon adapter', function () { seatbid: [{ bid: [{ id: '0', - impid: 'outstream_video1', + impid: '/19968336/header-bid-tag-0', adomain: ['test.com'], price: 2, crid: '4259970', @@ -3282,11 +3394,11 @@ describe('the rubicon adapter', function () { }], }; + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + sinon.spy(window.MagniteApex, 'renderAd'); - let bids = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); + let bids = spec.interpretResponse({body: response}, {data: request}); const bid = bids[0]; bid.adUnitCode = 'outstream_video1_placement'; const adUnit = document.createElement('div'); @@ -3617,3 +3729,164 @@ describe('the rubicon adapter', function () { }); }); }); + +function addNativeToBidRequest(bidderRequest) { + const nativeOrtbRequest = { + assets: [{ + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600 + } + }, + { + id: 2, + required: 1, + data: { + type: 1 + } + }] + }; + bidderRequest.refererInfo = { + page: 'localhost' + } + bidderRequest.bids[0] = { + bidder: 'rubicon', + params: { + accountId: '14062', + siteId: '70608', + zoneId: '335918', + }, + adUnitCode: '/19968336/header-bid-tag-0', + code: 'div-1', + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest + } + return bidderRequest; +} + +function getNativeResponse(options = {impid: 1234}) { + return { + 'id': 'd7786a80-bfb4-4541-859f-225a934e81d4', + 'seatbid': [ + { + 'bid': [ + { + 'id': '971650', + 'impid': options.impid, + 'price': 20, + 'adm': { + 'ver': '1.2', + 'assets': [ + { + 'id': 0, + 'title': { + 'text': 'This is a title' + }, + 'link': { + 'clicktrackers': [ + 'http://localhost:5500/event?type=click1&component=card&asset=0' + ] + } + }, + { + 'id': 1, + 'img': { + 'url': 'https:\\\\/\\\\/vcdn.adnxs.com\\\\/p\\\\/creative-image\\\\/94\\\\/22\\\\/cd\\\\/0f\\\\/9422cd0f-f400-45d3-80f5-2b92629d9257.jpg', + 'h': 2250, + 'w': 3000 + }, + 'link': { + 'clicktrackers': [ + 'http://localhost:5500/event?type=click1&component=card&asset=1' + ] + } + }, + { + 'id': 2, + 'data': { + 'value': 'this is asset data 1 that corresponds to sponsoredBy' + } + } + ], + 'link': { + 'url': 'https://magnite.com', + 'clicktrackers': [ + 'http://localhost:5500/event?type=click1&component=card', + 'http://localhost:5500/event?type=click2&component=card' + ] + }, + 'jstracker': '', + 'eventtrackers': [ + { + 'event': 1, + 'method': 2, + 'url': 'http://localhost:5500/event?type=1&method=2' + }, + { + 'event': 2, + 'method': 1, + 'url': 'http://localhost:5500/event?type=v50&component=card' + } + ] + }, + 'adid': '392180', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://lax1-ib.adnxs.com/cr?id=97494403', + 'cid': '9325', + 'crid': '97494403', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 600, + 'ext': { + 'prebid': { + 'targeting': { + 'hb_bidder': 'rubicon', + 'hb_cache_host': 'prebid.lax1.adnxs-simple.com', + 'hb_cache_path': '/pbc/v1/cache', + 'hb_pb': '20.00' + }, + 'type': 'native', + 'video': { + 'duration': 0, + 'primary_category': '' + } + }, + 'rubicon': { + 'auction_id': 642778043863823100, + 'bid_ad_type': 3, + 'bidder_id': 2, + 'brand_id': 555545 + } + } + } + ], + 'seat': 'rubicon' + } + ], + 'cur': 'USD' + }; +} diff --git a/test/spec/modules/showheroes-bsBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js index 1aa7b132221..30e95b04ccf 100644 --- a/test/spec/modules/showheroes-bsBidAdapter_spec.js +++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js @@ -5,7 +5,7 @@ import {VIDEO, BANNER} from 'src/mediaTypes.js' const bidderRequest = { refererInfo: { - referer: 'https://example.com' + canonicalUrl: 'https://example.com' } } @@ -19,6 +19,8 @@ const gdpr = { } } +const uspConsent = '1---'; + const schain = { 'schain': { 'validation': 'strict', @@ -338,18 +340,28 @@ describe('shBidAdapter', function () { }); }) - it('passes gdpr if present', function () { - const request = spec.buildRequests([bidRequestVideo], {...bidderRequest, ...gdpr}) + it('passes gdpr & uspConsent if present', function () { + const request = spec.buildRequests([bidRequestVideo], { + ...bidderRequest, + ...gdpr, + uspConsent, + }) const payload = request.data.requests[0]; expect(payload).to.be.an('object'); expect(payload.gdprConsent).to.eql(gdpr.gdprConsent) + expect(payload.uspConsent).to.eql(uspConsent) }) - it('passes gdpr if present (V2)', function () { - const request = spec.buildRequests([bidRequestVideoV2], {...bidderRequest, ...gdpr}) + it('passes gdpr & usp if present (V2)', function () { + const request = spec.buildRequests([bidRequestVideoV2], { + ...bidderRequest, + ...gdpr, + uspConsent, + }) const context = request.data.context; expect(context).to.be.an('object'); expect(context.gdprConsent).to.eql(gdpr.gdprConsent) + expect(context.uspConsent).to.eql(uspConsent) }) it('passes schain object if present', function() { @@ -379,7 +391,7 @@ describe('shBidAdapter', function () { expect(spec.interpretResponse({body: []}, {data: {meta: {}}}).length).to.equal(0) }) - const vastTag = 'https://video-library.stage.showheroes.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6' + const vastTag = 'https://test.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6' const vastXml = '' const basicResponse = { @@ -389,7 +401,7 @@ describe('shBidAdapter', function () { 'context': 'instream', 'bidId': '38b373e1e31c18', 'size': {'width': 640, 'height': 480}, - 'vastTag': 'https:\/\/video-library.stage.showheroes.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6', + 'vastTag': 'https:\/\/test.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6', 'vastXml': vastXml, 'adomain': adomain, }; @@ -423,13 +435,13 @@ describe('shBidAdapter', function () { 'height': 480, 'advertiserDomain': [], 'callbacks': { - 'won': ['https://api-n729.qa.viralize.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok'] + 'won': ['https://test.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok'] }, 'mediaType': 'video', 'adomain': adomain, }; - const vastUrl = 'https://api-n729.qa.viralize.com/vast/?zid=AACBWAcof-611K4U&u=https://example.org/?foo=bar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ecd03ce381505ccdeb88e555b05001&width=300&height=200&prebidmode=1' + const vastUrl = 'https://test.com/vast/?zid=AACBWAcof-611K4U&u=https://example.org/?foo=bar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ecd03ce381505ccdeb88e555b05001&width=300&height=200&prebidmode=1' const responseVideoV2 = { 'bidResponses': [{ @@ -443,7 +455,7 @@ describe('shBidAdapter', function () { 'bidResponses': [{ ...basicResponseV2, 'context': 'outstream', - 'ad': '', + 'ad': '', }], }; @@ -533,10 +545,6 @@ describe('shBidAdapter', function () { expect(renderer.config.vastUrl).to.equal(vastTag) renderer.render(bid) - // TODO: fix these. our tests should not be reliant on third-party scripts. wtf - // const scripts = document.querySelectorAll('script[src="https://static.showheroes.com/publishertag.js"]') - // expect(scripts.length).to.equal(1) - const spots = document.querySelectorAll('.showheroes-spot') expect(spots.length).to.equal(1) }) @@ -590,8 +598,6 @@ describe('shBidAdapter', function () { renderer.render(bid) const iframeDocument = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document) - // const scripts = iframeDocument.querySelectorAll('script[src="https://static.showheroes.com/publishertag.js"]') - // expect(scripts.length).to.equal(1) const spots = iframeDocument.querySelectorAll('.showheroes-spot') expect(spots.length).to.equal(1) }) @@ -602,8 +608,6 @@ describe('shBidAdapter', function () { customRender: function (bid, embedCode) { const container = document.createElement('div') container.appendChild(embedCode) - // const scripts = container.querySelectorAll('script[src="https://static.showheroes.com/publishertag.js"]') - // expect(scripts.length).to.equal(1) const spots = container.querySelectorAll('.showheroes-spot') expect(spots.length).to.equal(1) diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index c7dbf1363e7..7891a49b9db 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -297,6 +297,21 @@ describe('smaatoBidAdapterTest', () => { expect(req.regs.ext.us_privacy).to.equal('uspConsentString'); }); + it('sends gpp', () => { + const ortb2 = { + regs: { + gpp: 'gppString', + gpp_sid: [7] + } + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.gpp).to.eql('gppString'); + expect(req.regs.ext.gpp_sid).to.eql([7]); + }); + it('sends no schain if no schain exists', () => { const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index b41b0280235..6b3147859bf 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -138,10 +138,13 @@ function mockRandomSizeArray(len) { }); } -function mockBidRequestListData(mediaType, size) { +function mockBidRequestListData(mediaType, size, customSizes) { return Array.apply(null, {length: size}).map((i, index) => { const id = Math.floor(Math.random() * 800) * (index + 1); let mediaTypes; + let params = { + endpointId: id + } if (mediaType == 'video') { mediaTypes = { @@ -158,13 +161,15 @@ function mockBidRequestListData(mediaType, size) { } } + if (customSizes === undefined || customSizes.length > 0) { + params.sizes = customSizes + } + return { adUnitCode: `adUnitCode-${id}`, mediaTypes: mediaTypes, bidId: `bidId-${id}`, - params: { - endpointId: id - } + params: params } }); } @@ -211,7 +216,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { let mockBidRequest; let mockReferer; beforeEach(() => { - mockBidRequest = mockBidRequestListData('banner', 8); + mockBidRequest = mockBidRequestListData('banner', 8, []); mockReferer = mockRefererData(); }); it('has return data', () => { @@ -238,13 +243,45 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { }); }); +describe('SmartyTechDSPAdapter: buildRequests banner custom size', () => { + let mockBidRequest; + let mockReferer; + beforeEach(() => { + mockBidRequest = mockBidRequestListData('banner', 8, [[300, 600]]); + mockReferer = mockRefererData(); + }); + + it('correct request data', () => { + const data = spec.buildRequests(mockBidRequest, mockReferer).data; + data.forEach((request, index) => { + expect(request.banner.sizes).to.be.equal(mockBidRequest[index].params.sizes); + }) + }); +}); + +describe('SmartyTechDSPAdapter: buildRequests video custom size', () => { + let mockBidRequest; + let mockReferer; + beforeEach(() => { + mockBidRequest = mockBidRequestListData('video', 8, [[300, 300], [250, 250]]); + mockReferer = mockRefererData(); + }); + + it('correct request data', () => { + const data = spec.buildRequests(mockBidRequest, mockReferer).data; + data.forEach((request, index) => { + expect(request.video.sizes).to.be.equal(mockBidRequest[index].params.sizes); + }) + }); +}); + describe('SmartyTechDSPAdapter: interpretResponse', () => { let mockBidRequest; let mockReferer; let request; let mockResponse; beforeEach(() => { - const brData = mockBidRequestListData('banner', 2); + const brData = mockBidRequestListData('banner', 2, []); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { @@ -290,7 +327,7 @@ describe('SmartyTechDSPAdapter: interpretResponse video', () => { let request; let mockResponse; beforeEach(() => { - const brData = mockBidRequestListData('video', 2); + const brData = mockBidRequestListData('video', 2, []); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index d182b9db24c..8c9bbe3b336 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -557,6 +557,7 @@ describe('SSPBC adapter', function () { const nativeAssets = payloadNative.imp && payloadNative.imp[0].native.request; expect(payloadNative.imp.length).to.equal(1); + expect(nativeAssets).to.contain('{"id":0,"required":true,"title":{"len":80}}'); expect(nativeAssets).to.contain('{"id":2,"required":true,"img":{"type":1,"w":50,"h":50}}'); expect(nativeAssets).to.contain('{"id":3,"required":true,"img":{"type":3,"w":150,"h":50}}'); @@ -610,14 +611,14 @@ describe('SSPBC adapter', function () { expect(result.length).to.equal(bids.length); expect(resultSingle.length).to.equal(1); - expect(resultSingle[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl'); + expect(resultSingle[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls'); }); it('should create bid from OneCode (parameter-less) request, if response contains siteId', function () { let resultOneCode = spec.interpretResponse(serverResponseOneCode, requestOneCode); expect(resultOneCode.length).to.equal(1); - expect(resultOneCode[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl'); + expect(resultOneCode[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls'); }); it('should not create bid from OneCode (parameter-less) request, if response does not contain siteId', function () { @@ -648,7 +649,7 @@ describe('SSPBC adapter', function () { expect(resultVideo.length).to.equal(1); let videoBid = resultVideo[0]; - expect(videoBid).to.have.keys('adType', 'bidderCode', 'cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'vastContent', 'vastXml', 'vastUrl'); + expect(videoBid).to.have.keys('adType', 'bidderCode', 'cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'vastContent', 'vastXml', 'vastUrl', 'vurls'); expect(videoBid.adType).to.equal('instream'); expect(videoBid.mediaType).to.equal('video'); expect(videoBid.vastXml).to.match(/^<\?xml.*<\/VAST>$/); @@ -662,8 +663,8 @@ describe('SSPBC adapter', function () { expect(resultNative.length).to.equal(1); 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', 'javascriptTrackers'); + expect(nativeBid).to.have.keys('bidderCode', 'cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'native', 'vurls'); + expect(nativeBid.native).to.have.keys('image', 'icon', 'title', 'sponsoredBy', 'body', 'clickUrl', 'impressionTrackers', 'javascriptTrackers', 'clickTrackers'); }); }); diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js new file mode 100644 index 00000000000..d095fd3cf55 --- /dev/null +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -0,0 +1,402 @@ +import { expect } from 'chai'; +import { spec } from 'modules/stvBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const ENDPOINT_URL = 'https://ads.smartstream.tv/r/'; +const ENDPOINT_URL_DEV = 'https://ads.smartstream.tv/r/'; + +describe('stvAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'floorprice': 1000000, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + '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 false when required params are not passed', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'someIncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + // banner + { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'floorprice': 1000000, + 'geo': { + 'country': 'DE' + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e1', + 'bidderRequestId': '22edbae2733bf61', + 'auctionId': '1d1a030790a475', + 'adUnitCode': 'testDiv1', + }, + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e2', + 'bidderRequestId': '22edbae2733bf62', + 'auctionId': '1d1a030790a476' + }, { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'floorprice': 1000000, + 'geo': { + 'country': 'DE' + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e3', + 'bidderRequestId': '22edbae2733bf69', + 'auctionId': '1d1a030790a477', + 'adUnitCode': 'testDiv2' + }, + // video + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true, + 'max_duration': 20, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + }, + }, + + 'bidId': '30b31c1838de1e4', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv3' + }, + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + 'maxduration': 40, + } + }, + 'bidId': '30b31c1838de1e41', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv4' + }, + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true, + 'max_duration': 20, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + 'maxduration': 40, + } + }, + 'bidId': '30b31c1838de1e41', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv4' + } + + ]; + + // With gdprConsent + var bidderRequest = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: { someData: 'value' }, + gdprApplies: true + } + }; + + var request1 = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + it('sends bid request 1 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').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&bcat=IAB2%2CIAB4&dvt=desktop&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); + }); + + var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; + it('sends bid request 2 endpoint via GET', function() { + expect(request2.method).to.equal('GET'); + expect(request2.url).to.equal(ENDPOINT_URL); + let data = request2.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e2&pbver=test&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&prebidDevMode=1&media_types%5Bbanner%5D=300x250'); + }); + + // Without gdprConsent + var bidderRequestWithoutGdpr = { + refererInfo: { + referer: 'some_referrer.net' + } + }; + var request3 = spec.buildRequests([bidRequests[2]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 3 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').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e3&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&bcat=IAB2%2CIAB4&dvt=desktop&pbcode=testDiv2&media_types%5Bbanner%5D=300x250'); + }); + + var request4 = spec.buildRequests([bidRequests[3]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 4 (video) without gdprConsent endpoint via GET', function() { + expect(request4.method).to.equal('GET'); + expect(request4.url).to.equal(ENDPOINT_URL); + let data = request4.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e4&pbver=test&pfilter%5Bmax_duration%5D=20&prebidDevMode=1&pbcode=testDiv3&media_types%5Bvideo%5D=640x480'); + }); + + var request5 = spec.buildRequests([bidRequests[4]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 5 (video) to our endpoint via GET', function() { + expect(request5.method).to.equal('GET'); + expect(request5.url).to.equal(ENDPOINT_URL); + let data = request5.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&pfilter%5Bmax_duration%5D=40&prebidDevMode=1&pbcode=testDiv4&media_types%5Bvideo%5D=640x480'); + }); + + var request6 = spec.buildRequests([bidRequests[5]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 6 (video) to our endpoint via GET', function() { + expect(request6.method).to.equal('GET'); + expect(request6.url).to.equal(ENDPOINT_URL); + let data = request6.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&pfilter%5Bmax_duration%5D=20&prebidDevMode=1&pbcode=testDiv4&media_types%5Bvideo%5D=640x480'); + }); + }); + + describe('interpretResponse', function() { + let serverResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'type': 'sspHTML', + 'tag': '', + 'requestId': '220ed41385952a', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682', + 'adomain': ['bdomain'] + } + }; + let serverVideoResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'vastXml': '{"reason":7001,"status":"accepted"}', + 'requestId': '220ed41385952a', + 'type': 'vast2', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + } + }; + + let expectedResponse = [{ + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + meta: { advertiserDomains: ['bdomain'] }, + ad: '', + }, { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + meta: { advertiserDomains: [] }, + vastXml: '{"reason":7001,"status":"accepted"}', + mediaType: 'video', + }]; + + it('should get the correct bid response by display ad', function() { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].meta.advertiserDomains.length).to.equal(1); + expect(result[0].meta.advertiserDomains[0]).to.equal(expectedResponse[0].meta.advertiserDomains[0]); + }); + + it('should get the correct smartstream video bid response by display ad', function() { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + expect(result[0].meta.advertiserDomains.length).to.equal(0); + }); + + it('handles empty bid response', function() { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe(`getUserSyncs test usage`, function() { + let serverResponses; + + beforeEach(function() { + serverResponses = [{ + body: { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '', + userSync: { + iframeUrl: ['anyIframeUrl?a=1'], + imageUrl: ['anyImageUrl', 'anyImageUrl2'] + } + } + }]; + }); + + it(`return value should be an array`, function() { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + }); + it(`array should have only one object and it should have a property type = 'iframe'`, function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(1); + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses); + expect(userSync).to.have.property('type'); + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for iframe`, function() { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses, { consentString: 'anyString' }); + expect(userSync.url).to.be.equal('anyIframeUrl?a=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for image`, function() { + let [userSync] = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, { gdprApplies: true, consentString: 'anyString' }); + expect(userSync.url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('image'); + }); + it(`we have valid sync url for image and iframe`, function() { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses, { gdprApplies: true, consentString: 'anyString' }); + expect(userSync.length).to.be.equal(3); + expect(userSync[0].url).to.be.equal('anyIframeUrl?a=1&gdpr=1&gdpr_consent=anyString') + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[1].url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync[1].type).to.be.equal('image'); + expect(userSync[2].url).to.be.equal('anyImageUrl2?gdpr=1&gdpr_consent=anyString') + expect(userSync[2].type).to.be.equal('image'); + }); + }); + + describe(`getUserSyncs test usage in passback response`, function() { + let serverResponses; + + beforeEach(function() { + serverResponses = [{ + body: { + reason: 8002, + status: 'error', + msg: 'passback', + } + }]; + }); + + it(`check for zero array when iframeEnabled`, function() { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(0); + }); + it(`check for zero array when iframeEnabled`, function() { + expect(spec.getUserSyncs({ pixelEnabled: true })).to.be.an('array'); + expect(spec.getUserSyncs({ pixelEnabled: true }, serverResponses).length).to.be.equal(0); + }); + }); +}); diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 221ffb8371f..4debf516f89 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -167,7 +167,8 @@ describe('triplelift adapter', function () { mediaTypes: { video: { context: 'instream', - playerSize: [640, 480] + playerSize: [640, 480], + playbackmethod: 5 } }, adUnitCode: 'adunit-code-instream', @@ -292,7 +293,8 @@ describe('triplelift adapter', function () { mediaTypes: { video: { context: 'instream', - playerSize: [640, 480] + playerSize: [640, 480], + playbackmethod: [1, 2, 3] }, banner: { sizes: [ @@ -1181,6 +1183,16 @@ describe('triplelift adapter', function () { 'gpp_sid': [7] }) }); + it('should cast playbackmethod as an array if it is an integer and it exists', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[1].video.playbackmethod).to.be.a('array'); + expect(request.data.imp[1].video.playbackmethod).to.deep.equal([5]); + }); + it('should set playbackmethod as an array if it exists as an array', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[5].video.playbackmethod).to.be.a('array'); + expect(request.data.imp[5].video.playbackmethod).to.deep.equal([1, 2, 3]); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 9869d072657..ebaed2502f8 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -81,6 +81,18 @@ describe('ttdBidAdapter', function () { delete bid.mediaTypes expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false if bidfloor is passed incorrectly', function () { + let bid = makeBid(); + bid.params.bidfloor = 'invalid bidfloor'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true if bidfloor is passed correctly as a float', function () { + let bid = makeBid(); + bid.params.bidfloor = 3.01; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); }); describe('banner', function () { @@ -535,6 +547,17 @@ describe('ttdBidAdapter', function () { expect(requestBody.site.ref).to.equal('https://ref.example.com'); expect(requestBody.site.keywords).to.equal('power tools, drills'); }); + + it('should fallback to floor module if no bidfloor is sent ', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + const bidfloor = 5.00; + clonedBannerRequests[0].getFloor = () => { + return { currency: 'USD', floor: bidfloor }; + }; + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + config.resetConfig(); + expect(requestBody.imp[0].bidfloor).to.equal(bidfloor); + }); }); describe('buildRequests-banner-multiple', function () { diff --git a/test/spec/modules/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index 70d09513f27..4201ecc2329 100644 --- a/test/spec/modules/underdogmediaBidAdapter_spec.js +++ b/test/spec/modules/underdogmediaBidAdapter_spec.js @@ -1,29 +1,38 @@ -import { expect } from 'chai'; -import { spec, resetUserSync } from 'modules/underdogmediaBidAdapter.js'; +import { + expect +} from 'chai'; +import { + spec, + resetUserSync +} from 'modules/underdogmediaBidAdapter.js'; describe('UnderdogMedia adapter', function () { let bidRequests; let bidderRequest; beforeEach(function () { - bidRequests = [ - { - bidder: 'underdogmedia', - params: { - siteId: 12143 - }, - adUnitCode: '/19968336/header-bid-tag-1', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600], [728, 90], [160, 600], [320, 50]], - } - }, - bidId: '23acc48ad47af5', - auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; + bidRequests = [{ + bidder: 'underdogmedia', + params: { + siteId: 12143 + }, + adUnitCode: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [160, 600], + [320, 50] + ], + } + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + }]; bidderRequest = { timeout: 3000, @@ -49,7 +58,10 @@ describe('UnderdogMedia adapter', function () { }, mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [ + [300, 250], + [300, 600] + ] } } }; @@ -76,7 +88,10 @@ describe('UnderdogMedia adapter', function () { params: {}, mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [ + [300, 250], + [300, 600] + ] } } }; @@ -86,90 +101,94 @@ describe('UnderdogMedia adapter', function () { }); it('request data should contain sid', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - bidder: 'underdogmedia', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + bidder: 'underdogmedia', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sid).to.equal('12143'); + expect(request.data.sid).to.equal(12143); }); it('request data should contain sizes', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sizes).to.equal('300x250,728x90'); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('728x90'); }); it('request data should contain gdpr info', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.gdprApplies).to.equal(true); - expect(request.data.consentGiven).to.equal(true); - expect(request.data.consentData).to.equal('consentDataString'); + expect(request.data.gdpr.gdprApplies).to.equal(true); + expect(request.data.gdpr.consentGiven).to.equal(true); + expect(request.data.gdpr.consentData).to.equal('consentDataString'); }); it('should not build a request if no vendorConsent', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; let bidderRequest = { timeout: 3000, @@ -189,22 +208,23 @@ describe('UnderdogMedia adapter', function () { }); it('should properly build a request if no vendorConsent but no gdprApplies', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; let bidderRequest = { timeout: 3000, @@ -220,30 +240,32 @@ describe('UnderdogMedia adapter', function () { } const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sizes).to.equal('300x250,728x90'); - expect(request.data.sid).to.equal('12143'); - expect(request.data.gdprApplies).to.equal(false); - expect(request.data.consentGiven).to.equal(false); - expect(request.data.consentData).to.equal('consentDataString'); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('728x90'); + expect(request.data.sid).to.equal(12143); + expect(request.data.gdpr.gdprApplies).to.equal(false); + expect(request.data.gdpr.consentGiven).to.equal(false); + expect(request.data.gdpr.consentData).to.equal('consentDataString'); }); it('should properly build a request if gdprConsent empty', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; let bidderRequest = { timeout: 3000, @@ -251,21 +273,663 @@ describe('UnderdogMedia adapter', function () { } const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sizes).to.equal('300x250,728x90'); - expect(request.data.sid).to.equal('12143'); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('728x90'); + expect(request.data.sid).to.equal(12143); }); it('should have uspConsent if defined', function () { const uspConsent = '1YYN' bidderRequest.uspConsent = uspConsent const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.uspConsent).to.equal(uspConsent); + expect(request.data.usp.uspConsent).to.equal(uspConsent); + }); + + it('should have correct number of placements', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-2460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '3a378b833cdef4', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-3460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '4088f04e07c2a1', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-2' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + } + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.usp.uspConsent).to.be.undefined; + expect(request.data.placements.length).to.equal(3); + }); + + it('should have correct adUnitCode for each placement', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-2460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '3a378b833cdef4', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-3460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '4088f04e07c2a1', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-2' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + } + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.usp.uspConsent).to.be.undefined; + expect(request.data.placements[0].adUnitCode).to.equal('div-gpt-ad-1460505748561-0'); + expect(request.data.placements[1].adUnitCode).to.equal('div-gpt-ad-2460505748561-0'); + expect(request.data.placements[2].adUnitCode).to.equal('div-gpt-ad-3460505748561-0'); + }); + + it('should have gpid if it exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].gpid).to.equal('/19968336/header-bid-tag-0'); + }); + + it('gpid should be undefined if it does not exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].gpid).to.equal(undefined); + }); + + it('should have productId equal to 1 if the productId is standard', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143', + productId: 'standard' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].productId).to.equal(1); + }); + + it('should have productId equal to 2 if the productId is adhesion', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143', + productId: 'adhesion' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].productId).to.equal(2); + }); + + it('productId should default to 1 if it is not defined', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].productId).to.equal(1); + }); + + it('should have correct sizes for multiple placements', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-2460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '3a378b833cdef4', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-3460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '4088f04e07c2a1', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-2' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + } + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].sizes.length).to.equal(2); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('160x600'); + expect(request.data.placements[1].sizes.length).to.equal(1); + expect(request.data.placements[1].sizes[0]).to.equal('300x250'); + expect(request.data.placements[2].sizes.length).to.equal(1); + expect(request.data.placements[2].sizes[0]).to.equal('160x600'); + }); + + it('should have ref if it exists', function () { + let bidderRequest = { + timeout: 3000, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '159': 1 + }, + }, + }, + refererInfo: { + ref: 'www.example.com' + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.ref).to.equal('www.example.com'); + }); + + it('ref should be undefined if it does not exist', function () { + let bidderRequest = { + timeout: 3000, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '159': 1 + }, + }, + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.ref).to.equal(undefined); + }); + + it('should have pubcid if it exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.pubcid).to.equal('ba6cbf43-abc0-4d61-b14f-e10f605b74d7'); + }); + + it('pubcid should be undefined if it does not exist', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.pubcid).to.equal(undefined); + }); + + it('should have unifiedId if tdid if it exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.unifiedId).to.equal('7a9fc5a2-346d-4502-826e-017a9badf5f3'); + }); + + it('unifiedId should be undefined if tdid does not exist', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.unifiedId).to.equal(undefined); }); - it('should not have uspConsent if not defined', function () { - bidderRequest.uspConsent = undefined + it('should have correct viewability information', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.uspConsent).to.be.undefined; + + expect(request.data.placements[0].viewability).to.equal(-1) }); }); @@ -273,26 +937,27 @@ describe('UnderdogMedia adapter', function () { it('should return complete bid response', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_code_html', - advertiser_domains: ['domain1'], - cpm: 2.5, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - }, - { - ad_code_html: 'ad_code_html', - cpm: 2.5, - height: '250', - mid: '32633', - notification_url: 'notification_url', - tid: '2', - width: '300' - }, + mids: [{ + ad_code_html: 'ad_code_html', + ad_unit_code: '/19968336/header-bid-tag-1', + advertiser_domains: ['domain1'], + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160', + }, + { + ad_code_html: 'ad_code_html', + ad_unit_code: '/19968336/header-bid-tag-1', + cpm: 3.0, + height: '250', + mid: '32633', + notification_url: 'notification_url', + tid: '2', + width: '300' + }, ] } }; @@ -326,17 +991,15 @@ describe('UnderdogMedia adapter', function () { it('should return empty bid response on incorrect size', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_code_html', - cpm: 2.5, - height: '123', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: 'ad_code_html', + cpm: 2.5, + height: '123', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -348,17 +1011,15 @@ describe('UnderdogMedia adapter', function () { it('should return empty bid response on 0 cpm', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_code_html', - cpm: 0, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: 'ad_code_html', + cpm: 0, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -370,17 +1031,15 @@ describe('UnderdogMedia adapter', function () { it('should return empty bid response if no ad in response', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: '', - cpm: 2.5, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: '', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -392,17 +1051,16 @@ describe('UnderdogMedia adapter', function () { it('ad html string should contain the notification urls', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_cod_html', - cpm: 2.5, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: 'ad_cod_html', + ad_unit_code: '/19968336/header-bid-tag-1', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -430,15 +1088,14 @@ describe('UnderdogMedia adapter', function () { const responseWithUserSyncs = [{ body: { - userSyncs: [ - { - type: 'image', - url: 'https://test.url.com' - }, - { - type: 'iframe', - url: 'https://test.url.com' - } + userSyncs: [{ + type: 'image', + url: 'https://test.url.com' + }, + { + type: 'iframe', + url: 'https://test.url.com' + } ] } }]; diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index bf27ef0ff81..33e2e2ea264 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -27,7 +27,7 @@ import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; import {id5IdSubmodule} from 'modules/id5IdSystem.js'; import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import {dmdIdSubmodule} from 'modules/dmdIdSystem.js'; -import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem.js'; +import {liveIntentIdSubmodule, setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent} from 'modules/liveIntentIdSystem.js'; import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; import {netIdSubmodule} from 'modules/netIdSystem.js'; import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; @@ -147,6 +147,7 @@ describe('User ID', function () { hook.ready(); uninstallGdprEnforcement(); localStorage.removeItem(PBJS_USER_ID_OPTOUT_NAME); + liveIntentIdSubmoduleDoNotFireEvent(); }); beforeEach(function () { diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 24d565805d3..97f8af97339 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -110,6 +110,24 @@ const BIDDER_REQUEST = { 'regs': { 'gpp': 'gpp_string', 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } } }, }; @@ -281,6 +299,22 @@ describe('VidazooBidAdapter', function () { schain: VIDEO_BID.schain, sessionId: '', sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), isStorageAllowed: true, @@ -330,6 +364,22 @@ describe('VidazooBidAdapter', function () { transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/videoModule/pbVideo_spec.js b/test/spec/modules/videoModule/pbVideo_spec.js index 41780a007dd..2e26737da40 100644 --- a/test/spec/modules/videoModule/pbVideo_spec.js +++ b/test/spec/modules/videoModule/pbVideo_spec.js @@ -174,6 +174,37 @@ describe('Prebid Video', function () { expect(nextFn.calledOnce).to.be.true; expect(nextFn.getCall(0).args[0].ortb2).to.be.deep.equal({ site: { content: { test: 'contentTestValue' } } }); }); + + it('allows publishers to override video param', function () { + const getOrtbVideoSpy = videoCoreMock.getOrtbVideo = sinon.spy(() => ({ + test: 'videoTestValue', + test2: 'videoModuleValue' + })); + + let beforeBidRequestCallback; + const requestBids = { + before: callback_ => beforeBidRequestCallback = callback_ + }; + + pbVideoFactory(null, null, Object.assign({}, pbGlobalMock, { requestBids })); + expect(beforeBidRequestCallback).to.not.be.undefined; + const nextFn = sinon.spy(); + const adUnits = [{ + code: 'ad1', + mediaTypes: { + video: { + test2: 'publisherValue' + } + }, + video: { divId: 'divId' } + }]; + beforeBidRequestCallback(nextFn, { adUnits }); + expect(getOrtbVideoSpy.calledOnce).to.be.true; + const adUnit = adUnits[0]; + expect(adUnit.mediaTypes.video).to.have.property('test', 'videoTestValue'); + expect(adUnit.mediaTypes.video).to.have.property('test2', 'publisherValue'); + expect(nextFn.calledOnce).to.be.true; + }); }); describe('Ad tag injection', function () { diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js new file mode 100644 index 00000000000..93eeb7b556c --- /dev/null +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -0,0 +1,400 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/visiblemeasuresBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'visiblemeasures' +const adUrl = 'https://us-e.visiblemeasures.com/pbjs'; +const syncUrl = 'https://cs.visiblemeasures.com'; + +describe('VisibleMeasuresBidAdapter', 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(adUrl); + }); + + 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(`${syncUrl}/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(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) + }); + }); +}); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index b8a66e7c3b9..9a486cd6c34 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -1273,7 +1273,7 @@ describe('VisxAdapter', function () { it('onTimeout', function () { const data = [{ timeout: 3000, adUnitCode: 'adunit-code-1', auctionId: '1cbd2feafe5e8b', bidder: 'visx', bidId: '23423', params: [{ uid: '1' }] }]; - const expectedData = [{ ...data[0], params: [{ uid: 1 }] }]; + const expectedData = [{ timeout: 3000, params: [{ uid: 1 }] }]; spec.onTimeout(data); expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/bid_timeout//' + JSON.stringify(expectedData))).to.equal(true); }); diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index df91100b966..12d413a9c93 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -1,34 +1,10 @@ import { assert, expect } from 'chai'; -import { spec } from 'modules/yandexBidAdapter.js'; +import { spec, NATIVE_ASSETS } from 'modules/yandexBidAdapter.js'; import { parseUrl } from 'src/utils.js'; -import { BANNER } from '../../../src/mediaTypes'; +import { BANNER, NATIVE } from '../../../src/mediaTypes'; +import {OPENRTB} from '../../../modules/rtbhouseBidAdapter'; describe('Yandex adapter', function () { - function getBidConfig() { - return { - bidder: 'yandex', - params: { - placementId: '123-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 = getBidRequest(); @@ -65,19 +41,17 @@ describe('Yandex adapter', function () { }); describe('buildRequests', function () { - const gdprConsent = { - gdprApplies: 1, - consentString: 'concent-string', - apiVersion: 1, - }; - const bidderRequest = { refererInfo: { domain: 'ya.ru', ref: 'https://ya.ru/', page: 'https://ya.ru/', }, - gdprConsent + gdprConsent: { + gdprApplies: 1, + consentString: 'concent-string', + apiVersion: 1, + }, }; it('creates a valid banner request', function () { @@ -101,7 +75,7 @@ describe('Yandex adapter', function () { const { search: query } = parsedRequestUrl expect(parsedRequestUrl.hostname).to.equal('bs.yandex.ru'); - expect(parsedRequestUrl.pathname).to.equal('/metadsp/123'); + expect(parsedRequestUrl.pathname).to.equal('/prebid/123'); expect(query['imp-id']).to.equal('1'); expect(query['target-ref']).to.equal('ya.ru'); @@ -112,21 +86,197 @@ describe('Yandex adapter', function () { expect(request.data).to.exist; expect(data.site).to.not.equal(null); - expect(data.site.page_url).to.equal('https://ya.ru/'); - expect(data.site.ref_url).to.equal('https://ya.ru/'); + expect(data.site.page).to.equal('https://ya.ru/'); + expect(data.site.ref).to.equal('https://ya.ru/'); + }); + + describe('banner', () => { + it('should create valid banner object', () => { + const bannerRequest = getBidRequest({ + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + }, + } + }); - // expect(data.device).to.not.equal(null); - // expect(data.device.w).to.equal(window.innerWidth); - // expect(data.device.h).to.equal(window.innerHeight); + const requests = spec.buildRequests([bannerRequest], bidderRequest); + expect(requests[0].data.imp).to.have.lengthOf(1); - 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); + const imp = requests[0].data.imp[0]; + expect(imp.banner).to.not.equal(null); + expect(imp.banner.w).to.equal(300); + expect(imp.banner.h).to.equal(250); + + expect(imp.banner.format).to.deep.equal([ + { w: 300, h: 250 }, + { w: 300, h: 600 }, + ]); + }); }); + + describe('native', () => { + function buildRequestAndGetNativeParams(extra) { + const bannerRequest = getBidRequest(extra); + const requests = spec.buildRequests([bannerRequest], bidderRequest); + + return JSON.parse(requests[0].data.imp[0].native.request); + } + + it('should extract native params', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + title: { + required: true, + len: 100, + }, + body: { + len: 90 + }, + body2: { + len: 90 + }, + sponsoredBy: { + len: 25, + }, + icon: { + sizes: [32, 32], + }, + image: { + required: true, + sizes: [300, 250], + }, + }, + }, + }); + const sortedAssetsList = nativeParams.assets.sort((a, b) => a.id - b.id); + + expect(sortedAssetsList).to.deep.equal([ + { + id: NATIVE_ASSETS.title[0], + required: 1, + title: { + len: 100, + } + }, + { + id: NATIVE_ASSETS.body[0], + data: { + type: NATIVE_ASSETS.body[1], + len: 90, + }, + }, + { + id: NATIVE_ASSETS.body2[0], + data: { + type: NATIVE_ASSETS.body2[1], + len: 90, + }, + }, + { + id: NATIVE_ASSETS.sponsoredBy[0], + data: { + type: NATIVE_ASSETS.sponsoredBy[1], + len: 25, + }, + }, + { + id: NATIVE_ASSETS.icon[0], + img: { + type: NATIVE_ASSETS.icon[1], + w: 32, + h: 32, + }, + }, + { + id: NATIVE_ASSETS.image[0], + required: 1, + img: { + type: NATIVE_ASSETS.image[1], + w: 300, + h: 250, + }, + }, + ]); + }); + + it('should parse multiple image sizes', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + image: { + sizes: [[300, 250], [100, 100]], + }, + }, + }, + }); + + expect(nativeParams.assets[0]).to.deep.equal({ + id: NATIVE_ASSETS.image[0], + img: { + type: NATIVE_ASSETS.image[1], + w: 300, + h: 250, + }, + }); + }); + + it('should parse aspect ratios with min_width', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + image: { + aspect_ratios: [{ + min_width: 320, + ratio_width: 4, + ratio_height: 3, + }], + }, + }, + }, + }); + + expect(nativeParams.assets[0]).to.deep.equal({ + id: NATIVE_ASSETS.image[0], + img: { + type: NATIVE_ASSETS.image[1], + wmin: 320, + hmin: 240, + }, + }); + }); + + it('should parse aspect ratios without min_width', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + image: { + aspect_ratios: [{ + ratio_width: 4, + ratio_height: 3, + }], + }, + }, + }, + }); + + expect(nativeParams.assets[0]).to.deep.equal({ + id: NATIVE_ASSETS.image[0], + img: { + type: NATIVE_ASSETS.image[1], + wmin: 100, + hmin: 75, + }, + }); + }); + }) }); - describe('response handler', function () { + describe('interpretResponse', function () { const bannerRequest = getBidRequest(); const bannerResponse = { @@ -172,5 +322,125 @@ describe('Yandex adapter', function () { expect(rtbBid.meta.advertiserDomains).to.deep.equal(['example.com']); }); + + describe('native', () => { + function getNativeAdmResponse() { + return { + native: { + link: { + url: 'https://example.com' + }, + imptrackers: [ + 'https://example.com/imptracker' + ], + assets: [ + { + title: { + text: 'title text', + }, + id: NATIVE_ASSETS.title[0], + }, + { + data: { + value: 'body text' + }, + id: NATIVE_ASSETS.body[0], + }, + { + data: { + value: 'sponsoredBy text' + }, + id: NATIVE_ASSETS.sponsoredBy[0], + }, + { + img: { + url: 'https://example.com/image', + w: 200, + h: 150, + }, + id: NATIVE_ASSETS.image[0], + }, + { + img: { + url: 'https://example.com/icon', + h: 32, + w: 32 + }, + id: NATIVE_ASSETS.icon[0], + }, + ] + } + }; + } + + it('handles native responses', function() { + bannerRequest.bidRequest = { + mediaType: NATIVE, + bidId: 'bidid-1', + }; + + const nativeAdmResponce = getNativeAdmResponse(); + const bannerResponse = { + body: { + seatbid: [{ + bid: [ + { + impid: 1, + price: 0.3, + adomain: [ + 'example.com' + ], + adid: 'yabs.123=', + adm: JSON.stringify(nativeAdmResponce), + }, + ], + }], + }, + }; + + const result = spec.interpretResponse(bannerResponse, bannerRequest); + + expect(result).to.have.lengthOf(1); + expect(result[0]).to.exist; + + const bid = result[0]; + expect(bid.meta.advertiserDomains).to.deep.equal(['example.com']); + expect(bid.native).to.deep.equal({ + clickUrl: 'https://example.com', + impressionTrackers: ['https://example.com/imptracker'], + title: 'title text', + body: 'body text', + sponsoredBy: 'sponsoredBy text', + image: { + url: 'https://example.com/image', + width: 200, + height: 150, + }, + icon: { + url: 'https://example.com/icon', + width: 32, + height: 32, + }, + }); + }); + }); }); }); + +function getBidConfig() { + return { + bidder: 'yandex', + params: { + placementId: '123-1', + }, + }; +} + +function getBidRequest(extra = {}) { + return { + ...getBidConfig(), + bidId: 'bidid-1', + adUnitCode: 'adUnit-123', + ...extra, + }; +} diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index f616403cfcd..4e18f49c849 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -25,6 +25,22 @@ describe('Zeta Ssp Bid Adapter', function () { } ]; + const schain = { + complete: 1, + nodes: [ + { + asi: 'asi1', + sid: 'sid1', + rid: 'rid1' + }, + { + asi: 'asi2', + sid: 'sid2', + rid: 'rid2' + } + ] + }; + const params = { user: { uid: 222, @@ -103,6 +119,7 @@ describe('Zeta Ssp Bid Adapter', function () { gdprApplies: 1, consentString: 'consentString' }, + schain: schain, uspConsent: 'someCCPAString', params: params, userIdAsEids: eids, @@ -367,4 +384,16 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.imp[0].bidfloor).to.eql(params.bidfloor); }); + + it('Timeout should exists and be a function', function () { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + expect(spec.onTimeout({ timeout: 1000 })).to.be.undefined; + }); + + it('Test schain provided', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.source.ext.schain).to.eql(schain); + }); }); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 2b7c2b88449..9150329ff60 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -14,6 +14,7 @@ import { import CONSTANTS from 'src/constants.json'; import { stubAuctionIndex } from '../helpers/indexStub.js'; import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '../../src/native.js'; +import {auctionManager} from '../../src/auctionManager.js'; const utils = require('src/utils'); const bid = { @@ -430,158 +431,180 @@ describe('native.js', function () { sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); }); - it('creates native asset message', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'assetRequest', - adId: '123', - assets: ['hb_native_body', 'hb_native_image', 'hb_native_linkurl'], - }; + describe('native postMessages', () => { + let adUnit; + beforeEach(() => { + adUnit = {}; + sinon.stub(auctionManager, 'index').get(() => ({ + getAdUnit: () => adUnit + })) + }); + + it('creates native asset message', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'assetRequest', + adId: '123', + assets: ['hb_native_body', 'hb_native_image', 'hb_native_linkurl'], + }; - const message = getAssetMessage(messageRequest, bid); + const message = getAssetMessage(messageRequest, bid); - expect(message.assets.length).to.equal(3); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, + expect(message.assets.length).to.equal(3); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); }); - }); - it('creates native all asset message', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; + it('creates native all asset message', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; - const message = getAllAssetsMessage(messageRequest, bid); + const message = getAllAssetsMessage(messageRequest, bid); - expect(message.assets.length).to.equal(10); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'icon', - value: bid.native.icon.url, - }); - expect(message.assets).to.deep.include({ - key: 'cta', - value: bid.native.cta, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, - }); - expect(message.assets).to.deep.include({ - key: 'baz', - value: bid.native.ext.baz, + expect(message.assets.length).to.equal(10); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url, + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz, + }); }); - }); - it('creates native all asset message with only defined fields', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; + it('creates native all asset message with only defined fields', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; - const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); + const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); - expect(message.assets.length).to.equal(4); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, + expect(message.assets.length).to.equal(4); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); }); - }); - it('creates native all asset message with complete format', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; + it('creates native all asset message with complete format', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; - const message = getAllAssetsMessage(messageRequest, completeNativeBid); + const message = getAllAssetsMessage(messageRequest, completeNativeBid); - expect(message.assets.length).to.equal(10); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'icon', - value: bid.native.icon.url, - }); - expect(message.assets).to.deep.include({ - key: 'cta', - value: bid.native.cta, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'privacyLink', - value: ortbBid.native.ortb.privacy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, - }); - expect(message.assets).to.deep.include({ - key: 'baz', - value: bid.native.ext.baz, + expect(message.assets.length).to.equal(10); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url, + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'privacyLink', + value: ortbBid.native.ortb.privacy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz, + }); }); - }); + + it('if necessary, adds ortb response when the request was in ortb', () => { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + adUnit = {mediaTypes: {native: {ortb: ortbRequest}}, nativeOrtbRequest: ortbRequest} + const message = getAllAssetsMessage(messageRequest, bid); + const expected = toOrtbNativeResponse(bid.native, ortbRequest) + expect(message.ortb).to.eql(expected); + }) + }) const SAMPLE_ORTB_REQUEST = toOrtbNativeRequest({ title: 'vtitle', @@ -1303,5 +1326,24 @@ describe('toOrtbNativeResponse', () => { text: 'vtitle' } }) + }); + + it('should accept objects as legacy assets', () => { + const legacyResponse = { + icon: { + url: 'image-url' + } + } + const request = toOrtbNativeRequest({ + icon: { + required: true + } + }); + const response = toOrtbNativeResponse(legacyResponse, request); + sinon.assert.match(response.assets[0], { + img: { + url: 'image-url' + } + }) }) }) diff --git a/test/spec/unit/utils/promise_spec.js b/test/spec/unit/utils/promise_spec.js index a931b8bc9c4..bd8b0390b2e 100644 --- a/test/spec/unit/utils/promise_spec.js +++ b/test/spec/unit/utils/promise_spec.js @@ -19,94 +19,6 @@ describe('GreedyPromise', () => { }) }); - describe('unhandled rejections', () => { - let unhandled, done, stop; - - function reset(expectUnhandled) { - let pending = expectUnhandled; - let resolver; - unhandled.reset(); - unhandled.callsFake(() => { - pending--; - if (pending === 0) { - resolver(); - } - }) - done = new Promise((resolve) => { - resolver = resolve; - stop = function () { - if (expectUnhandled === 0) { - resolve() - } else { - resolver = resolve; - } - } - }) - } - - before(() => { - unhandled = sinon.stub(); - window.addEventListener('unhandledrejection', unhandled); - }); - - after(() => { - window.removeEventListener('unhandledrejection', unhandled); - }); - - function getUnhandledErrors() { - return unhandled.args.map((args) => args[0].reason); - } - - Object.entries({ - 'simple reject': [1, (P) => { P.reject('err'); stop() }], - 'caught reject': [0, (P) => P.reject('err').catch((e) => { stop(); return e })], - 'unhandled reject with finally': [1, (P) => P.reject('err').finally(() => 'finally')], - 'error handler that throws': [1, (P) => P.reject('err').catch((e) => { stop(); throw e })], - 'rejection handled later in the chain': [0, (P) => P.reject('err').then((v) => v).catch((e) => { stop(); return e })], - 'multiple errors in one chain': [1, (P) => P.reject('err').then((v) => v).catch((e) => e).then((v) => { stop(); return P.reject(v) })], - 'multiple errors in one chain, all handled': [0, (P) => P.reject('err').then((v) => v).catch((e) => e).then((v) => P.reject(v)).catch((e) => { stop(); return e })], - 'separate chains for rejection and handling': [1, (P) => { - const p = P.reject('err'); - p.catch((e) => { stop(); return e; }) - p.then((v) => v); - }], - 'separate rejections merged without handling': [2, (P) => { - const p1 = P.reject('err1'); - const p2 = P.reject('err2'); - p1.then(() => p2).finally(stop); - }], - 'separate rejections merged for handling': [0, (P) => { - const p1 = P.reject('err1'); - const p2 = P.reject('err2'); - P.all([p1, p2]).catch((e) => { stop(); return e }); - }], - // eslint-disable-next-line no-throw-literal - 'exception in resolver': [1, (P) => new P(() => { stop(); throw 'err'; })], - // eslint-disable-next-line no-throw-literal - 'exception in resolver, caught': [0, (P) => new P(() => { throw 'err' }).catch((e) => { stop(); return e })], - 'errors from nested promises': [1, (P) => new P((resolve) => setTimeout(() => { resolve(P.reject('err')); stop(); }))], - 'errors from nested promises, caught': [0, (P) => new P((resolve) => setTimeout(() => resolve(P.reject('err')))).catch((e) => { stop(); return e })], - }).forEach(([t, [expectUnhandled, op]]) => { - describe(`on ${t}`, () => { - it('should match vanilla Promises', () => { - let vanillaUnhandled; - reset(expectUnhandled); - op(Promise); - return done.then(() => { - vanillaUnhandled = getUnhandledErrors(); - reset(expectUnhandled); - op(GreedyPromise); - return done; - }).then(() => { - const actualUnhandled = getUnhandledErrors(); - expect(actualUnhandled.length).to.eql(expectUnhandled); - expect(actualUnhandled).to.eql(vanillaUnhandled); - }) - }) - }) - }); - }); - describe('idioms', () => { let makePromise, pendingFailure, pendingSuccess; @@ -172,9 +84,11 @@ describe('GreedyPromise', () => { 'chained Promise.reject': (P) => P.reject(pendingSuccess), 'chained Promise.reject on failure': (P) => P.reject(pendingFailure), 'simple Promise.all': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two')]), + 'empty Promise.all': (P) => P.all([]), 'Promise.all with scalars': (P) => P.all([makePromise(P, 'one'), 'two']), 'Promise.all with errors': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two'), makePromise(P, 'err', true)]), 'Promise.allSettled': (P) => P.allSettled([makePromise(P, 'one', true), makePromise(P, 'two'), makePromise(P, 'three', true)]), + 'empty Promise.allSettled': (P) => P.allSettled([]), 'Promise.allSettled with scalars': (P) => P.allSettled([makePromise(P, 'value'), 'scalar']), 'Promise.race that succeeds': (P) => P.race([makePromise(P, 'error', true, 10), makePromise(P, 'success')]), 'Promise.race that fails': (P) => P.race([makePromise(P, 'success', false, 10), makePromise(P, 'error', true)]), diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index a13028c966a..c7c0b2eb329 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -72,6 +72,29 @@ describe('The video cache', function () { config.resetConfig(); }); + describe('cache.timeout', () => { + let getAjax, cb; + beforeEach(() => { + getAjax = sinon.stub().callsFake(() => sinon.stub()); + cb = sinon.stub(); + }); + + it('should be respected', () => { + config.setConfig({ + cache: { + timeout: 1 + } + }); + store([{ vastUrl: 'my-mock-url.com' }], cb, getAjax); + sinon.assert.calledWith(getAjax, 1); + }); + + it('should use default when not specified', () => { + store([], cb, getAjax); + sinon.assert.calledWith(getAjax, undefined); + }) + }); + it('should execute the callback with a successful result when store() is called', function () { const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; const callback = fakeServerCall( diff --git a/test/test_index.js b/test/test_index.js index 883f4d0590c..04d1412860b 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,3 +1,15 @@ + +[it, describe].forEach((ob) => { + ob.only = function () { + [ + 'describe.only and it.only are disabled unless you provide a single spec --file,', + 'because they can silently break the pipeline tests', + // eslint-disable-next-line no-console + ].forEach(l => console.error(l)) + throw new Error('do not use .only()') + } +}) + require('./test_deps.js'); var testsContext = require.context('.', true, /_spec$/);