From d090a3889aad5332ec27bf55d9e6dfa80dbeb8e1 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Tue, 17 Jan 2023 16:17:11 +0000 Subject: [PATCH 001/375] Increment version to 7.33.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85cea5127cc..9701ff8c91c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.32.0", + "version": "7.33.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 05808cd732d..58c50ee882e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.32.0", + "version": "7.33.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 5e4085d397dcd50d2ff29988845b2ad02d61d7d6 Mon Sep 17 00:00:00 2001 From: Dejan Grbavcic Date: Tue, 17 Jan 2023 19:12:45 +0100 Subject: [PATCH 002/375] 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 --- modules/targetVideoBidAdapter.js | 2 +- test/spec/modules/targetVideoBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index bc775e78647..1977686dd23 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -157,7 +157,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { const sizes = getSizes(bidRequest); const bid = { requestId: serverBid.uuid, - cpm: rtbBid.cpm * MARGIN, + cpm: rtbBid.cpm / MARGIN, creativeId: rtbBid.creative_id, dealId: rtbBid.deal_id, currency: 'USD', diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js index adbc982e509..8180183e6d7 100644 --- a/test/spec/modules/targetVideoBidAdapter_spec.js +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -58,7 +58,7 @@ describe('TargetVideo Bid Adapter', function() { 'uuid': '84ab500420319d', 'ads': [{ 'ad_type': 'video', - 'cpm': 0.500000, + 'cpm': 0.675000, 'notify_url': 'https://www.target-video.com/', 'rtb': { 'video': { @@ -87,7 +87,7 @@ describe('TargetVideo Bid Adapter', function() { const bid = bidResponse[0]; expect(bid).to.not.be.empty; - expect(bid.cpm).to.equal(0.675); + expect(bid.cpm).to.equal(0.5); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.ad).to.include('') From 36834f40c300fb862e84fd975b42656761708a9b Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 17 Jan 2023 11:58:27 -0800 Subject: [PATCH 003/375] PBjs Core (Price Floors) : Support inverseBidAdjustment function (#9395) * support inverseBidAdjustment function * pass in bidRequest object to adjustments * dont do fake bids bobby duh --- modules/priceFloors.js | 16 ++- test/spec/modules/priceFloors_spec.js | 155 ++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 5 deletions(-) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 8b5976149d0..32b3cbaa607 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -179,10 +179,10 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) { /** * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted */ -export function getBiddersCpmAdjustment(bidderName, inputCpm, bid = {}) { +export function getBiddersCpmAdjustment(bidderName, inputCpm, bid, bidRequest) { const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment'); if (adjustmentFunction) { - return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm})); + return parseFloat(adjustmentFunction(inputCpm, { ...bid, cpm: inputCpm }, bidRequest)); } return parseFloat(inputCpm); } @@ -249,8 +249,14 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: // if cpmAdjustment flag is true and we have a valid floor then run the adjustment on it if (floorData.enforcement.bidAdjustment && floorInfo.matchingFloor) { - let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor); - floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + // pub provided inverse function takes precedence, otherwise do old adjustment stuff + const inverseFunction = bidderSettings.get(bidRequest.bidder, 'inverseBidAdjustment'); + if (inverseFunction) { + floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest); + } else { + let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor, {}, bidRequest); + floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + } } if (floorInfo.matchingFloor) { @@ -731,7 +737,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a } // ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists - adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid); + adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid, matchingBidRequest); // add necessary data information for analytics adapters / floor providers would possibly need addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index b7d771814d0..f232631d73d 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1495,6 +1495,161 @@ describe('the price floors module', function () { }); }); + it('should use inverseFloorAdjustment function before bidder cpm adjustment', function () { + let functionUsed; + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Rubicon Adjustment'; + bidCpm *= 0.5; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Rubicon Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.5; + }, + }, + appnexus: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Appnexus Adjustment'; + bidCpm *= 0.75; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Appnexus Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.75; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // start with banner as only mediaType + bidRequest.mediaTypes = { banner: { sizes: [[300, 250]] } }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus', + }; + + // should be same as the adjusted calculated inverses above test (banner) + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // should use rubicon inverse + expect(functionUsed).to.equal('Rubicon Inverse'); + + // appnexus just using banner should be same + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + + // now since asking for 'video' only mediaType inverse function should include the .18 + bidRequest.mediaTypes = { video: { context: 'instream' } }; + expect(bidRequest.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 2.36 + }); + + expect(functionUsed).to.equal('Rubicon Inverse'); + + // now since asking for 'video' inverse function should include the .18 + appnexusBid.mediaTypes = { video: { context: 'instream' } }; + expect(appnexusBid.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 1.5734 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + }); + + it('should pass inverseFloorAdjustment the bidRequest object so it can be used', function () { + // Adjustment factors based on Bid Media Type + const mediaTypeFactors = { + banner: 0.5, + native: 0.7, + video: 0.9 + } + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidCpm * mediaTypeFactors[bidResponse.mediaType]; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + // For the inverse we add up each mediaType in the request and divide by number of Mt's to get the inverse number + let factor = Object.keys(bidRequest.mediaTypes).reduce((sum, mediaType) => sum += mediaTypeFactors[mediaType], 0); + factor = factor / Object.keys(bidRequest.mediaTypes).length; + return bidCpm / factor; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // banner only should be 2 + bidRequest.mediaTypes = { banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // native only should be 1.4286 + bidRequest.mediaTypes = { native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video only should be 1.1112 + bidRequest.mediaTypes = { video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.1112 + }); + + // video and banner should even out to 0.7 factor so 1.4286 + bidRequest.mediaTypes = { video: {}, banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video and native should even out to 0.8 factor so -- 1.25 + bidRequest.mediaTypes = { video: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.25 + }); + + // banner and native should even out to 0.6 factor so -- 1.6667 + bidRequest.mediaTypes = { banner: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.6667 + }); + + // all 3 banner video and native should even out to 0.7 factor so -- 1.4286 + bidRequest.mediaTypes = { banner: {}, native: {}, video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + }); + it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { getGlobal().bidderSettings = { rubicon: { From 31b662cef4819dabc196fbf631f440b792025e72 Mon Sep 17 00:00:00 2001 From: Tachfine Date: Tue, 17 Jan 2023 22:43:46 +0100 Subject: [PATCH 004/375] Criteo Bid Adapter : Bump Publisher Tag version (#9429) Update reference to version 133 (latest) --- modules/criteoBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 5567103db69..a056454564c 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -27,7 +27,7 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 132; +export const FAST_BID_VERSION_CURRENT = 133; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; From 2c12cd27481cc0c248f6afe52f704faa0cbeba13 Mon Sep 17 00:00:00 2001 From: ccorbo Date: Wed, 18 Jan 2023 15:09:08 -0500 Subject: [PATCH 005/375] 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 --- modules/ixBidAdapter.js | 31 +++++-- package-lock.json | 2 +- test/spec/modules/ixBidAdapter_spec.js | 123 +++++++++++++++++++++++-- 3 files changed, 143 insertions(+), 13 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 27e6d0aee02..bd598cf2d04 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -380,8 +380,8 @@ function parseBid(rawBid, currency, bidRequest) { bid.netRevenue = NET_REVENUE; bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; - - if (rawBid.mtype == MEDIA_TYPES.Video) { + // If mtype = video is passed and vastURl is not set, set vastxml + if (rawBid.mtype == MEDIA_TYPES.Video && ((rawBid.ext && !rawBid.ext.vasturl) || !rawBid.ext)) { bid.vastXml = rawBid.adm; } else if (rawBid.ext && rawBid.ext.vasturl) { bid.vastUrl = rawBid.ext.vasturl; @@ -425,7 +425,6 @@ function parseBid(rawBid, currency, bidRequest) { if (rawBid.adomain && rawBid.adomain.length > 0) { bid.meta.advertiserDomains = rawBid.adomain; } - return bid; } @@ -629,8 +628,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } const r = {}; - const tmax = config.getConfig('bidderTimeout'); - + const tmax = deepAccess(bidderRequest, 'timeout'); // Since bidderRequestId are the same for different bid request, just use the first one. r.id = validBidRequests[0].bidderRequestId.toString(); r.site = {}; @@ -720,6 +718,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (pageUrl) { r.site.page = pageUrl; } + + if (bidderRequest.gppConsent) { + deepSetValue(r, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(r, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } } if (config.getConfig('coppa')) { @@ -856,7 +859,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { currentRequestSize += currentImpressionSize; const fpd = deepAccess(bidderRequest, 'ortb2') || {}; - if (!isEmpty(fpd) && !isFpdAdded) { r.ext.ixdiag.fpd = true; @@ -876,6 +878,23 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } }); + if (fpd.device) { + const sua = {...fpd.device.sua}; + if (!isEmpty(sua)) { + deepSetValue(r, 'device.sua', sua); + } + } + + if (fpd.hasOwnProperty('regs') && !bidderRequest.gppConsent) { + if (fpd.regs.hasOwnProperty('gpp') && typeof fpd.regs.gpp == 'string') { + deepSetValue(r, 'regs.gpp', fpd.regs.gpp) + } + + if (fpd.regs.hasOwnProperty('gpp_sid') && Array.isArray(fpd.regs.gpp_sid)) { + deepSetValue(r, 'regs.gpp_sid', fpd.regs.gpp_sid) + } + } + const clonedRObject = deepClone(r); clonedRObject.site = mergeDeep({}, clonedRObject.site, site); diff --git a/package-lock.json b/package-lock.json index 9701ff8c91c..54d48311d36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.31.0-pre", + "version": "7.32.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 0de300e4a32..2327376d9ac 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -658,7 +658,7 @@ describe('IndexexchangeAdapter', function () { } }; - const DEFAULT_VIDEO_BID_RESPONSE_WITH_MTYPE_SET = { + const DEFAULT_VIDEO_BID_RESPONSE_WITH_XML_ADM = { cur: 'USD', id: '1aa2bb3cc4de', seatbid: [ @@ -675,7 +675,6 @@ describe('IndexexchangeAdapter', function () { mtype: 2, adm: ' Test In-Stream Video Date: Wed, 18 Jan 2023 21:39:08 +0100 Subject: [PATCH 006/375] PBjs Core (Promises): fix static method GreedyPromise.resolve not working with Angular + Zone.js (#9426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- src/utils/promise.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/utils/promise.js b/src/utils/promise.js index 97c64a96f8b..69e40791ab1 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -90,6 +90,14 @@ export class GreedyPromise extends (getGlobal().Promise || Promise) { res.#parent = this; return res; } + + static resolve(value) { + return new this(resolve => resolve(value)) + } + + static reject(error) { + return new this((resolve, reject) => reject(error)) + } } /** From 27884fc39175960ee54c4a5735ec9d4a594c68a1 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 19 Jan 2023 03:30:27 -0700 Subject: [PATCH 007/375] USP consent management: handle errors from CMPs that cannot deal with `registerDeletion` (#9434) --- modules/consentManagementUsp.js | 20 +++++++++---------- .../spec/modules/consentManagementUsp_spec.js | 17 ++++++++++++++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 0a99ec4a913..fcc13152aa9 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -100,6 +100,14 @@ function lookupUspConsent({onSuccess, onError}) { return onError('USP CMP not found.'); } + function registerDataDelHandler(invoker, arg2) { + try { + invoker('registerDeletion', arg2, adapterManager.callDataDeletionRequest); + } catch (e) { + logError('Error invoking CMP `registerDeletion`:', e); + } + } + // to collect the consent information from the user, we perform a call to USPAPI // to collect the user's consent choices represented as a string (via getUSPData) @@ -115,11 +123,7 @@ function lookupUspConsent({onSuccess, onError}) { USPAPI_VERSION, callbackHandler.consentDataCallback ); - uspapiFunction( - 'registerDeletion', - USPAPI_VERSION, - adapterManager.callDataDeletionRequest - ) + registerDataDelHandler(uspapiFunction, USPAPI_VERSION); } else { logInfo( 'Detected USP CMP is outside the current iframe where Prebid.js is located, calling it now...' @@ -129,11 +133,7 @@ function lookupUspConsent({onSuccess, onError}) { uspapiFrame, callbackHandler.consentDataCallback ); - callUspApiWhileInIframe( - 'registerDeletion', - uspapiFrame, - adapterManager.callDataDeletionRequest - ); + registerDataDelHandler(callUspApiWhileInIframe, uspapiFrame); } let listening = false; diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index f9c3cd5890e..e98486754ab 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -8,7 +8,7 @@ import { } from 'modules/consentManagementUsp.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; -import adapterManager, {uspDataHandler} from 'src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler} from 'src/adapterManager.js'; import 'src/prebid.js'; import {defer} from '../../../src/utils/promise.js'; @@ -508,7 +508,20 @@ describe('consentManagement', function () { sinon.assert.notCalled(adapterManager.callDataDeletionRequest); listener(); sinon.assert.calledOnce(adapterManager.callDataDeletionRequest); - }) + }); + + it('does not fail if CMP does not support registerDeletion', () => { + sandbox.stub(window, '__uspapi').callsFake((cmd, _, cb) => { + if (cmd === 'registerDeletion') { + throw new Error('CMP not compliant'); + } else if (cmd === 'getUSPData') { + // eslint-disable-next-line standard/no-callback-literal + cb({uspString: 'string'}, true); + } + }); + setConsentConfig(goodConfig); + expect(uspDataHandler.getConsentData()).to.eql('string'); + }); }); }); }); From 352fcb3d4a27b6f1948b0616db945f0ed976c8b4 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Thu, 19 Jan 2023 15:08:48 +0100 Subject: [PATCH 008/375] nexx360 Bid Adapter: aliases list update (#9439) * ssp added to meta.demandSource * aliases update --- modules/nexx360BidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 96738f586c1..b04bb47543f 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -21,7 +21,8 @@ export const spec = { gvlid: GVLID, aliases: [ { code: 'revenuemaker' }, - { code: 'firstid-ssp' }, + { code: 'first-id', gvlid: 1178 }, + { code: 'adwebone' }, ], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, From 452fbabe9144d869f1b898e35f3328b333f8f007 Mon Sep 17 00:00:00 2001 From: Wiem Zine El Abidine Date: Thu, 19 Jan 2023 15:26:21 +0100 Subject: [PATCH 009/375] Update live-connect-js version (#9438) * update live-connect-js * fix * fix package-lock.json --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 54d48311d36..fcdd783bb75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.32.0-pre", + "version": "7.33.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.1" + "live-connect-js": "3.0.5" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16229,9 +16229,9 @@ "dev": true }, "node_modules/live-connect-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.1.tgz", - "integrity": "sha512-rwB0IQfuKPJM+I96nLyq8Utr3LkQ7Z/iuq/xKlWDckQRJLYyWkk7F7yaavf/VsjazzLK2dpJeXGijoDkK4Vz8g==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.5.tgz", + "integrity": "sha512-KqxE+V/050nK2tUx8PnAtQBOK4E29WVasQTrLkkCwSebmV5uqMu+VMcwhJSbnyh/g+GhDAE/LL9RB6X9vcmLrg==", "dependencies": { "tiny-hashes": "1.0.1" }, @@ -37865,9 +37865,9 @@ "dev": true }, "live-connect-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.1.tgz", - "integrity": "sha512-rwB0IQfuKPJM+I96nLyq8Utr3LkQ7Z/iuq/xKlWDckQRJLYyWkk7F7yaavf/VsjazzLK2dpJeXGijoDkK4Vz8g==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.5.tgz", + "integrity": "sha512-KqxE+V/050nK2tUx8PnAtQBOK4E29WVasQTrLkkCwSebmV5uqMu+VMcwhJSbnyh/g+GhDAE/LL9RB6X9vcmLrg==", "requires": { "tiny-hashes": "1.0.1" } diff --git a/package.json b/package.json index 58c50ee882e..6101967c552 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.1" + "live-connect-js": "3.0.5" }, "optionalDependencies": { "fsevents": "^2.3.2" From 1e318fde36659a6b072bfbc46463efb038f75422 Mon Sep 17 00:00:00 2001 From: Yohan Boutin Date: Thu, 19 Jan 2023 18:44:03 +0100 Subject: [PATCH 010/375] enable video/banner mediatypes for inImage/inBanner/inArticle/inScreen (#9417) --- modules/seedtagBidAdapter.js | 26 +++++-- test/spec/modules/seedtagBidAdapter_spec.js | 75 ++++++++++++++++----- 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index d5abb89437b..f54245a41ab 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -60,6 +60,10 @@ function hasVideoMediaType(bid) { return !!bid.mediaTypes && !!bid.mediaTypes.video; } +function hasBannerMediaType(bid) { + return !!bid.mediaTypes && !!bid.mediaTypes.banner; +} + function hasMandatoryDisplayParams(bid) { const p = bid.params; return ( @@ -72,17 +76,27 @@ function hasMandatoryDisplayParams(bid) { function hasMandatoryVideoParams(bid) { const videoParams = getVideoParams(bid); - return ( + let isValid = !!bid.params.publisherId && !!bid.params.adUnitId && hasVideoMediaType(bid) && !!videoParams.playerSize && isArray(videoParams.playerSize) && - videoParams.playerSize.length > 0 && - // only instream is supported for video - videoParams.context === 'instream' && - bid.params.placement === 'inStream' - ); + videoParams.playerSize.length > 0; + + switch (bid.params.placement) { + // instream accept only video format + case 'inStream': + return isValid && videoParams.context === 'instream'; + // outstream accept banner/native/video format + default: + return ( + isValid && + videoParams.context === 'outstream' && + hasBannerMediaType(bid) && + hasMandatoryDisplayParams(bid) + ); + } } function buildBidRequest(validBidRequest) { diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index cfdd3365269..3627296975b 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -33,30 +33,60 @@ function createInStreamSlotConfig(mediaType) { }); } +const createBannerSlotConfig = (placement, mediatypes) => { + return getSlotConfigs(mediatypes || { banner: {} }, { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement, + }); +}; + describe('Seedtag Adapter', function () { describe('isBidRequestValid method', function () { describe('returns true', function () { describe('when banner slot config has all mandatory params', () => { - describe('and placement has the correct value', function () { - const createBannerSlotConfig = (placement) => { - return getSlotConfigs( - { banner: {} }, - { - publisherId: PUBLISHER_ID, - adUnitId: ADUNIT_ID, - placement, - } + const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; + placements.forEach((placement) => { + it(placement + 'should be valid', function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement) ); - }; - const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; - placements.forEach((placement) => { - it('should be ' + placement, function () { + expect(isBidRequestValid).to.equal(true); + }); + + it( + placement + + ' should be valid when has display and video mediatypes, and video context is outstream', + function () { const isBidRequestValid = spec.isBidRequestValid( - createBannerSlotConfig(placement) + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'outstream', + playerSize: [[600, 200]], + }, + }) ); expect(isBidRequestValid).to.equal(true); - }); - }); + } + ); + + it( + placement + + " shouldn't be valid when has display and video mediatypes, and video context is instream", + function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + }) + ); + expect(isBidRequestValid).to.equal(false); + } + ); }); }); describe('when video slot has all mandatory params', function () { @@ -70,7 +100,18 @@ describe('Seedtag Adapter', function () { const isBidRequestValid = spec.isBidRequestValid(slotConfig); expect(isBidRequestValid).to.equal(true); }); - it('should return true, when video context is instream, but placement is not inStream', function () { + it('should return true, when video context is instream and mediatype is video and banner', function () { + const slotConfig = createInStreamSlotConfig({ + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + banner: {}, + }); + const isBidRequestValid = spec.isBidRequestValid(slotConfig); + expect(isBidRequestValid).to.equal(true); + }); + it('should return false, when video context is instream, but placement is not inStream', function () { const slotConfig = getSlotConfigs( { video: { From 26e53fe98fafa9f058605e1cb729edde310eb503 Mon Sep 17 00:00:00 2001 From: pkwisniowski-id5 <121963897+pkwisniowski-id5@users.noreply.github.com> Date: Fri, 20 Jan 2023 11:15:21 +0100 Subject: [PATCH 011/375] The payload extended with document.referer and canonicalUrl (#9416) --- modules/id5IdSystem.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index c2c4a97c62e..a0745df37c8 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -247,7 +247,9 @@ class IdFetchFlow { 'gdpr': hasGdpr, 'nbPage': nbPage, 'o': 'pbjs', - 'rf': referer.topmostLocation, + 'tml': referer.topmostLocation, + 'ref': referer.ref, + 'cu': referer.canonicalUrl, 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$', From 4096bec171ba5736988cdeb4fdf77aef79f98204 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 20 Jan 2023 16:49:38 +0000 Subject: [PATCH 012/375] Prebid 7.33.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fcdd783bb75..ed18633033b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.33.0-pre", + "version": "7.33.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6101967c552..07d848a5592 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.33.0-pre", + "version": "7.33.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 165233b99cd87326eb0b3b96f796aa6767dc6d7c Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 20 Jan 2023 16:49:38 +0000 Subject: [PATCH 013/375] Increment version to 7.34.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index ed18633033b..3d5072df524 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.33.0", + "version": "7.34.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 07d848a5592..4b52267af26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.33.0", + "version": "7.34.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 678d13653f381c6c56395f12150c12d3bda1da4c Mon Sep 17 00:00:00 2001 From: Daria Boyko Date: Mon, 23 Jan 2023 10:54:57 +0200 Subject: [PATCH 014/375] 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 --- modules/admixerBidAdapter.js | 30 ++- test/spec/modules/admixerBidAdapter_spec.js | 200 ++++++++++++++------ 2 files changed, 166 insertions(+), 64 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 90031ed7f5d..8dbcfdafd32 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,12 +1,14 @@ -import { logError } from '../src/utils.js'; +import {logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'admixer'; -const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads', 'smn']; +const BIDDER_CODE_ADX = 'admixeradx'; +const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads', 'admixeradx']; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; +const ADX_ENDPOINT_URL = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; export const spec = { code: BIDDER_CODE, aliases: ALIASES, @@ -57,6 +59,10 @@ export const spec = { if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + let bidFloor = getBidFloor(bidderRequest); + if (bidFloor) { + payload.bidFloor = bidFloor; + } } validRequest.forEach((bid) => { let imp = {}; @@ -65,7 +71,11 @@ export const spec = { }); return { method: 'POST', - url: endpointUrl || ENDPOINT_URL, + url: + endpointUrl || + (bidderRequest.bidderCode === BIDDER_CODE_ADX + ? ADX_ENDPOINT_URL + : ENDPOINT_URL), data: payload, }; }, @@ -96,4 +106,16 @@ export const spec = { return pixels; } }; +function getBidFloor(bid) { + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0; + } +} registerBidder(spec); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 228b87ae4d5..26caaf5b70f 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -4,8 +4,10 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from '../../../src/config.js'; const BIDDER_CODE = 'admixer'; +const BIDDER_CODE_ADX = 'admixeradx'; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; const ENDPOINT_URL_CUSTOM = 'https://custom.admixer.net/prebid.aspx'; +const ENDPOINT_URL_ADX = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; describe('AdmixerAdapter', function () { @@ -16,18 +18,22 @@ describe('AdmixerAdapter', function () { expect(adapter.callBids).to.be.exist.and.to.be.a('function'); }); }); + // inv-nets.admixer.net/adxprebid.1.2.aspx describe('isBidRequestValid', function () { let bid = { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', }; it('should return true when required params found', function () { @@ -38,7 +44,7 @@ describe('AdmixerAdapter', function () { let bid = Object.assign({}, bid); delete bid.params; bid.params = { - 'placementId': 0 + placementId: 0, }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -47,22 +53,25 @@ describe('AdmixerAdapter', function () { describe('buildRequests', function () { let validRequest = [ { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, ]; let bidderRequest = { bidderCode: BIDDER_CODE, refererInfo: { - page: 'https://example.com' - } + page: 'https://example.com', + }, }; it('should add referrer and imp to be equal bidRequest', function () { @@ -81,49 +90,122 @@ describe('AdmixerAdapter', function () { it('sends bid request to CUSTOM_ENDPOINT via GET', function () { config.setBidderConfig({ bidders: [BIDDER_CODE], // one or more bidders - config: {[BIDDER_CODE]: {endpoint_url: ENDPOINT_URL_CUSTOM}} + config: { [BIDDER_CODE]: { endpoint_url: ENDPOINT_URL_CUSTOM } }, }); - const request = config.runWithBidder(BIDDER_CODE, () => spec.buildRequests(validRequest, bidderRequest)); + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests(validRequest, bidderRequest) + ); expect(request.url).to.equal(ENDPOINT_URL_CUSTOM); expect(request.method).to.equal('POST'); }); }); + describe('buildRequestsAdmixerADX', function () { + let validRequest = [ + { + bidder: BIDDER_CODE_ADX, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + let bidderRequest = { + bidderCode: BIDDER_CODE_ADX, + refererInfo: { + page: 'https://example.com', + }, + }; + + it('sends bid request to ADX ENDPOINT', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(ENDPOINT_URL_ADX); + expect(request.method).to.equal('POST'); + }); + }); + + describe('checkFloorGetting', function () { + let validRequest = [ + { + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + let bidderRequest = { + bidderCode: BIDDER_CODE, + refererInfo: { + page: 'https://example.com', + }, + }; + it('gets floor', function () { + bidderRequest.getFloor = () => { + return { floor: 0.6 }; + }; + const request = spec.buildRequests(validRequest, bidderRequest); + const payload = request.data; + expect(payload.bidFloor).to.deep.equal(0.6); + }); + }); + describe('interpretResponse', function () { let response = { body: { - ads: [{ - 'currency': 'USD', - 'cpm': 6.210000, - 'ad': '
ad
', - 'width': 300, - 'height': 600, - 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', - 'ttl': 360, - 'netRevenue': false, - 'requestId': '5e4e763b6bc60b', - 'dealId': 'asd123', - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - }] - } + ads: [ + { + currency: 'USD', + cpm: 6.21, + ad: '
ad
', + width: 300, + height: 600, + creativeId: 'ccca3e5e-0c54-4761-9667-771322fbdffc', + ttl: 360, + netRevenue: false, + requestId: '5e4e763b6bc60b', + dealId: 'asd123', + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, + ], + }, }; it('should get correct bid response', function () { const ads = response.body.ads; let expectedResponse = [ { - 'requestId': ads[0].requestId, - 'cpm': ads[0].cpm, - 'creativeId': ads[0].creativeId, - 'width': ads[0].width, - 'height': ads[0].height, - 'ad': ads[0].ad, - 'currency': ads[0].currency, - 'netRevenue': ads[0].netRevenue, - 'ttl': ads[0].ttl, - 'dealId': ads[0].dealId, - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - } + requestId: ads[0].requestId, + cpm: ads[0].cpm, + creativeId: ads[0].creativeId, + width: ads[0].width, + height: ads[0].height, + ad: ads[0].ad, + currency: ads[0].currency, + netRevenue: ads[0].netRevenue, + ttl: ads[0].ttl, + dealId: ads[0].dealId, + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, ]; let result = spec.interpretResponse(response); @@ -141,18 +223,16 @@ describe('AdmixerAdapter', function () { describe('getUserSyncs', function () { let imgUrl = 'https://example.com/img1'; let frmUrl = 'https://example.com/frm2'; - let responses = [{ - body: { - cm: { - pixels: [ - imgUrl - ], - iframes: [ - frmUrl - ], - } - } - }]; + let responses = [ + { + body: { + cm: { + pixels: [imgUrl], + iframes: [frmUrl], + }, + }, + }, + ]; it('Returns valid values', function () { let userSyncAll = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, responses); From 35be6c81a4a60cfbe442893bbe020e00f88d7546 Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Mon, 23 Jan 2023 16:09:39 +0300 Subject: [PATCH 015/375] 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 --- modules/admaticBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 148424c0a98..808c788fcb9 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,5 +1,6 @@ import { getValue, logError, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; let SYNC_URL = ''; const BIDDER_CODE = 'admatic'; @@ -35,7 +36,7 @@ export const spec = { const bids = validBidRequests.map(buildRequestObject); const networkId = getValue(validBidRequests[0].params, 'networkId'); const host = getValue(validBidRequests[0].params, 'host'); - const currency = getValue(validBidRequests[0].params, 'currency') || 'TRY'; + const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; const bidderName = validBidRequests[0].bidder; const payload = { From 2741fe9b2bacebedacd7ed7edf7db304fc5dafd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Stropa?= <51368253+jaropenx@users.noreply.github.com> Date: Mon, 23 Jan 2023 14:43:15 +0100 Subject: [PATCH 016/375] added support for user agent client hints (#9445) --- modules/openxBidAdapter.js | 5 +++ test/spec/modules/openxBidAdapter_spec.js | 41 +++++++++++++++++++ test/spec/modules/openxOrtbBidAdapter_spec.js | 41 +++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 14525cd0cfc..b6f4523bb50 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -268,6 +268,11 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { nocache: new Date().getTime() }; + const userAgentClientHints = deepAccess(bidderRequest, 'ortb2.device.sua'); + if (userAgentClientHints) { + defaultParams.sua = JSON.stringify(userAgentClientHints); + } + const userDataSegments = buildFpdQueryParams('user.data', bidderRequest.ortb2); if (userDataSegments.length > 0) { defaultParams.sm = userDataSegments; diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index cc1c2d1e607..8fe220aa202 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1729,6 +1729,47 @@ describe('OpenxAdapter', function () { }); }); }); + describe('with user agent client hints', function () { + it('should add json query param sua with BidRequest.device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests([bidRequest], bidderRequestWithUserAgentClientHints); + expect(request[0].data.sua).to.exist; + const payload = JSON.parse(request[0].data.sua); + expect(payload).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests([bidRequest], bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.sua).to.not.exist; + }); + }); }); }) diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index be6427f607b..4295cb36df2 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -579,6 +579,47 @@ describe('OpenxRtbAdapter', function () { }); }); }); + + describe('with user agent client hints', function () { + it('should add device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); + expect(request[0].data.device.sua).to.exist; + expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.device.sua).to.not.exist; + }); + }); }); context('when there is a consent management framework', function () { From 9808b3f352764268a46f50b43416c999a93c0f59 Mon Sep 17 00:00:00 2001 From: Mikhail Ivanchenko Date: Mon, 23 Jan 2023 21:59:43 +0300 Subject: [PATCH 017/375] nextMillenniumBidAdapter: fix replaceGetUserMacro function (#9442) * add video support * fix replaceUserMacro func * Add tests Co-authored-by: Mikhail Ivanchenko --- modules/nextMillenniumBidAdapter.js | 28 +++++++++++++------ .../modules/nextMillenniumBidAdapter_spec.js | 16 +++++++++-- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index b5d0e15d078..655f5fc3a35 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -271,15 +271,25 @@ export const spec = { }; function replaceUsersyncMacros(url, gdprConsent, uspConsent) { - const { consentString, gdprApplies } = gdprConsent; - - return url.replace( - '{{.GDPR}}', Number(gdprApplies) - ).replace( - '{{.GDPRConsent}}', consentString - ).replace( - '{{.USPrivacy}}', uspConsent - ); + const { consentString, gdprApplies } = gdprConsent || {}; + + if (gdprApplies) { + const gdpr = Number(gdprApplies); + url = url.replace('{{.GDPR}}', gdpr); + + if (gdpr == 1 && consentString && consentString.length > 0) { + url = url.replace('{{.GDPRConsent}}', consentString); + } + } else { + url = url.replace('{{.GDPR}}', 0); + url = url.replace('{{.GDPRConsent}}', ''); + } + + if (uspConsent) { + url = url.replace('{{.USPrivacy}}', uspConsent); + } + + return url; }; function getAdEl(bid) { diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 90f0d8ae15c..571bb5fc584 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -49,7 +49,7 @@ describe('nextMillenniumBidAdapterTests', function() { cur: 'USD', ext: { sync: { - image: ['urlA'], + image: ['urlA?gdpr={{.GDPR}}'], iframe: ['urlB'], } } @@ -132,7 +132,7 @@ describe('nextMillenniumBidAdapterTests', function() { let userSync = spec.getUserSyncs(syncOptions, [serverResponse], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.equal('image'); - expect(userSync[0].url).to.equal('urlA'); + expect(userSync[0].url).to.equal('urlA?gdpr=1'); syncOptions.iframeEnabled = true; syncOptions.pixelEnabled = false; @@ -142,6 +142,18 @@ describe('nextMillenniumBidAdapterTests', function() { expect(userSync[0].url).to.equal('urlB'); }); + it('Test getUserSyncs function if GDPR is undefined', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + } + + let userSync = spec.getUserSyncs(syncOptions, [serverResponse], undefined, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.equal('image'); + expect(userSync[0].url).to.equal('urlA?gdpr=0'); + }); + it('Request params check without GDPR Consent', function () { delete bidRequestData[0].gdprConsent const request = spec.buildRequests(bidRequestData, bidRequestData[0]); From eafc9887bddc467596496e2d2c3d9f1259d912a8 Mon Sep 17 00:00:00 2001 From: Andy Rusiecki Date: Mon, 23 Jan 2023 14:05:38 -0500 Subject: [PATCH 018/375] kargo - adding support for vast url in bid response (#9447) --- modules/kargoBidAdapter.js | 6 ++++- test/spec/modules/kargoBidAdapter_spec.js | 32 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 2c33c3f61d1..6a1c1cf94b0 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -116,7 +116,11 @@ export const spec = { }; if (meta.mediaType == VIDEO) { - bidResponse.vastXml = adUnit.adm; + if (adUnit.admUrl) { + bidResponse.vastUrl = adUnit.admUrl; + } else { + bidResponse.vastXml = adUnit.adm; + } } bidResponses.push(bidResponse); diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 565b83704fa..525c00d655c 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -556,6 +556,17 @@ describe('kargo adapter tests', function () { mediaType: 'video', metadata: {}, currency: 'EUR' + }, + 6: { + id: 'bar', + cpm: 2.5, + adm: '', + admUrl: 'https://foobar.com/vast_adm', + width: 300, + height: 250, + mediaType: 'video', + metadata: {}, + currency: 'EUR' } }}, { currency: 'USD', @@ -584,6 +595,11 @@ describe('kargo adapter tests', function () { params: { placementId: 'bar' } + }, { + bidId: 6, + params: { + placementId: 'bar' + } }] }); var expectation = [{ @@ -664,6 +680,22 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'video' } + }, { + requestId: '6', + cpm: 2.5, + width: 300, + height: 250, + ad: '', + vastUrl: 'https://foobar.com/vast_adm', + ttl: 300, + creativeId: 'bar', + dealId: undefined, + netRevenue: true, + currency: 'EUR', + mediaType: 'video', + meta: { + mediaType: 'video' + } }]; expect(resp).to.deep.equal(expectation); }); From 6d9887a672b28a4d7018a491839b06fbdd90f0b1 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 23 Jan 2023 12:36:13 -0700 Subject: [PATCH 019/375] openxOrtbBidAdapter: fix device.sua test (#9452) --- test/spec/modules/openxOrtbBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index 4295cb36df2..3bb1958c5fe 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -617,7 +617,7 @@ describe('OpenxRtbAdapter', function () { expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); - expect(request[0].data.device.sua).to.not.exist; + expect(request[0].data.device?.sua).to.not.exist; }); }); }); From b77951dd732a60863fb7c45ce05eb301836007f1 Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 23 Jan 2023 21:01:10 +0100 Subject: [PATCH 020/375] Criteo Bid Adapter : Bump Publisher Tag version (#9450) Co-authored-by: v.raybaud --- modules/criteoBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index a056454564c..fb42066a008 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -27,7 +27,7 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 133; +export const FAST_BID_VERSION_CURRENT = 134; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; From 11b4c97f0867e383575e7eab275e2941b7d9791a Mon Sep 17 00:00:00 2001 From: samous Date: Tue, 24 Jan 2023 05:01:05 +0100 Subject: [PATCH 021/375] BLIINK Bid Adapter: fix ttl (#9443) * fix(bliink): bid ttl * fix(bliink): ttl unit tests Co-authored-by: Samous --- modules/bliinkBidAdapter.js | 2 +- test/spec/modules/bliinkBidAdapter_spec.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 127b534c989..ee814807331 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -125,7 +125,7 @@ export const buildBid = (bidResponse) => { requestId: deepAccess(bidResponse, 'extras.transaction_id'), width: deepAccess(bidResponse, `creative.${bid.mediaType}.width`) || 1, height: deepAccess(bidResponse, `creative.${bid.mediaType}.height`) || 1, - ttl: 3600, + ttl: 300, netRevenue: true, }); }; diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index b8994b86847..8e96bd76940 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -130,7 +130,7 @@ const getConfigCreative = () => { creativeId: '34567erty', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -145,7 +145,7 @@ const getConfigCreativeVideo = (isNoVast) => { requestId: '6a204ce130280d', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -395,7 +395,7 @@ const testsInterpretResponse = [ mediaType: 'video', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(vastXml.replace(/\\"/g, '"')) }] @@ -421,7 +421,7 @@ const testsInterpretResponse = [ mediaType: 'banner', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, width: 300 }] }, @@ -505,7 +505,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -535,7 +535,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -552,7 +552,7 @@ const testsBuildBid = [ height: 250, creativeId: getConfigCreative().creativeId, ad: getConfigBannerBid().creative.banner.adm, - ttl: 3600, + ttl: 300, netRevenue: true, } } From 57f6109c79fc6cbdffe6dfaf66ee1d96331ff49e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 13:23:05 -0500 Subject: [PATCH 022/375] 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> --- package-lock.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d5072df524..6298a19479c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.33.0-pre", + "version": "7.34.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -2797,9 +2797,9 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true, "funding": [ { @@ -8453,9 +8453,9 @@ } }, "node_modules/devtools/node_modules/ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true, "funding": [ { @@ -22962,9 +22962,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", - "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", "dev": true, "funding": [ { @@ -27305,9 +27305,9 @@ } }, "ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true }, "uuid": { @@ -31718,9 +31718,9 @@ } }, "ua-parser-js": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", - "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true }, "uuid": { @@ -43058,9 +43058,9 @@ } }, "ua-parser-js": { - "version": "0.7.32", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", - "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", "dev": true }, "uglify-js": { From a009ced31f55c5402bf92b773561468abb8508a6 Mon Sep 17 00:00:00 2001 From: Patrick Loughrey Date: Tue, 24 Jan 2023 13:26:22 -0500 Subject: [PATCH 023/375] 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> --- modules/tripleliftBidAdapter.js | 4 ++++ test/spec/modules/tripleliftBidAdapter_spec.js | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 7f6ec90c7b9..3ce44d067bf 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -154,6 +154,10 @@ function _buildPostBody(bidRequests, bidderRequest) { if (!isEmpty(ext)) { data.ext = ext; } + + if (bidderRequest?.ortb2?.regs?.gpp) { + data.regs = Object.assign({}, bidderRequest.ortb2.regs); + } return data; } diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 5cfa64184f9..0447cb4d5d6 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -1168,6 +1168,19 @@ describe('triplelift adapter', function () { } }) }); + it('should add gpp consent data to bid request object if gpp data exists', function() { + bidderRequest.ortb2 = { + regs: { + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gpp_sid': [7] + } + } + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.regs).to.deep.equal({ + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gpp_sid': [7] + }) + }); }); describe('interpretResponse', function () { From 97c149daf215f5f0b178c9fb88f29a3fd9b5b32c Mon Sep 17 00:00:00 2001 From: Mike Marcus Date: Tue, 24 Jan 2023 17:05:59 -0500 Subject: [PATCH 024/375] 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 --- modules/lotamePanoramaIdSystem.js | 10 ++++++- .../modules/lotamePanoramaIdSystem_spec.js | 26 ++++++++++++------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 832d98e4f83..883c931824b 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -29,6 +29,7 @@ const DAY_MS = 60 * 60 * 24 * 1000; const MISSING_CORE_CONSENT = 111; const GVLID = 95; const ID_HOST = 'id.crwdcntrl.net'; +const ID_HOST_COOKIELESS = 'c.ltmsphrcl.net'; export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); let cookieDomain; @@ -252,6 +253,13 @@ export const lotamePanoramaIdSubmodule = { usPrivacy = getFromStorage('us_privacy'); } + const getRequestHost = function() { + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + return ID_HOST_COOKIELESS; + } + return ID_HOST; + } + const resolveIdFunction = function (callback) { let queryParams = {}; if (storedUserId) { @@ -288,7 +296,7 @@ export const lotamePanoramaIdSubmodule = { const url = buildUrl({ protocol: 'https', - host: ID_HOST, + host: getRequestHost(), pathname: '/id', search: isEmpty(queryParams) ? undefined : queryParams, }); diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index 5dc055ac080..ea538db08e1 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -18,6 +18,7 @@ describe('LotameId', function() { let removeFromLocalStorageStub; let timeStampStub; let uspConsentDataStub; + let requestHost; const nowTimestamp = new Date().getTime(); @@ -33,6 +34,11 @@ describe('LotameId', function() { ); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + requestHost = 'https://c.ltmsphrcl.net/id'; + } else { + requestHost = 'https://id.crwdcntrl.net/id'; + } }); afterEach(function () { @@ -69,7 +75,7 @@ describe('LotameId', function() { }); it('should call the remote server when getId is called', function () { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + expect(request.url).to.be.eq(`${requestHost}`); expect(callBackSpy.calledOnce).to.be.true; }); @@ -439,7 +445,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -471,7 +477,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -503,7 +509,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -531,7 +537,7 @@ describe('LotameId', function() { it('should not include the gdpr consent string on the url', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true' + `${requestHost}?gdpr_applies=true` ); }); }); @@ -560,7 +566,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -589,7 +595,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -613,7 +619,7 @@ describe('LotameId', function() { }); it('should pass the gdpr consent string back', function() { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + expect(request.url).to.be.eq(`${requestHost}`); }); }); @@ -835,7 +841,7 @@ describe('LotameId', function() { it('should pass the usp consent string and client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&us_privacy=1NNN&c=1234' + `${requestHost}?gdpr_applies=false&us_privacy=1NNN&c=1234` ); }); @@ -923,7 +929,7 @@ describe('LotameId', function() { it('should pass client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&c=1234' + `${requestHost}?gdpr_applies=false&c=1234` ); }); From a10a0fa431d3ce1ad160169457cad82818bcadf9 Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Wed, 25 Jan 2023 16:08:35 +0100 Subject: [PATCH 025/375] Adnuntius Bid Adapter: Bug fix for multiple mime types. (#9458) --- modules/adnuntiusBidAdapter.js | 74 +++--- test/spec/modules/adnuntiusBidAdapter_spec.js | 240 +++++++++--------- 2 files changed, 156 insertions(+), 158 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 5ad5436e732..ea3b723b316 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; -import { isStr, deepAccess, logInfo } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { isStr, deepAccess } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -8,7 +8,7 @@ const BIDDER_CODE = 'adnuntius'; const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' -const DEFAULT_NATIVE = 'native' +// const DEFAULT_NATIVE = 'native' const checkSegment = function (segment) { if (isStr(segment)) return segment; @@ -28,31 +28,31 @@ const getSegmentsFromOrtb = function (ortb2) { return segments } -function createNative(ad) { - const native = {}; - const assets = ad.assets - native.title = ad.text.title.content; - native.image = { - url: assets.image.cdnId, - height: assets.image.height, - width: assets.image.width, - }; - if (assets.icon) { - native.icon = { - url: assets.icon.cdnId, - height: assets.icon.height, - width: assets.icon.width, - }; - } - - native.sponsoredBy = ad.text.sponsoredBy?.content || ''; - native.body = ad.text.body?.content || ''; - native.cta = ad.text.cta?.content || ''; - native.clickUrl = ad.destinationUrls.destination || ''; - native.impressionTrackers = ad.impressionTrackingUrls || [ad.renderedPixel]; - - return native; -} +// function createNative(ad) { +// const native = {}; +// const assets = ad.assets +// native.title = ad.text.title.content; +// native.image = { +// url: assets.image.cdnId, +// height: assets.image.height, +// width: assets.image.width, +// }; +// if (assets.icon) { +// native.icon = { +// url: assets.icon.cdnId, +// height: assets.icon.height, +// width: assets.icon.width, +// }; +// } + +// native.sponsoredBy = ad.text.sponsoredBy?.content || ''; +// native.body = ad.text.body?.content || ''; +// native.cta = ad.text.cta?.content || ''; +// native.clickUrl = ad.destinationUrls.destination || ''; +// native.impressionTrackers = ad.impressionTrackingUrls || [ad.renderedPixel]; + +// return native; +// } const handleMeta = function () { const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }) @@ -73,7 +73,7 @@ const getUsi = function (meta, ortb2, bidderRequest) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function (bid) { return !!(bid.bidId || (bid.params.member && bid.params.invCode)); }, @@ -109,9 +109,9 @@ export const spec = { network += '_video' } - if (bid.mediaTypes && bid.mediaTypes.native) { - network += '_native' - } + // if (bid.mediaTypes && bid.mediaTypes.native) { + // network += '_native' + // } bidRequests[network] = bidRequests[network] || []; bidRequests[network].push(bid); @@ -130,7 +130,7 @@ export const spec = { const network = networkKeys[j]; const networkRequest = [...request] if (network.indexOf('_video') > -1) { networkRequest.push('tt=' + DEFAULT_VAST_VERSION) } - if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } + // if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } requests.push({ method: 'POST', url: ENDPOINT_URL + '?' + networkRequest.join('&'), @@ -170,15 +170,13 @@ export const spec = { if (adUnit.vastXml) { adResponse[adUnit.targetId].vastXml = adUnit.vastXml adResponse[adUnit.targetId].mediaType = VIDEO - } else if (ad.assets && ad.assets.image && ad.text && ad.text.title && ad.text.body && ad.destinationUrls && ad.destinationUrls.destination) { - adResponse[adUnit.targetId].native = createNative(ad); - adResponse[adUnit.targetId].mediaType = NATIVE; + // } else if (ad.assets && ad.assets.image && ad.text && ad.text.title && ad.text.body && ad.destinationUrls && ad.destinationUrls.destination) { + // adResponse[adUnit.targetId].native = createNative(ad); + // adResponse[adUnit.targetId].mediaType = NATIVE; } else { adResponse[adUnit.targetId].ad = adUnit.html } - logInfo('BID', adResponse) - return adResponse } else return response }, {}); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index ee585862800..b787a52d6f2 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -71,29 +71,29 @@ describe('adnuntiusBidAdapter', function () { } ] - const nativeBidderRequest = [ - { - bidId: '123', - bidder: 'adnuntius', - params: { - auId: '8b6bc', - network: 'adnuntius', - }, - mediaTypes: { - native: { - title: { - required: true - }, - image: { - required: true - }, - body: { - required: true - } - } - }, - } - ] + // const nativeBidderRequest = [ + // { + // bidId: '123', + // bidder: 'adnuntius', + // params: { + // auId: '8b6bc', + // network: 'adnuntius', + // }, + // mediaTypes: { + // native: { + // title: { + // required: true + // }, + // image: { + // required: true + // }, + // body: { + // required: true + // } + // } + // }, + // } + // ] const singleBidRequest = { bid: [ @@ -107,9 +107,9 @@ describe('adnuntiusBidAdapter', function () { bid: videoBidderRequest } - const nativeBidRequest = { - bid: nativeBidderRequest - } + // const nativeBidRequest = { + // bid: nativeBidderRequest + // } const serverResponse = { body: { @@ -237,83 +237,83 @@ describe('adnuntiusBidAdapter', function () { ] } } - const serverNativeResponse = { - body: { - 'adUnits': [ - { - 'auId': '000000000008b6bc', - 'targetId': '123', - 'html': '

hi!

', - 'matchedAdCount': 1, - 'responseId': 'adn-rsp-1460129238', - 'ads': [ - { - 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', - 'assets': { - 'image': { - 'cdnId': 'https://assets.adnuntius.com/K9rfXC6wJvgVuy4Fbt5P8oEEGXme9ZaP8BNDzz3OMGQ.jpg', - 'width': '300', - 'height': '250' - } - }, - 'text': { - 'body': { - 'content': 'Testing Native ad from Adnuntius', - 'length': '32', - 'minLength': '0', - 'maxLength': '100' - }, - 'title': { - 'content': 'Native Ad', - 'length': '9', - 'minLength': '5', - 'maxLength': '100' - } - }, - 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'urls': { - 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' - }, - 'urlsEsc': { - 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' - }, - 'destinationUrls': { - 'destination': 'http://google.com' - }, - 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, - 'bid': { 'amount': 0.005, 'currency': 'NOK' }, - 'cost': { 'amount': 0.005, 'currency': 'NOK' }, - 'impressionTrackingUrls': [], - 'impressionTrackingUrlsEsc': [], - 'adId': 'adn-id-1347343135', - 'selectedColumn': '0', - 'selectedColumnPosition': '0', - 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'creativeWidth': '980', - 'creativeHeight': '120', - 'creativeId': 'wgkq587vgtpchsx1', - 'lineItemId': 'scyjdyv3mzgdsnpf', - 'layoutId': 'sw6gtws2rdj1kwby', - 'layoutName': 'Responsive image' - }, - - ] - }, - { - 'auId': '000000000008b6bc', - 'targetId': '456', - 'matchedAdCount': 0, - 'responseId': 'adn-rsp-1460129238', - } - ] - } - } + // const serverNativeResponse = { + // body: { + // 'adUnits': [ + // { + // 'auId': '000000000008b6bc', + // 'targetId': '123', + // 'html': '

hi!

', + // 'matchedAdCount': 1, + // 'responseId': 'adn-rsp-1460129238', + // 'ads': [ + // { + // 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', + // 'assets': { + // 'image': { + // 'cdnId': 'https://assets.adnuntius.com/K9rfXC6wJvgVuy4Fbt5P8oEEGXme9ZaP8BNDzz3OMGQ.jpg', + // 'width': '300', + // 'height': '250' + // } + // }, + // 'text': { + // 'body': { + // 'content': 'Testing Native ad from Adnuntius', + // 'length': '32', + // 'minLength': '0', + // 'maxLength': '100' + // }, + // 'title': { + // 'content': 'Native Ad', + // 'length': '9', + // 'minLength': '5', + // 'maxLength': '100' + // } + // }, + // 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'urls': { + // 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' + // }, + // 'urlsEsc': { + // 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' + // }, + // 'destinationUrls': { + // 'destination': 'http://google.com' + // }, + // 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, + // 'bid': { 'amount': 0.005, 'currency': 'NOK' }, + // 'cost': { 'amount': 0.005, 'currency': 'NOK' }, + // 'impressionTrackingUrls': [], + // 'impressionTrackingUrlsEsc': [], + // 'adId': 'adn-id-1347343135', + // 'selectedColumn': '0', + // 'selectedColumnPosition': '0', + // 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + // 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + // 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'creativeWidth': '980', + // 'creativeHeight': '120', + // 'creativeId': 'wgkq587vgtpchsx1', + // 'lineItemId': 'scyjdyv3mzgdsnpf', + // 'layoutId': 'sw6gtws2rdj1kwby', + // 'layoutName': 'Responsive image' + // }, + + // ] + // }, + // { + // 'auId': '000000000008b6bc', + // 'targetId': '456', + // 'matchedAdCount': 0, + // 'responseId': 'adn-rsp-1460129238', + // } + // ] + // } + // } describe('inherited functions', function () { it('exists and is a function', function () { @@ -531,21 +531,21 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].vastXml).to.equal(serverVideoResponse.body.adUnits[0].vastXml); }); }); - describe('interpretNativeResponse', function () { - it('should return valid response when passed valid server response', function () { - const interpretedResponse = spec.interpretResponse(serverNativeResponse, nativeBidRequest); - const ad = serverNativeResponse.body.adUnits[0].ads[0] - expect(interpretedResponse).to.have.lengthOf(1); - expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); - expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); - expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); - expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); - expect(interpretedResponse[0].netRevenue).to.equal(false); - expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); - expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); - expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); - expect(interpretedResponse[0].native.body).to.equal(serverNativeResponse.body.adUnits[0].ads[0].text.body.content); - }); - }); + // describe('interpretNativeResponse', function () { + // it('should return valid response when passed valid server response', function () { + // const interpretedResponse = spec.interpretResponse(serverNativeResponse, nativeBidRequest); + // const ad = serverNativeResponse.body.adUnits[0].ads[0] + // expect(interpretedResponse).to.have.lengthOf(1); + // expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); + // expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); + // expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); + // expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + // expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + // expect(interpretedResponse[0].netRevenue).to.equal(false); + // expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); + // expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); + // expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); + // expect(interpretedResponse[0].native.body).to.equal(serverNativeResponse.body.adUnits[0].ads[0].text.body.content); + // }); + // }); }); From 3c536e181ed104bcfdc9766bea8034afe123482a Mon Sep 17 00:00:00 2001 From: Krzysztof Desput Date: Wed, 25 Jan 2023 16:18:39 +0100 Subject: [PATCH 026/375] Holid bid adapter: skip user syncs when no bidders in bid response (#9462) --- modules/holidBidAdapter.js | 13 ++++++---- test/spec/modules/holidBidAdapter_spec.js | 29 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index be5a7a044c3..92b0a1c18db 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -85,11 +85,12 @@ export const spec = { } const syncs = [] + const bidders = getBidders(serverResponse) - if (optionsType.iframeEnabled) { + if (optionsType.iframeEnabled && bidders) { const queryParams = [] - queryParams.push('bidders=' + getBidders(serverResponse)) + queryParams.push('bidders=' + bidders) queryParams.push('gdpr=' + +gdprConsent.gdprApplies) queryParams.push('gdpr_consent=' + gdprConsent.consentString) queryParams.push('usp_consent=' + (uspConsent || '')) @@ -107,6 +108,8 @@ export const spec = { return syncs } + + return [] }, } @@ -136,10 +139,12 @@ function getImp(bid) { function getBidders(serverResponse) { const bidders = serverResponse - .map((res) => Object.keys(res.body.ext.responsetimemillis)) + .map((res) => Object.keys(res.body.ext.responsetimemillis || [])) .flat(1) - return encodeURIComponent(JSON.stringify([...new Set(bidders)])) + if (bidders.length) { + return encodeURIComponent(JSON.stringify([...new Set(bidders)])) + } } function addWurl(auctionId, adId, wurl) { diff --git a/test/spec/modules/holidBidAdapter_spec.js b/test/spec/modules/holidBidAdapter_spec.js index e18a5ac58f4..e55befd213a 100644 --- a/test/spec/modules/holidBidAdapter_spec.js +++ b/test/spec/modules/holidBidAdapter_spec.js @@ -161,5 +161,34 @@ describe('holidBidAdapterTests', () => { expect(userSyncs).to.deep.equal(expectedUserSyncs) }) + + it('should return empty user syncs when responsetimemillis is not defined', () => { + const optionsType = { + iframeEnabled: true, + pixelEnabled: true, + } + const serverResponse = [ + { + body: { + ext: {}, + }, + }, + ] + const gdprConsent = { + gdprApplies: 1, + consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', + } + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + const expectedUserSyncs = [] + + const userSyncs = spec.getUserSyncs( + optionsType, + serverResponse, + gdprConsent, + uspConsent + ) + + expect(userSyncs).to.deep.equal(expectedUserSyncs) + }) }) }) From da42b1b607d4d463551e3eaf8ef5bfd02f94370e Mon Sep 17 00:00:00 2001 From: sag-jonhil <78849369+sag-jonhil@users.noreply.github.com> Date: Wed, 25 Jan 2023 17:29:10 +0100 Subject: [PATCH 027/375] 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 --- modules/seedingAllianceBidAdapter.js | 245 +++++++++--------- .../modules/seedingAllianceAdapter_spec.js | 89 +++++-- 2 files changed, 195 insertions(+), 139 deletions(-) diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index 64b9cd5d4aa..29953da7ffa 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -2,120 +2,111 @@ 'use strict'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE } from '../src/mediaTypes.js'; -import { _map, deepSetValue, isEmpty, deepAccess } from '../src/utils.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import { _map, isArray, isEmpty, deepSetValue, replaceAuctionPrice } from '../src/utils.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const BIDDER_CODE = 'seedingAlliance'; const GVL_ID = 371; +const BIDDER_CODE = 'seedingAlliance'; const DEFAULT_CUR = 'EUR'; const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; -const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; +const NATIVE_ASSET_IDS = { 0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon' }; const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - - body: { - id: 1, - name: 'data', - type: 2 - }, - - sponsoredBy: { - id: 2, - name: 'data', - type: 1 - }, - - image: { - id: 3, - type: 3, - name: 'img' - }, - - cta: { - id: 4, - type: 12, - name: 'data' - }, - - icon: { - id: 5, - type: 1, - name: 'img' - } + title: { id: 0, name: 'title' }, + body: { id: 1, name: 'data', type: 2 }, + sponsoredBy: { id: 2, name: 'data', type: 1 }, + image: { id: 3, type: 3, name: 'img' }, + cta: { id: 4, type: 12, name: 'data' }, + icon: { id: 5, type: 1, name: 'img' } }; export const spec = { code: BIDDER_CODE, - gvlid: GVL_ID, + supportedMediaTypes: [NATIVE, BANNER], - supportedMediaTypes: [NATIVE], - - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { return !!bid.params.adUnitId; }, - buildRequests: (validBidRequests, bidderRequest) => { + buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = bidderRequest.auctionId; - const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; let url = bidderRequest.refererInfo.page; - const imp = validBidRequests.map((bid, id) => { - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - - const asset = { - required: bidParams.required & 1 - }; + const imps = validBidRequests.map((bidRequest, id) => { + const imp = { + id: String(id + 1), + tagid: bidRequest.params.adUnitId + }; - if (props) { - asset.id = props.id; + /** + * Native Ad + */ + if (bidRequest.nativeParams) { + const assets = _map(bidRequest.nativeParams, (nativeAsset, key) => { + const props = NATIVE_PARAMS[key]; + + if (props) { + let wmin, hmin, w, h; + let aRatios = nativeAsset.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } - let w, h; + if (nativeAsset.sizes) { + const sizes = flatten(nativeAsset.sizes); + w = parseInt(sizes[0], 10); + h = parseInt(sizes[1], 10); + } - if (bidParams.sizes) { - w = bidParams.sizes[0]; - h = bidParams.sizes[1]; + const asset = { + id: props.id, + required: nativeAsset.required & 1 + }; + + asset[props.name] = { + len: nativeAsset.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } else { + // TODO Filter impressions with required assets we don't support } + }).filter(Boolean); - asset[props.name] = { - len: bidParams.len, - type: props.type, - w, - h - }; + imp.native = { + request: { + assets + } + }; + } else { + let sizes = transformSizes(bidRequest.sizes); - return asset; + imp.banner = { + format: sizes, + w: sizes[0] ? sizes[0].w : 0, + h: sizes[0] ? sizes[0].h : 0 } - }) - .filter(Boolean); + } - if (bid.params.url) { - url = bid.params.url; + if (bidRequest.params.url) { + url = bidRequest.params.url; } - return { - id: String(id + 1), - tagid: bid.params.adUnitId, - tid: tid, - pt: pt, - native: { - request: { - assets - } - } - }; + return imp; }); const request = { @@ -123,12 +114,9 @@ export const spec = { site: { page: url }, - device: { - ua: navigator.userAgent - }, - cur, - imp, - user: {}, + cur: [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR], + imp: imps, + tmax: bidderRequest.timeout, regs: { ext: { gdpr: 0, @@ -137,23 +125,22 @@ export const spec = { } }; - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent) { + request.user = {}; + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); } return { method: 'POST', - url: ENDPOINT_URL, + url: config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL, data: JSON.stringify(request), - options: { - contentType: 'application/json' - }, - bids: validBidRequests + bidRequests: validBidRequests }; }, - interpretResponse: function(serverResponse, { bids }) { + interpretResponse: function (serverResponse, { bidRequests }) { if (isEmpty(serverResponse.body)) { return []; } @@ -165,35 +152,72 @@ export const spec = { return result; }, []) : []; - return bids - .map((bid, id) => { + return bidRequests + .map((bidRequest, id) => { const bidResponse = bidResponses[id]; + const type = bidRequest.nativeParams ? NATIVE : BANNER; + if (bidResponse) { - return { - requestId: bid.bidId, + const bidObject = { + requestId: bidRequest.bidId, // TODO get this value from response? cpm: bidResponse.price, creativeId: bidResponse.crid, - ttl: 1000, - netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), + ttl: 600, + netRevenue: true, currency: cur, - mediaType: NATIVE, + mediaType: type, bidderCode: BIDDER_CODE, - native: parseNative(bidResponse), meta: { advertiserDomains: bidResponse.adomain && bidResponse.adomain.length > 0 ? bidResponse.adomain : [] } }; + + if (type === NATIVE) { + bidObject.native = parseNative(bidResponse); + bidObject.mediaType = NATIVE; + } + + if (type === BANNER) { + bidObject.ad = replaceAuctionPrice(bidResponse.adm, bidResponse.price); + bidObject.width = bidResponse.w; + bidObject.height = bidResponse.h; + bidObject.mediaType = BANNER; + } + + return bidObject; } }) .filter(Boolean); } }; -registerBidder(spec); +function transformSizes(requestSizes) { + if (!isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (isArray(requestSizes[0])) { + return requestSizes.map(item => ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + })); + } + + return []; +} + +function flatten(arr) { + return [].concat(...arr); +} function parseNative(bid) { - const {assets, link, imptrackers} = bid.adm.native; + const { assets, link, imptrackers } = bid.adm.native; let clickUrl = link.url.replace(/\$\{AUCTION_PRICE\}/g, bid.price); @@ -228,15 +252,4 @@ function parseNative(bid) { return result; } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - -function flatten(arr) { - return [].concat(...arr); -} +registerBidder(spec); diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index 81af9546ff0..6086db01de4 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -38,7 +38,7 @@ describe('SeedingAlliance adapter', function () { }); it('should have default request structure', function () { - let keys = 'site,device,cur,imp,user,regs'.split(','); + let keys = 'site,cur,imp,regs'.split(','); let validBidRequests = [{ bidId: 'bidId', params: {} @@ -60,14 +60,17 @@ describe('SeedingAlliance adapter', function () { assert.equal(request.id, validBidRequests[0].auctionId); }); - it('Verify the device', function () { + it('Verify the site url', function () { + let siteUrl = 'https://www.yourdomain.tld/your-directory/'; let validBidRequests = [{ bidId: 'bidId', - params: {} + params: { + url: siteUrl + } }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); - assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.site.page, siteUrl); }); it('Verify native asset ids', function () { @@ -109,7 +112,7 @@ describe('SeedingAlliance adapter', function () { }); describe('interpretResponse', function () { - const goodResponse = { + const goodNativeResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', @@ -136,51 +139,91 @@ describe('SeedingAlliance adapter', function () { ] } }; + + const goodBannerResponse = { + body: { + cur: 'EUR', + id: 'b4516b80-886e-4ec0-82ae-9209e6d625fb', + seatbid: [ + { + seat: 'seedingAlliance', + bid: [{ + adm: '', + impid: 1, + price: 0.90, + h: 250, + w: 300 + }] + } + ] + } + }; + const badResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', seatbid: [] }}; - const bidRequest = { + const bidNativeRequest = { data: {}, - bids: [{ bidId: 'bidId1' }] + bidRequests: [{bidId: 'bidId1', nativeParams: {title: {required: true, len: 800}}}] + }; + + const bidBannerRequest = { + data: {}, + bidRequests: [{bidId: 'bidId1', sizes: [300, 250]}] }; it('should return null if body is missing or empty', function () { - const result = spec.interpretResponse(badResponse, bidRequest); + const result = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); delete badResponse.body - const result1 = spec.interpretResponse(badResponse, bidRequest); + const result1 = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); }); it('should return the correct params', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; - - assert.deepEqual(result[0].currency, goodResponse.body.cur); - assert.deepEqual(result[0].requestId, bidRequest.bids[0].bidId); - assert.deepEqual(result[0].cpm, bid.price); - assert.deepEqual(result[0].creativeId, bid.crid); - assert.deepEqual(result[0].mediaType, 'native'); - assert.deepEqual(result[0].bidderCode, 'seedingAlliance'); + const resultNative = spec.interpretResponse(goodNativeResponse, bidNativeRequest); + const bidNative = goodNativeResponse.body.seatbid[0].bid[0]; + + assert.deepEqual(resultNative[0].bidderCode, 'seedingAlliance'); + assert.deepEqual(resultNative[0].currency, goodNativeResponse.body.cur); + assert.deepEqual(resultNative[0].requestId, bidNativeRequest.bidRequests[0].bidId); + assert.deepEqual(resultNative[0].cpm, bidNative.price); + assert.deepEqual(resultNative[0].creativeId, bidNative.crid); + assert.deepEqual(resultNative[0].mediaType, 'native'); + + const resultBanner = spec.interpretResponse(goodBannerResponse, bidBannerRequest); + + assert.deepEqual(resultBanner[0].bidderCode, 'seedingAlliance'); + assert.deepEqual(resultBanner[0].mediaType, 'banner'); + assert.deepEqual(resultBanner[0].width, bidBannerRequest.bidRequests[0].sizes[0]); + assert.deepEqual(resultBanner[0].height, bidBannerRequest.bidRequests[0].sizes[1]); }); - it('should return the correct tracking links', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; + it('should return the correct native tracking links', function () { + const result = spec.interpretResponse(goodNativeResponse, bidNativeRequest); + const bid = goodNativeResponse.body.seatbid[0].bid[0]; const regExpPrice = new RegExp('price=' + bid.price); result[0].native.clickTrackers.forEach(function (clickTracker) { - assert.ok(clickTracker.search(regExpPrice) > -1); + assert.ok(clickTracker.search(regExpPrice) > -1); }); result[0].native.impressionTrackers.forEach(function (impTracker) { - assert.ok(impTracker.search(regExpPrice) > -1); + assert.ok(impTracker.search(regExpPrice) > -1); }); }); + + it('should return the correct banner content', function () { + const result = spec.interpretResponse(goodBannerResponse, bidBannerRequest); + const bid = goodBannerResponse.body.seatbid[0].bid[0]; + const regExpContent = new RegExp(''); + + assert.ok(result[0].ad.search(regExpContent) > -1); + }); }); }); From 628c22931d9d49c11a014b1a7fb6197fb4b04b69 Mon Sep 17 00:00:00 2001 From: EMX Digital <43830380+EMXDigital@users.noreply.github.com> Date: Wed, 25 Jan 2023 09:16:16 -0800 Subject: [PATCH 028/375] 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 --- modules/emx_digitalBidAdapter.js | 11 +++++-- .../modules/emx_digitalBidAdapter_spec.js | 31 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index eb69e76a837..99f313b9484 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -354,16 +354,23 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { const syncs = []; + const consentParams = []; if (syncOptions.iframeEnabled) { let url = 'https://biddr.brealtime.com/check.html'; if (gdprConsent && typeof gdprConsent.consentString === 'string') { // add 'gdpr' only if 'gdprApplies' is defined if (typeof gdprConsent.gdprApplies === 'boolean') { - url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + consentParams.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); } else { - url += `?gdpr_consent=${gdprConsent.consentString}`; + consentParams.push(`?gdpr_consent=${gdprConsent.consentString}`); } } + if (uspConsent && typeof uspConsent.consentString === 'string') { + consentParams.push(`usp=${uspConsent.consentString}`); + } + if (consentParams.length > 0) { + url = url + '?' + consentParams.join('&'); + } syncs.push({ type: 'iframe', url: url diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index d99318b5ddc..d80d0f3e875 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -707,7 +707,7 @@ describe('emx_digital Adapter', function () { })); }); - it('returns valid advertiser domain', function () { + it('returns valid advertiser domains', function () { const bidResponse = utils.deepClone(serverResponse); let result = spec.interpretResponse({body: bidResponse}); expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); @@ -724,6 +724,7 @@ describe('emx_digital Adapter', function () { expect(syncs).to.not.be.an('undefined'); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html') }); it('should pass gdpr params', function () { @@ -734,6 +735,34 @@ describe('emx_digital Adapter', function () { expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); expect(syncs[0].url).to.contains('gdpr=0'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=0&gdpr_consent=test') + }); + + it('should pass us_privacy string', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, { + consentString: 'test', + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('usp=test'); + }); + + it('should pass us_privacy and gdpr strings', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, + { + gdprApplies: true, + consentString: 'test' + }, + { + consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=1'); + expect(syncs[0].url).to.contains('usp=test'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=1&gdpr_consent=test&usp=test') }); }); }); From fd952368c8bd3ca070645d28aedf9fcdca592da8 Mon Sep 17 00:00:00 2001 From: Jason Piros Date: Wed, 25 Jan 2023 09:17:40 -0800 Subject: [PATCH 029/375] consumableBidAdapter: add gdpr and usp sync params (#9463) --- modules/consumableBidAdapter.js | 22 ++++++++- .../spec/modules/consumableBidAdapter_spec.js | 46 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index c91f1a7f906..4e2a92fb594 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -180,12 +180,26 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + let syncUrl = 'https://sync.serverbid.com/ss/' + siteId + '.html'; + if (syncOptions.iframeEnabled) { + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl = appendUrlParam(syncUrl, `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); + } else { + syncUrl = appendUrlParam(syncUrl, `gdpr=0&gdpr_consent=${gdprConsent.consentString}`); + } + } + + if (uspConsent && uspConsent.consentString) { + syncUrl = appendUrlParam(syncUrl, `us_privacy=${uspConsent.consentString}`); + } + if (!serverResponses || serverResponses.length === 0 || !serverResponses[0].body.bdr || serverResponses[0].body.bdr !== 'cx') { return [{ type: 'iframe', - url: 'https://sync.serverbid.com/ss/' + siteId + '.html' + url: syncUrl }]; } } @@ -294,4 +308,8 @@ function getBidFloor(bid, sizes) { return floor; } +function appendUrlParam(url, queryString) { + return `${url}${url.indexOf('?') > -1 ? '&' : '?'}${queryString}`; +} + registerBidder(spec); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index d1b310624a6..556dce447b9 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -625,6 +625,52 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); + it('should return a sync url if iframe syncs are enabled and GDPR applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and GDPR is undefined', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: undefined, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=0&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and USP applies', function () { + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], {}, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?us_privacy=USP_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled, GDPR and USP applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING&us_privacy=USP_CONSENT_STRING'); + }) + it('should return a sync url if pixel syncs are enabled and some are returned from the server', function () { let syncOptions = {'pixelEnabled': true}; let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE]); From ff84384903473b6fdf57fdedba659645ff04041b Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Wed, 25 Jan 2023 11:48:41 -0700 Subject: [PATCH 030/375] 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 --- libraries/ortbConverter/processors/default.js | 20 +++++++++++++++- .../prebidServerBidAdapter/ortbConverter.js | 2 +- .../modules/prebidServerBidAdapter_spec.js | 22 ++++++++++++++++++ .../ortbConverter/default_processors_spec.js | 23 +++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 test/spec/ortbConverter/default_processors_spec.js diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index 8d44de00fa2..1d6bfb8424e 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -1,4 +1,4 @@ -import {deepSetValue, mergeDeep} from '../../../src/utils.js'; +import {deepSetValue, logWarn, mergeDeep} from '../../../src/utils.js'; import {bannerResponseProcessor, fillBannerImp} from './banner.js'; import {fillVideoImp, fillVideoResponse} from './video.js'; import {setResponseMediaType} from './mediaType.js'; @@ -20,6 +20,10 @@ export const DEFAULT_PROCESSORS = { appFpd: fpdFromTopLevelConfig('app'), siteFpd: fpdFromTopLevelConfig('site'), deviceFpd: fpdFromTopLevelConfig('device'), + onlyOneClient: { + priority: -99, + fn: onlyOneClientSection + }, props: { // sets request properties id, tmax, test, source.tid fn(ortbRequest, bidderRequest) { @@ -133,3 +137,17 @@ function fpdFromTopLevelConfig(prop) { } } } + +export function onlyOneClientSection(ortbRequest) { + ['dooh', 'app', 'site'].reduce((found, section) => { + if (ortbRequest[section] != null && Object.keys(ortbRequest[section]).length > 0) { + if (found != null) { + logWarn(`ORTB request specifies both '${found}' and '${section}'; dropping the latter.`) + delete ortbRequest[section]; + } else { + found = section; + } + } + return found; + }, null); +} diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 83335f81bc2..e35a3825826 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -47,7 +47,7 @@ const PBS_CONVERTER = ortbConverter({ request.tmax = s2sBidRequest.s2sConfig.timeout; deepSetValue(request, 'source.tid', proxyBidderRequest.auctionId); - [request.app, request.site].forEach(section => { + [request.app, request.dooh, request.site].forEach(section => { if (section && !section.publisher?.id) { deepSetValue(section, 'publisher.id', s2sBidRequest.s2sConfig.accountId); } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 9da242381be..999a4477d19 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1505,6 +1505,28 @@ describe('S2S Adapter', function () { }); }); + it('site should not be present when app is present', function () { + const _config = { + s2sConfig: CONFIG, + app: { bundle: 'com.test.app' }, + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.site).to.not.exist; + expect(requestBid.app).to.exist.and.to.be.a('object'); + }); + it('adds appnexus aliases to request', function () { config.setConfig({ s2sConfig: CONFIG }); diff --git a/test/spec/ortbConverter/default_processors_spec.js b/test/spec/ortbConverter/default_processors_spec.js new file mode 100644 index 00000000000..48204b2c861 --- /dev/null +++ b/test/spec/ortbConverter/default_processors_spec.js @@ -0,0 +1,23 @@ +import {onlyOneClientSection} from '../../../libraries/ortbConverter/processors/default.js'; + +describe('onlyOneClientSection', () => { + [ + [['app'], 'app'], + [['site'], 'site'], + [['dooh'], 'dooh'], + [['app', 'site'], 'app'], + [['dooh', 'app', 'site'], 'dooh'], + [['dooh', 'site'], 'dooh'] + ].forEach(([sections, winner]) => { + it(`should leave only ${winner} in request when it contains ${sections.join(', ')}`, () => { + const req = Object.fromEntries(sections.map(s => [s, {foo: 'bar'}])); + onlyOneClientSection(req); + expect(Object.keys(req)).to.eql([winner]); + }) + }); + it('should not choke if none of the sections are in the request', () => { + const req = {}; + onlyOneClientSection(req); + expect(req).to.eql({}); + }); +}); From ed385ba9a5dd1d795fd7830267832eb8d61bb4d4 Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Wed, 25 Jan 2023 12:48:49 -0800 Subject: [PATCH 031/375] updated pbs filterSettings to sync with pbjs config filterSettings (#9423) --- modules/prebidServerBidAdapter/index.js | 15 ++- .../modules/prebidServerBidAdapter_spec.js | 116 ++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index b609d1a54ec..924748ce197 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -222,10 +222,23 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, gppConsent, s2sConfig) } _syncCount++; + let filterSettings = {}; + const userSyncFilterSettings = getConfig('userSync.filterSettings'); + + if (userSyncFilterSettings) { + const { all, iframe, image } = userSyncFilterSettings; + const ifrm = iframe || all; + const img = image || all; + + if (ifrm) filterSettings = Object.assign({ iframe: ifrm }, filterSettings); + if (img) filterSettings = Object.assign({ image: img }, filterSettings); + } + const payload = { uuid: generateUUID(), bidders: bidderCodes, - account: s2sConfig.accountId + account: s2sConfig.accountId, + filterSettings }; let userSyncLimit = s2sConfig.userSyncLimit; diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 999a4477d19..cffc75f6949 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-trailing-spaces */ import {expect} from 'chai'; import { PrebidServer as Adapter, @@ -1725,6 +1726,121 @@ describe('S2S Adapter', function () { }]); }); + describe('filterSettings', function () { + const getRequestBid = userSync => { + let cookieSyncConfig = utils.deepClone(CONFIG); + const s2sBidRequest = utils.deepClone(REQUEST); + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; + s2sBidRequest.s2sConfig = cookieSyncConfig; + + config.setConfig({ userSync, s2sConfig: cookieSyncConfig }); + + let bidRequest = utils.deepClone(BID_REQUESTS); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + return JSON.parse(server.requests[0].requestBody); + } + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the all key is present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['appnexus', 'rubicon', 'pubmatic'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + }, + 'iframe': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the iframe key is present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + iframe: { + bidders: ['rubicon', 'pubmatic'], + filter: 'include' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': '*', + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['rubicon', 'pubmatic'], + 'filter': 'include' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the image and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + image: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the all and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + }); + it('adds limit to the cookie_sync request if userSyncLimit is greater than 0', function () { let cookieSyncConfig = utils.deepClone(CONFIG); cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; From bd57c8dffe2effe149d3f0e71ab07605c578dc8b Mon Sep 17 00:00:00 2001 From: Jose Cabal-Ugaz <6942011+josecu@users.noreply.github.com> Date: Wed, 25 Jan 2023 17:59:49 -0500 Subject: [PATCH 032/375] 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 --- modules/.submodules.json | 1 + modules/arcspanRtdProvider.js | 73 ++++++++ modules/arcspanRtdProvider.md | 11 ++ src/adloader.js | 3 +- test/spec/modules/arcspanRtdProvider_spec.js | 187 +++++++++++++++++++ 5 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 modules/arcspanRtdProvider.js create mode 100644 modules/arcspanRtdProvider.md create mode 100644 test/spec/modules/arcspanRtdProvider_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index deeee91e247..a535fd4988d 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -55,6 +55,7 @@ "aaxBlockmeterRtdProvider", "airgridRtdProvider", "akamaiDapRtdProvider", + "arcspanRtdProvider", "blueconicRtdProvider", "browsiRtdProvider", "captifyRtdProvider", diff --git a/modules/arcspanRtdProvider.js b/modules/arcspanRtdProvider.js new file mode 100644 index 00000000000..a7ffa059279 --- /dev/null +++ b/modules/arcspanRtdProvider.js @@ -0,0 +1,73 @@ +import { submodule } from '../src/hook.js'; +import { mergeDeep } from '../src/utils.js'; +import {loadExternalScript} from '../src/adloader.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'arcspan'; + +/** @type {RtdSubmodule} */ +export const arcspanSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: alterBidRequests, +}; + +function init(config, userConsent) { + if (typeof config.params.silo === 'undefined') { + return false; + } + if (typeof window.arcobj2 === 'undefined') { + var scriptUrl; + if (config.params.silo === 'test') { + scriptUrl = 'https://localhost:8080/as.js'; + } else { + scriptUrl = 'https://silo' + config.params.silo + '.p7cloud.net/as.js'; + } + loadExternalScript(scriptUrl, SUBMODULE_NAME); + } + return true; +} + +function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { + var _v1 = []; + var _v1s = []; + var _v2 = []; + var arcobj1 = window.arcobj1; + if (typeof arcobj1 != 'undefined') { + if (typeof arcobj1.page_iab_codes.text != 'undefined') { _v1 = _v1.concat(arcobj1.page_iab_codes.text); } + if (typeof arcobj1.page_iab_codes.images != 'undefined') { _v1 = _v1.concat(arcobj1.page_iab_codes.images); } + if (typeof arcobj1.page_iab.text != 'undefined') { _v1s = _v1s.concat(arcobj1.page_iab.text); } + if (typeof arcobj1.page_iab.images != 'undefined') { _v1s = _v1s.concat(arcobj1.page_iab.images); } + if (typeof arcobj1.page_iab_newcodes.text != 'undefined') { _v2 = [...new Set([..._v2, ...arcobj1.page_iab_newcodes.text])]; } + if (typeof arcobj1.page_iab_newcodes.images != 'undefined') { _v2 = [...new Set([..._v2, ...arcobj1.page_iab_newcodes.images])]; } + + var _content = {}; + _content.data = []; + var p = {}; + p.name = 'arcspan'; + p.segment = []; + p.ext = { segtax: 6 }; + _v2.forEach(function (e) { + p.segment = p.segment.concat({ id: e }); + }); + _content.data = _content.data.concat(p); + var _ortb2 = { + site: { + name: 'arcspan', + domain: new URL(location.href).hostname, + cat: _v1, + sectioncat: _v1, + pagecat: _v1, + page: location.href, + ref: document.referrer, + keywords: _v1s.toString(), + content: _content, + }, + }; + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, _ortb2); + } + callback(); +} + +submodule(MODULE_NAME, arcspanSubmodule); diff --git a/modules/arcspanRtdProvider.md b/modules/arcspanRtdProvider.md new file mode 100644 index 00000000000..4aa1de02acf --- /dev/null +++ b/modules/arcspanRtdProvider.md @@ -0,0 +1,11 @@ +# Overview + +Module Name: ArcSpan Rtd Provider + +Module Type: Rtd Provider + +Maintainer: engineering@arcspan.com + +# Description + +RTD provider for ArcSpan Technologies. Contact jcabalugaz@arcspan.com for more information. diff --git a/src/adloader.js b/src/adloader.js index 01a77971b93..f0b7f7f3e8c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -21,7 +21,8 @@ const _approvedLoadExternalJSList = [ 'medianet', 'improvedigital', 'aaxBlockmeter', - 'confiant' + 'confiant', + 'arcspan' ] /** diff --git a/test/spec/modules/arcspanRtdProvider_spec.js b/test/spec/modules/arcspanRtdProvider_spec.js new file mode 100644 index 00000000000..c75075d8e05 --- /dev/null +++ b/test/spec/modules/arcspanRtdProvider_spec.js @@ -0,0 +1,187 @@ +import { arcspanSubmodule } from 'modules/arcspanRtdProvider.js'; +import { expect } from 'chai'; +import { loadExternalScript } from 'src/adloader.js'; + +describe('arcspanRtdProvider', function () { + describe('init', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('successfully initializes with a valid silo ID', function () { + expect(arcspanSubmodule.init(getGoodConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://silo13.p7cloud.net/as.js'); + loadExternalScript.resetHistory(); + }); + + it('fails to initialize with a missing silo ID', function () { + expect(arcspanSubmodule.init(getBadConfig())).to.equal(false); + expect(loadExternalScript.called).to.be.not.ok; + loadExternalScript.resetHistory(); + }); + + it('drops localhost script for test silo', function () { + expect(arcspanSubmodule.init(getTestConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://localhost:8080/as.js'); + loadExternalScript.resetHistory(); + }); + }); + + describe('alterBidRequests', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('alters the bid request 1', function () { + setIAB({ + raw: { + images: [ + 'Religion & Spirituality', + 'Medical Health>Substance Abuse', + 'Religion & Spirituality>Astrology', + 'Medical Health', + 'Events & Attractions', + ], + }, + codes: { + images: ['IAB23-10', 'IAB7', 'IAB7-42', 'IAB15-1'], + }, + newcodes: { + images: ['150', '453', '311', '456', '286'], + }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Religion & Spirituality,Medical Health>Substance Abuse,Religion & Spirituality>Astrology,Medical Health,Events & Attractions' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '150' }, + { id: '453' }, + { id: '311' }, + { id: '456' }, + { id: '286' } + ]); + }); + }); + + it('alters the bid request 2', function () { + setIAB({ + raw: { text: ['Sports', 'Sports>Soccer'] }, + codes: { text: ['IAB17', 'IAB17-44'] }, + newcodes: { text: ['483', '533'] }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Sports,Sports>Soccer' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB17', + 'IAB17_44' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '483' }, + { id: '533' } + ]); + }); + }); + }); +}); + +function getGoodConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 13, + }, + }; +} + +function getBadConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + notasilo: 1, + }, + }; +} + +function getTestConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 'test', + }, + }; +} + +function setIAB(vjson) { + window.arcobj2 = {}; + window.arcobj2.cat = 0; + if (typeof vjson.codes != 'undefined') { + window.arcobj2.cat = 1; + if (typeof vjson.codes.images != 'undefined') { + vjson.codes.images.forEach(function f(e, i) { + vjson.codes.images[i] = e.replace('-', '_'); + }); + } + if (typeof vjson.codes.text != 'undefined') { + vjson.codes.text.forEach(function f(e, i) { + vjson.codes.text[i] = e.replace('-', '_'); + }); + } + window.arcobj2.sampled = 1; + window.arcobj1 = {}; + window.arcobj1.page_iab_codes = vjson.codes; + window.arcobj1.page_iab = vjson.raw; + window.arcobj1.page_iab_newcodes = vjson.newcodes; + } +} From 39c2f87a2b791363e1c4066176b41a0572aa803f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 26 Jan 2023 06:46:58 -0700 Subject: [PATCH 033/375] Update issue tracker action to use new gh api (#9466) --- .github/workflows/issue_tracker.yml | 41 ++++++++++++++++++----------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml index 4a9502e38c1..29082a4990a 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -29,21 +29,30 @@ jobs: gh api graphql -f query=' query($org: String!, $number: Int!) { organization(login: $org){ - projectNext(number: $number) { + projectV2(number: $number) { id fields(first:100) { nodes { - id - name - settings + ... on ProjectV2Field { + id + name + } + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } } } } } }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json - echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV - echo 'DATE_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV + echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV + echo 'DATE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name=="'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV - name: Add issue to project env: @@ -52,9 +61,9 @@ jobs: run: | gh api graphql -f query=' mutation($project:ID!, $issue:ID!) { - addProjectNextItem(input: {projectId: $project, contentId: $issue}) { - projectNextItem { - id, + addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) { + item { + id content { ... on Issue { createdAt @@ -67,8 +76,8 @@ jobs: } }' -f project=$PROJECT_ID -f issue=$ISSUE_ID > issue_data.json - echo 'ITEM_ID='$(jq '.data.addProjectNextItem.projectNextItem.id' issue_data.json) >> $GITHUB_ENV - echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectNextItem.projectNextItem.content.createdAt' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_ID='$(jq '.data.addProjectV2ItemById.item.id' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectV2ItemById.item.content.createdAt' issue_data.json | cut -c 2-11) >> $GITHUB_ENV - name: Set fields env: @@ -79,15 +88,17 @@ jobs: $project: ID! $item: ID! $date_field: ID! - $date_value: String! + $date_value: Date! ) { - set_creation_date: updateProjectNextItemField(input: { + set_creation_date: updateProjectV2ItemFieldValue(input: { projectId: $project itemId: $item fieldId: $date_field - value: $date_value + value: { + date: $date_value + } }) { - projectNextItem { + projectV2Item { id } } From 199349c30733e2aff1a43ababb613fa41caffec7 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 26 Jan 2023 15:20:39 +0000 Subject: [PATCH 034/375] Prebid 7.34.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6298a19479c..925a29338b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.34.0-pre", + "version": "7.34.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 4b52267af26..c5d5f83eb2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.34.0-pre", + "version": "7.34.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 9a5f08ce3a9a86a337c9718bc159bdb5c814251d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 26 Jan 2023 15:20:40 +0000 Subject: [PATCH 035/375] Increment version to 7.35.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 925a29338b4..6249ef52c04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.34.0", + "version": "7.35.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c5d5f83eb2c..6467045121b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.34.0", + "version": "7.35.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 60f96abe1f0068820f25b99d9084165d050c25a8 Mon Sep 17 00:00:00 2001 From: Eugene Rachitskiy Date: Thu, 26 Jan 2023 10:54:52 -0500 Subject: [PATCH 036/375] 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 --- modules/pulsepointBidAdapter.js | 2 ++ test/spec/modules/pulsepointBidAdapter_spec.js | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 25f82fb60d9..015e80d5692 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -16,6 +16,7 @@ const DEFAULT_BID_TTL = 20; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'video', 'battr', 'bcat', 'badv', 'bidfloor']; +const DEFAULT_TMAX = 500; /** * PulsePoint Bid Adapter. @@ -54,6 +55,7 @@ export const spec = { user: user(bidRequests[0], bidderRequest), regs: regs(bidderRequest), source: source(bidRequests[0].schain), + tmax: bidderRequest.timeout || DEFAULT_TMAX, }; return { method: 'POST', diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 825b3abf432..60dca9e6da0 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -2,7 +2,6 @@ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter.js'; import {deepClone} from 'src/utils.js'; -import { config } from 'src/config.js'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -225,6 +224,8 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[1].banner).to.not.equal(null); expect(ortbRequest.imp[1].banner.w).to.equal(728); expect(ortbRequest.imp[1].banner.h).to.equal(90); + // tmax + expect(ortbRequest.tmax).to.equal(500); }); it('Verify parse response', function () { @@ -918,4 +919,13 @@ describe('PulsePoint Adapter Tests', function () { } }); }); + + it('Verify bid request timeouts', function () { + const mkRequest = (bidderRequest) => spec.buildRequests(slotConfigs, bidderRequest).data; + // assert default is used when no bidderRequest.timeout value is available + expect(mkRequest(bidderRequest).tmax).to.equal(500) + + // assert bidderRequest value is used when available + expect(mkRequest(Object.assign({}, { timeout: 6000 }, bidderRequest)).tmax).to.equal(6000) + }); }); From ba7d0d0026484396d3098da5dfc96586ca68568b Mon Sep 17 00:00:00 2001 From: joseluis laso Date: Thu, 26 Jan 2023 19:44:41 +0100 Subject: [PATCH 037/375] hadronId user id submodule: force localStorage (#9432) * Storing hadronId in localStorage after getting it from server * reverting hadronId documentation --- modules/hadronIdSystem.js | 69 +++++++++++++----------- test/spec/modules/hadronIdSystem_spec.js | 10 ++-- test/spec/modules/userId_spec.js | 33 +++++++----- 3 files changed, 60 insertions(+), 52 deletions(-) diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index 2f10245cd59..a75c03ee1c4 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -8,8 +8,9 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; -import { isFn, isStr, isPlainObject, logError } from '../src/utils.js'; +import {isFn, isStr, isPlainObject, logError, logInfo} from '../src/utils.js'; +const HADRONID_LOCAL_NAME = 'auHadronId'; const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; @@ -18,8 +19,9 @@ export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'hadron'} /** * Param or default. - * @param {String} param + * @param {String|function} param * @param {String} defaultVal + * @param arg */ function paramOrDefault(param, defaultVal, arg) { if (isFn(param)) { @@ -53,11 +55,11 @@ export const hadronIdSubmodule = { * @returns {Object} */ decode(value) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); + const hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); if (isStr(hadronId)) { return {hadronId: hadronId}; } - return (value && typeof value['hadronId'] === 'string') ? { 'hadronId': value['hadronId'] } : undefined; + return (value && typeof value['hadronId'] === 'string') ? {'hadronId': value['hadronId']} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -70,37 +72,40 @@ export const hadronIdSubmodule = { config.params = {}; } const partnerId = config.params.partnerId | 0; - - const url = urlAddParams( - paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), - `partner_id=${partnerId}&_it=prebid` - ); - + let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); + if (isStr(hadronId)) { + return {id: {hadronId}}; + } const resp = function (callback) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); - if (isStr(hadronId)) { - const responseObj = {hadronId: hadronId}; - callback(responseObj); - } else { - const callbacks = { - success: response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - logError(error); - } + let responseObj = {}; + const callbacks = { + success: response => { + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + logError(error); } - callback(responseObj); - }, - error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); - callback(); + logInfo(`Response from backend is ${responseObj}`); + hadronId = responseObj['hadronId']; + storage.setDataInLocalStorage(HADRONID_LOCAL_NAME, hadronId); + responseObj = {id: {hadronId}}; } - }; - ajax(url, callbacks, undefined, {method: 'GET'}); - } + callback(responseObj); + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + logInfo('HadronId not found in storage, calling backend...'); + const url = urlAddParams( + // config.params.url and config.params.urlArg are not documented + // since their use is for debugging purposes only + paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), + `partner_id=${partnerId}&_it=prebid` + ); + ajax(url, callbacks, undefined, {method: 'GET'}); }; return {callback: resp}; } diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index ca9eadc7fd4..c998ef2cf14 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -24,7 +24,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq(`https://id.hadron.ad.gt/api/v1/pbhid?partner_id=0&_it=prebid`); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); it('gets a cached hadronid', function() { @@ -33,10 +33,8 @@ describe('HadronIdSystem', function () { }; getDataFromLocalStorageStub.withArgs('auHadronId').returns('tstCachedHadronId1'); - const callbackSpy = sinon.spy(); - const callback = hadronIdSubmodule.getId(config).callback; - callback(callbackSpy); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'tstCachedHadronId1'}); + const result = hadronIdSubmodule.getId(config); + expect(result).to.deep.equal({ id: { hadronId: 'tstCachedHadronId1' } }); }); it('allows configurable id url', function() { @@ -51,7 +49,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq('https://hadronid.publync.com?partner_id=0&_it=prebid'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); }); }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 99e0681e547..5403d842e02 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -897,7 +897,7 @@ describe('User ID', function () { storage: {name: 'intentIqId', type: 'cookie'} }, { name: 'hadronId', - storage: {name: 'hadronId', type: 'cookie'} + storage: {name: 'hadronId', type: 'html5'} }, { name: 'zeotapIdPlus' }, { @@ -1872,8 +1872,8 @@ describe('User ID', function () { it('test hook from hadronId html5', function (done) { // simulate existing browser local storage values - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'random-ls-identifier'})); - localStorage.setItem('hadronId_exp', ''); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); init(config); setSubmoduleRegistry([hadronIdSubmodule]); @@ -1883,15 +1883,15 @@ describe('User ID', function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('random-ls-identifier'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'audigent.com', - uids: [{id: 'random-ls-identifier', atype: 1}] + uids: [{id: 'testHadronId1', atype: 1}] }); }); }); localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp', ''); + localStorage.removeItem('hadronId_exp'); done(); }, {adUnits}); }); @@ -2125,7 +2125,9 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + // hadronId only supports localStorage + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); @@ -2149,7 +2151,7 @@ describe('User ID', function () { ['netId', 'netId', 'cookie'], ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], - ['hadronId', 'hadronId', 'cookie'], + ['hadronId', 'hadronId', 'html5'], ['criteo', 'storage_criteo', 'cookie'], ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'], @@ -2192,7 +2194,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); // also check that criteo id was copied to bid expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); @@ -2231,7 +2233,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); @@ -2284,7 +2287,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); @@ -2320,7 +2324,7 @@ describe('User ID', function () { }, { name: 'zeotapIdPlus' }, { - name: 'hadronId', storage: {name: 'hadronId', type: 'cookie'} + name: 'hadronId', storage: {name: 'hadronId', type: 'html5'} }, { name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} }, { @@ -2388,7 +2392,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userId.uid2).to.deep.equal({ id: 'Sample_AD_Token' }); @@ -2420,7 +2424,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); From 5787552f19a06c3f777da0f0531e4032c501d3ed Mon Sep 17 00:00:00 2001 From: Mark Kuhar Date: Thu, 26 Jan 2023 20:40:58 +0100 Subject: [PATCH 038/375] Outbrain Bid Adapter: added video support (#9405) * add video support * add more video props --- modules/outbrainBidAdapter.js | 109 +++++++++- test/spec/modules/outbrainBidAdapter_spec.js | 200 +++++++++++++++++-- 2 files changed, 285 insertions(+), 24 deletions(-) diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 3db1da0d689..6bcbc6a1cba 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -4,11 +4,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE, BANNER } from '../src/mediaTypes.js'; -import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray } from '../src/utils.js'; +import { NATIVE, BANNER, VIDEO } from '../src/mediaTypes.js'; +import { OUTSTREAM } from '../src/video.js'; +import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'outbrain'; const GVLID = 164; @@ -22,11 +24,12 @@ const NATIVE_PARAMS = { body: { id: 4, name: 'data', type: 2 }, cta: { id: 1, type: 12, name: 'data' } }; +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [ NATIVE, BANNER ], + supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], isBidRequestValid: (bid) => { if (typeof bid.params !== 'object') { return false; @@ -50,7 +53,7 @@ export const spec = { return ( !!config.getConfig('outbrain.bidderUrl') && - !!(bid.nativeParams || bid.sizes) + (!!(bid.nativeParams || bid.sizes) || isValidVideoRequest(bid)) ); }, buildRequests: (validBidRequests, bidderRequest) => { @@ -85,6 +88,8 @@ export const spec = { assets: getNativeAssets(bid) }) } + } else if (isVideoRequest(bid)) { + imp.video = getVideoAsset(bid); } else { imp.banner = { format: transformSizes(bid.sizes) @@ -163,7 +168,12 @@ export const spec = { return bids.map((bid, id) => { const bidResponse = bidResponses[id]; if (bidResponse) { - const type = bid.nativeParams ? NATIVE : BANNER; + let type = BANNER; + if (bid.nativeParams) { + type = NATIVE; + } else if (isVideoRequest(bid)) { + type = VIDEO; + } const bidObject = { requestId: bid.bidId, cpm: bidResponse.price, @@ -176,10 +186,16 @@ export const spec = { }; if (type === NATIVE) { bidObject.native = parseNative(bidResponse); - } else { + } else if (type === BANNER) { bidObject.ad = bidResponse.adm; bidObject.width = bidResponse.w; bidObject.height = bidResponse.h; + } else if (type === VIDEO) { + bidObject.vastXml = bidResponse.adm; + const videoContext = deepAccess(bid, 'mediaTypes.video.context'); + if (videoContext === OUTSTREAM) { + bidObject.renderer = createRenderer(bid); + } } bidObject.meta = {}; if (bidResponse.adomain && bidResponse.adomain.length > 0) { @@ -304,6 +320,27 @@ function getNativeAssets(bid) { }).filter(Boolean); } +function getVideoAsset(bid) { + const sizes = flatten(bid.mediaTypes.video.playerSize); + return { + w: parseInt(sizes[0], 10), + h: parseInt(sizes[1], 10), + protocols: bid.mediaTypes.video.protocols, + playbackmethod: bid.mediaTypes.video.playbackmethod, + mimes: bid.mediaTypes.video.mimes, + skip: bid.mediaTypes.video.skip, + delivery: bid.mediaTypes.video.delivery, + api: bid.mediaTypes.video.api, + minbitrate: bid.mediaTypes.video.minbitrate, + maxbitrate: bid.mediaTypes.video.maxbitrate, + minduration: bid.mediaTypes.video.minduration, + maxduration: bid.mediaTypes.video.maxduration, + startdelay: bid.mediaTypes.video.startdelay, + placement: bid.mediaTypes.video.placement, + linearity: bid.mediaTypes.video.linearity + }; +} + /* Turn bid request sizes into ut-compatible format */ function transformSizes(requestSizes) { if (!isArray(requestSizes)) { @@ -338,3 +375,63 @@ function _getFloor(bid, type) { } return null; } + +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} + +function createRenderer(bid) { + let config = {}; + let playerUrl = OUTSTREAM_RENDERER_URL; + let render = function (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: bid.sizes, + targetId: bid.adUnitCode, + adResponse: { content: bid.vastXml } + }); + }); + }; + + let externalRenderer = deepAccess(bid, 'mediaTypes.video.renderer'); + if (!externalRenderer) { + externalRenderer = deepAccess(bid, 'renderer'); + } + + if (externalRenderer) { + config = externalRenderer.options; + playerUrl = externalRenderer.url; + render = externalRenderer.render; + } + + const renderer = Renderer.install({ + id: bid.adUnitCode, + url: playerUrl, + config: config, + adUnitCode: bid.adUnitCode, + loaded: false + }); + try { + renderer.setRender(render); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function isValidVideoRequest(bid) { + const videoAdUnit = deepAccess(bid, 'mediaTypes.video') + if (!videoAdUnit) { + return false; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (videoAdUnit.context == '') { + return false; + } + + return true; +} diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index 5d7bebc1de1..f5ce00ed8df 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -1,7 +1,7 @@ -import {expect} from 'chai'; -import {spec} from 'modules/outbrainBidAdapter.js'; -import {config} from 'src/config.js'; -import {server} from 'test/mocks/xhr'; +import { expect } from 'chai'; +import { spec } from 'modules/outbrainBidAdapter.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr'; import { createEidsArray } from 'modules/userId/eids.js'; describe('Outbrain Adapter', function () { @@ -45,6 +45,26 @@ describe('Outbrain Adapter', function () { ] } + const videoBidRequestParams = { + mediaTypes: { + video: { + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + } + describe('isBidRequestValid', function () { before(() => { config.setConfig({ @@ -93,6 +113,34 @@ describe('Outbrain Adapter', function () { } expect(spec.isBidRequestValid(bid)).to.equal(true) }) + it('should succeed when bid contains video', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...videoBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail when bid contains insufficient video information', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) it('should fail if publisher id is not set', function () { const bid = { bidder: 'outbrain', @@ -298,6 +346,61 @@ describe('Outbrain Adapter', function () { expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) + it('should build video request', function () { + const bidRequest = { + ...commonBidRequest, + ...videoBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + video: { + w: 640, + h: 480, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + mimes: ['video/mp4'], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + ], + ext: { + prebid: { + channel: { + name: 'pbjs', version: '$prebid.version$' + } + } + } + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + it('should pass optional parameters in request', function () { const bidRequest = { ...commonBidRequest, @@ -390,7 +493,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - config.setConfig({coppa: true}) + config.setConfig({ coppa: true }) const res = spec.buildRequests([bidRequest], commonBidderRequest) const resData = JSON.parse(res.data) @@ -412,7 +515,7 @@ describe('Outbrain Adapter', function () { let res = spec.buildRequests([bidRequest], commonBidderRequest); const resData = JSON.parse(res.data) expect(resData.user.ext.eids).to.deep.equal([ - {source: 'liveramp.com', uids: [{id: 'id-value', atype: 3}]} + { source: 'liveramp.com', uids: [{ id: 'id-value', atype: 3 }] } ]); }); @@ -421,7 +524,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - bidRequest.getFloor = function() { + bidRequest.getFloor = function () { return { currency: 'USD', floor: 1.23, @@ -619,6 +722,67 @@ describe('Outbrain Adapter', function () { const res = spec.interpretResponse(serverResponse, request) expect(res).to.deep.equal(expectedRes) }); + + it('should interpret video response', function () { + const serverResponse = { + body: { + id: '123', + seatbid: [ + { + bid: [ + { + id: '111', + impid: '1', + price: 1.1, + adm: '\u003cVAST version="3.0"\u003e\u003cAd\u003e\u003cInLine\u003e\u003cAdSystem\u003ezemanta\u003c/AdSystem\u003e\u003cAdTitle\u003e1\u003c/AdTitle\u003e\u003cImpression\u003ehttp://win.com\u003c/Impression\u003e\u003cImpression\u003ehttp://example.com/imptracker\u003c/Impression\u003e\u003cCreatives\u003e\u003cCreative\u003e\u003cLinear\u003e\u003cDuration\u003e00:00:25\u003c/Duration\u003e\u003cTrackingEvents\u003e\u003cTracking event="start"\u003ehttp://example.com/start\u003c/Tracking\u003e\u003cTracking event="progress" offset="00:00:03"\u003ehttp://example.com/p3s\u003c/Tracking\u003e\u003c/TrackingEvents\u003e\u003cVideoClicks\u003e\u003cClickThrough\u003ehttp://link.com\u003c/ClickThrough\u003e\u003c/VideoClicks\u003e\u003cMediaFiles\u003e\u003cMediaFile delivery="progressive" type="video/mp4" bitrate="700" width="640" height="360"\u003ehttps://example.com/123_360p.mp4\u003c/MediaFile\u003e\u003c/MediaFiles\u003e\u003c/Linear\u003e\u003c/Creative\u003e\u003c/Creatives\u003e\u003c/InLine\u003e\u003c/Ad\u003e\u003c/VAST\u003e', + adid: '100', + cid: '5', + crid: '29998660', + cat: ['cat-1'], + adomain: [ + 'example.com' + ], + nurl: 'http://example.com/win/${AUCTION_PRICE}' + } + ], + seat: '100', + group: 1 + } + ], + bidid: '456', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...videoBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'video', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + vastXml: 'zemanta1http://win.comhttp://example.com/imptracker00:00:25http://example.com/starthttp://example.com/p3shttp://link.comhttps://example.com/123_360p.mp4', + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); }) }) @@ -637,41 +801,41 @@ describe('Outbrain Adapter', function () { }) it('should return user sync if pixel enabled with outbrain config', function () { - const ret = spec.getUserSyncs({pixelEnabled: true}) - expect(ret).to.deep.equal([{type: 'image', url: usersyncUrl}]) + const ret = spec.getUserSyncs({ pixelEnabled: true }) + expect(ret).to.deep.equal([{ type: 'image', url: usersyncUrl }]) }) it('should not return user sync if pixel disabled', function () { - const ret = spec.getUserSyncs({pixelEnabled: false}) + const ret = spec.getUserSyncs({ pixelEnabled: false }) expect(ret).to.be.an('array').that.is.empty }) it('should not return user sync if url is not set', function () { config.resetConfig() - const ret = spec.getUserSyncs({pixelEnabled: true}) + const ret = spec.getUserSyncs({ pixelEnabled: true }) expect(ret).to.be.an('array').that.is.empty }) - it('should pass GDPR consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + it('should pass GDPR consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` }]); }); - it('should pass US consent', function() { + it('should pass US consent', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?us_privacy=1NYN` }]); }); - it('should pass GDPR and US consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + it('should pass GDPR and US consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` }]); }); From 9a83c0307f7d18e5f6a495c4681e098909eb9d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= <88041828+smart-adserver@users.noreply.github.com> Date: Thu, 26 Jan 2023 21:04:47 +0100 Subject: [PATCH 039/375] 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 --- modules/smartadserverBidAdapter.js | 48 ++++----- .../modules/smartadserverBidAdapter_spec.js | 97 ++++++++++++++++++- 2 files changed, 116 insertions(+), 29 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 6ff0e592542..719d621b056 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -13,6 +13,7 @@ export const spec = { gvlid: GVL_ID, aliases: ['smart'], // short code supportedMediaTypes: [BANNER, VIDEO], + /** * Determines whether or not the given bid request is valid. * @@ -131,7 +132,6 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { // use bidderRequest.bids[] to get bidder-dependent request info - const adServerCurrency = config.getConfig('currency.adServerCurrency'); const sellerDefinedAudience = deepAccess(bidderRequest, 'ortb2.user.data', config.getAnyConfig('ortb2.user.data')); const sellerDefinedContext = deepAccess(bidderRequest, 'ortb2.site.content.data', config.getAnyConfig('ortb2.site.content.data')); @@ -144,7 +144,6 @@ export const spec = { pageid: bid.params.pageId, formatid: bid.params.formatId, currencyCode: adServerCurrency, - bidfloor: bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency), targeting: bid.params.target && bid.params.target !== '' ? bid.params.target : undefined, buid: bid.params.buId && bid.params.buId !== '' ? bid.params.buId : undefined, appname: bid.params.appName && bid.params.appName !== '' ? bid.params.appName : undefined, @@ -175,24 +174,28 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); const bannerMediaType = deepAccess(bid, 'mediaTypes.banner'); - const isAdUnitContainingVideo = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); - if (!isAdUnitContainingVideo && bannerMediaType) { - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && !bannerMediaType) { - spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && bannerMediaType) { - // If there are video and banner media types in the ad unit, we clone the payload - // to create a specific one for video. - let videoPayload = deepClone(payload); + const videoMediaType = deepAccess(bid, 'mediaTypes.video'); + const isSupportedVideoContext = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); - spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + if (bannerMediaType || isSupportedVideoContext) { + let type; + if (bannerMediaType) { + type = BANNER; + payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); + if (isSupportedVideoContext) { + let videoPayload = deepClone(payload); + spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); + videoPayload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, VIDEO); + bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + } + } else { + type = VIDEO; + spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); + } + + payload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, type); bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); } else { bidRequests.push({}); @@ -253,24 +256,21 @@ export const spec = { * * @param {object} bid Bid request object * @param {string} currency Ad server currency + * @param {string} mediaType Bid media type * @return {number} Floor price */ - getBidFloor: function (bid, currency) { + getBidFloor: function (bid, currency, mediaType) { if (!isFn(bid.getFloor)) { return DEFAULT_FLOOR; } const floor = bid.getFloor({ currency: currency || 'USD', - mediaType: '*', + mediaType, size: '*' }); - if (isPlainObject(floor) && !isNaN(floor.floor)) { - return floor.floor; - } - - return DEFAULT_FLOOR; + return isPlainObject(floor) && !isNaN(floor.floor) ? floor.floor : DEFAULT_FLOOR; }, /** diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index db61983c9c9..4dacb356894 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; import { spec } from 'modules/smartadserverBidAdapter.js'; @@ -394,7 +395,6 @@ describe('Smart bid adapter tests', function () { afterEach(function () { config.setConfig({ ortb2: undefined }); config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with GDPR', function () { @@ -446,7 +446,6 @@ describe('Smart bid adapter tests', function () { describe('ccpa/us privacy tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with us privacy', function () { @@ -475,7 +474,6 @@ describe('Smart bid adapter tests', function () { describe('Instream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const INSTREAM_DEFAULT_PARAMS = [{ @@ -746,7 +744,6 @@ describe('Smart bid adapter tests', function () { describe('Outstream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const OUTSTREAM_DEFAULT_PARAMS = [{ @@ -1055,6 +1052,17 @@ describe('Smart bid adapter tests', function () { }); describe('Floors module', function () { + const getFloor = (bid) => { + switch (bid.mediaType) { + case BANNER: + return { currency: 'USD', floor: 1.93 }; + case VIDEO: + return { currency: 'USD', floor: 2.72 }; + default: + return {}; + } + }; + it('should include floor from bid params', function() { const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS))[0].data); expect(bidRequest.bidfloor).to.deep.equal(DEFAULT_PARAMS[0].params.bidfloor); @@ -1094,12 +1102,91 @@ describe('Smart bid adapter tests', function () { const floor = spec.getBidFloor(bidRequest, null); expect(floor).to.deep.equal(0); }); + + it('should take floor from bidder params over ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73, bidfloor: 1.25 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.25); + }); + + it('should take floor from banner ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.93); + }); + + it('should take floor from video ad unit', function() { + const bidRequest = [{ + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(2.72); + }); + + it('should take floor from multiple media type ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 600]] + }, + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const requests = spec.buildRequests(bidRequest); + expect(requests).to.have.lengthOf(2); + + const requestContents = requests.map(r => JSON.parse(r.data)); + const videoRequest = requestContents.filter(r => r.videoData)[0]; + expect(videoRequest).to.not.equal(null).and.to.not.be.undefined; + expect(videoRequest).to.have.property('bidfloor').and.to.equal(2.72); + + const bannerRequest = requestContents.filter(r => !r.videoData)[0]; + expect(bannerRequest).to.not.equal(null).and.to.not.be.undefined; + expect(bannerRequest).to.have.property('bidfloor').and.to.equal(1.93); + }); }); describe('Verify bid requests with multiple mediaTypes', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); var DEFAULT_PARAMS_MULTIPLE_MEDIA_TYPES = [{ From f520803a19f0c62aacb82847e757d52d65250345 Mon Sep 17 00:00:00 2001 From: preved-medved Date: Fri, 27 Jan 2023 11:24:35 +0000 Subject: [PATCH 040/375] 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 --- modules/smartytechBidAdapter.js | 77 +++++++- modules/smartytechBidAdapter.md | 77 ++++---- .../spec/modules/smartytechBidAdapter_spec.js | 186 ++++++++++++++++-- 3 files changed, 283 insertions(+), 57 deletions(-) diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index 231ca315de8..9f275a761c7 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -1,29 +1,77 @@ +import {buildUrl, deepAccess} from '../src/utils.js' +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {buildUrl} from '../src/utils.js' const BIDDER_CODE = 'smartytech'; export const ENDPOINT_PROTOCOL = 'https'; export const ENDPOINT_DOMAIN = 'server.smartytech.io'; -export const ENDPOINT_PATH = '/hb/bidder'; +export const ENDPOINT_PATH = '/hb/v2/bidder'; export const spec = { + supportedMediaTypes: [ BANNER, VIDEO ], code: BIDDER_CODE, isBidRequestValid: function (bidRequest) { - return !!parseInt(bidRequest.params.endpointId); + return ( + !!parseInt(bidRequest.params.endpointId) && + spec._validateBanner(bidRequest) && + spec._validateVideo(bidRequest) + ); + }, + + _validateBanner: function(bidRequest) { + const bannerAdUnit = deepAccess(bidRequest, 'mediaTypes.banner'); + + if (bannerAdUnit === undefined) { + return true; + } + + if (!Array.isArray(bannerAdUnit.sizes)) { + return false; + } + + return true; + }, + + _validateVideo: function(bidRequest) { + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video'); + + if (videoAdUnit === undefined) { + return true; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (!videoAdUnit.context) { + return false; + } + + return true; }, buildRequests: function (validBidRequests, bidderRequest) { const referer = bidderRequest?.refererInfo?.page || window.location.href; const bidRequests = validBidRequests.map((validBidRequest) => { - return { + let video = deepAccess(validBidRequest, 'mediaTypes.video', false); + let banner = deepAccess(validBidRequest, 'mediaTypes.banner', false); + + let oneRequest = { endpointId: validBidRequest.params.endpointId, adUnitCode: validBidRequest.adUnitCode, - sizes: validBidRequest.sizes, - bidId: validBidRequest.bidId, - referer: referer + referer: referer, + bidId: validBidRequest.bidId }; + + if (video) { + oneRequest.video = video; + } else if (banner) { + oneRequest.banner = banner; + } + + return oneRequest }); let adPartnerRequestUrl = buildUrl({ @@ -55,12 +103,14 @@ export const spec = { bid: validBids.find(b => b.adUnitCode === key), response: responseBody[key] } - }).map(item => spec.adResponse(item.bid.bidId, item.response)); + }).map(item => spec._adResponse(item.bid, item.response)); }, - adResponse: function (requestId, response) { + _adResponse: function (request, response) { const bidObject = { - requestId, + requestId: request.bidId, + adUnitCode: request.adUnitCode, + bidderCode: BIDDER_CODE, ad: response.ad, cpm: response.cpm, width: response.width, @@ -69,7 +119,14 @@ export const spec = { creativeId: response.creativeId, netRevenue: true, currency: response.currency, + mediaType: BANNER } + + if (response.mediaType === VIDEO) { + bidObject.vastXml = response.ad; + bidObject.mediaType = VIDEO; + } + return bidObject; }, diff --git a/modules/smartytechBidAdapter.md b/modules/smartytechBidAdapter.md index dbfc2833c78..9df57ddbde7 100644 --- a/modules/smartytechBidAdapter.md +++ b/modules/smartytechBidAdapter.md @@ -1,44 +1,55 @@ # Overview -Module Name: SmartyTech Bidder Adapter - -Module Type: Bidder Adapter - +``` +Module Name: SmartyTech Bid Adapter +Module Type: Bidder Adapter Maintainer: info@adpartner.pro +``` # Description -You can use this adapter to get a bid from smartytech.io. +Connects to SmartyTech's exchange for bids. -About us : https://smartytech.io +SmartyTech bid adapter supports Banner and Video -# Test Parameters +# Sample Ad Unit: For Publishers +## Sample Banner Ad Unit +``` +var adUnits = [{ + code: '/123123123/prebidjs-banner', + mediaTypes: { + banner: { + sizes: [ + [300, 301], + [300, 250] + ] + } + }, + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}]; +``` -```javascript - var adUnits = [ - { - code: 'div-smartytech-example', - sizes: [[300, 250]], - bids: [ - { - bidder: "smartytech", - params: { - endpointId: 14 - } - } - ] +## Sample Video Ad Unit +``` +var videoAdUnit = { + code: '/123123123/video-vast-banner', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + } }, - { - code: 'div-smartytech-example-2', - sizes: [[300, 250]], - bids: [ - { - bidder: "smartytech", - params: { - endpointId: 14 - } - } - ] - } -]; + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}; ``` diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index 78503d7a6f0..b41b0280235 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -19,20 +19,116 @@ describe('SmartyTechDSPAdapter: inherited functions', function () { describe('SmartyTechDSPAdapter: isBidRequestValid', function () { it('Invalid bid request. Should return false', function () { - const invalidBidFixture = { + const bidFixture = { params: { use_id: 13144375 } } - expect(spec.isBidRequestValid(invalidBidFixture)).to.be.false + + expect(spec.isBidRequestValid(bidFixture)).to.be.false }); it('Valid bid request. Should return true', function () { - const validBidFixture = { + const bidFixture = { params: { endpointId: 13144375 } } - expect(spec.isBidRequestValid(validBidFixture)).to.be.true + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check video block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check playerSize', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check context', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid video bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'instream' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check banner block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check banner sizes', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid banner bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true }); }); @@ -42,12 +138,29 @@ function mockRandomSizeArray(len) { }); } -function mockBidRequestListData(size) { +function mockBidRequestListData(mediaType, size) { return Array.apply(null, {length: size}).map((i, index) => { const id = Math.floor(Math.random() * 800) * (index + 1); + let mediaTypes; + + if (mediaType == 'video') { + mediaTypes = { + video: { + playerSize: mockRandomSizeArray(1), + context: 'instream' + }, + } + } else { + mediaTypes = { + banner: { + sizes: mockRandomSizeArray(index + 1) + } + } + } + return { adUnitCode: `adUnitCode-${id}`, - sizes: mockRandomSizeArray(index + 1), + mediaTypes: mediaTypes, bidId: `bidId-${id}`, params: { endpointId: id @@ -66,18 +179,27 @@ function mockRefererData() { function mockResponseData(requestData) { let data = {} - requestData.data.forEach((request, index) => { - const sizeArrayIndex = Math.floor(Math.random() * (request.sizes.length - 1)); const rndIndex = Math.floor(Math.random() * 800); + let width, height, mediaType; + if (request.video !== undefined) { + width = request.video.playerSize[0][0]; + height = request.video.playerSize[0][1]; + mediaType = 'video'; + } else { + width = request.banner.sizes[0][0]; + height = request.banner.sizes[0][1]; + mediaType = 'banner'; + } data[request.adUnitCode] = { ad: `ad-${rndIndex}`, - width: request.sizes[sizeArrayIndex][0], - height: request.sizes[sizeArrayIndex][1], + width: width, + height: height, creativeId: `creative-id-${index}`, cpm: Math.floor(Math.random() * 100), - currency: `UAH-${rndIndex}` + currency: `UAH-${rndIndex}`, + mediaType: mediaType }; }); return { @@ -89,7 +211,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { let mockBidRequest; let mockReferer; beforeEach(() => { - mockBidRequest = mockBidRequestListData(8); + mockBidRequest = mockBidRequestListData('banner', 8); mockReferer = mockRefererData(); }); it('has return data', () => { @@ -108,7 +230,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { const data = spec.buildRequests(mockBidRequest, mockReferer).data; data.forEach((request, index) => { expect(request.adUnitCode).to.be.equal(mockBidRequest[index].adUnitCode); - expect(request.sizes).to.be.equal(mockBidRequest[index].sizes); + expect(request.banner).to.be.equal(mockBidRequest[index].mediaTypes.banner); expect(request.bidId).to.be.equal(mockBidRequest[index].bidId); expect(request.endpointId).to.be.equal(mockBidRequest[index].params.endpointId); expect(request.referer).to.be.equal(mockReferer.refererInfo.page); @@ -122,7 +244,7 @@ describe('SmartyTechDSPAdapter: interpretResponse', () => { let request; let mockResponse; beforeEach(() => { - const brData = mockBidRequestListData(2); + const brData = mockBidRequestListData('banner', 2); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { @@ -157,6 +279,42 @@ describe('SmartyTechDSPAdapter: interpretResponse', () => { expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + }); + }); +}); + +describe('SmartyTechDSPAdapter: interpretResponse video', () => { + let mockBidRequest; + let mockReferer; + let request; + let mockResponse; + beforeEach(() => { + const brData = mockBidRequestListData('video', 2); + mockReferer = mockRefererData(); + request = spec.buildRequests(brData, mockReferer); + mockBidRequest = { + data: brData + } + mockResponse = mockResponseData(request); + }); + + it('interpretResponse: convert to correct data', () => { + const keys = Object.keys(mockResponse.body); + const data = spec.interpretResponse(mockResponse, mockBidRequest); + + data.forEach((responseItem, index) => { + expect(responseItem.ad).to.be.equal(mockResponse.body[keys[index]].ad); + expect(responseItem.cpm).to.be.equal(mockResponse.body[keys[index]].cpm); + expect(responseItem.creativeId).to.be.equal(mockResponse.body[keys[index]].creativeId); + expect(responseItem.currency).to.be.equal(mockResponse.body[keys[index]].currency); + expect(responseItem.netRevenue).to.be.true; + expect(responseItem.ttl).to.be.equal(60); + expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); + expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); + expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + expect(responseItem.vastXml).to.be.equal(mockResponse.body[keys[index]].ad); }); }); }); From a0e514cf9ad882f9f8bc1675d9f88a0a638e33c3 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 27 Jan 2023 06:56:19 -0700 Subject: [PATCH 041/375] 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 --- modules/priceFloors.js | 15 ++++---- src/auction.js | 13 +------ src/utils/cpm.js | 17 +++++++++ test/spec/unit/utils/cpm_spec.js | 64 ++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 src/utils/cpm.js create mode 100644 test/spec/unit/utils/cpm_spec.js diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 32b3cbaa607..92aecb0ca50 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -28,6 +28,7 @@ import {auctionManager} from '../src/auctionManager.js'; import {IMP, PBS, registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js'; import {beConvertCurrency} from '../src/utils/currency.js'; +import {adjustCpm} from '../src/utils/cpm.js'; /** * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. @@ -179,12 +180,8 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) { /** * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted */ -export function getBiddersCpmAdjustment(bidderName, inputCpm, bid, bidRequest) { - const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment'); - if (adjustmentFunction) { - return parseFloat(adjustmentFunction(inputCpm, { ...bid, cpm: inputCpm }, bidRequest)); - } - return parseFloat(inputCpm); +export function getBiddersCpmAdjustment(inputCpm, bid, bidRequest) { + return parseFloat(adjustCpm(inputCpm, {...bid, cpm: inputCpm}, bidRequest)); } /** @@ -254,7 +251,7 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: if (inverseFunction) { floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest); } else { - let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor, {}, bidRequest); + let cpmAdjustment = getBiddersCpmAdjustment(floorInfo.matchingFloor, null, bidRequest); floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; } } @@ -313,6 +310,8 @@ export function getFloorDataFromAdUnits(adUnits) { // copy over the new rules into our values object Object.assign(accum.values, newRules); } + } else if (adUnit.floors != null) { + logWarn(`adUnit '${adUnit.code}' provides an invalid \`floor\` definition, it will be ignored for floor calculations`, adUnit); } return accum; }, {}); @@ -737,7 +736,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a } // ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists - adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid, matchingBidRequest); + adjustedCpm = getBiddersCpmAdjustment(adjustedCpm, bid, matchingBidRequest); // add necessary data information for analytics adapters / floor providers would possibly need addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm); diff --git a/src/auction.js b/src/auction.js index 87397b0dc15..41e6fe3565b 100644 --- a/src/auction.js +++ b/src/auction.js @@ -93,6 +93,7 @@ import CONSTANTS from './constants.json'; import {GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {createBid} from './bidfactory.js'; +import {adjustCpm} from './utils/cpm.js'; const { syncUsers } = userSync; @@ -971,17 +972,7 @@ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { } export function adjustBids(bid) { - let code = bid.bidderCode; - let bidPriceAdjusted = bid.cpm; - const bidCpmAdjustment = bidderSettings.get(code || null, 'bidCpmAdjustment'); - - if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') { - try { - bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid)); - } catch (e) { - logError('Error during bid adjustment', 'bidmanager.js', e); - } - } + let bidPriceAdjusted = adjustCpm(bid.cpm, bid); if (bidPriceAdjusted >= 0) { bid.cpm = bidPriceAdjusted; diff --git a/src/utils/cpm.js b/src/utils/cpm.js new file mode 100644 index 00000000000..07113e7c944 --- /dev/null +++ b/src/utils/cpm.js @@ -0,0 +1,17 @@ +import {auctionManager} from '../auctionManager.js'; +import {bidderSettings} from '../bidderSettings.js'; +import {logError} from '../utils.js'; + +export function adjustCpm(cpm, bidResponse, bidRequest, {index = auctionManager.index, bs = bidderSettings} = {}) { + bidRequest = bidRequest || index.getBidRequest(bidResponse); + const bidCpmAdjustment = bs.get(bidResponse?.bidderCode || bidRequest?.bidder, 'bidCpmAdjustment'); + + if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') { + try { + return bidCpmAdjustment(cpm, Object.assign({}, bidResponse), bidRequest); + } catch (e) { + logError('Error during bid adjustment', e); + } + } + return cpm; +} diff --git a/test/spec/unit/utils/cpm_spec.js b/test/spec/unit/utils/cpm_spec.js new file mode 100644 index 00000000000..9d104b04d09 --- /dev/null +++ b/test/spec/unit/utils/cpm_spec.js @@ -0,0 +1,64 @@ +import {adjustCpm} from '../../../../src/utils/cpm.js'; + +describe('adjustCpm', () => { + const bidderCode = 'mockBidder'; + let adjustmentFn, bs, index; + beforeEach(() => { + bs = { + get: sinon.stub() + } + index = { + getBidRequest: sinon.stub() + } + adjustmentFn = sinon.stub().callsFake((cpm) => cpm * 2); + }) + + it('throws when neither bidRequest nor bidResponse are provided', () => { + expect(() => adjustCpm(1)).to.throw(); + }) + + it('always provides an object as bidResponse for the adjustment fn', () => { + bs.get.callsFake(() => adjustmentFn); + adjustCpm(1, null, {bidder: bidderCode}, {index, bs}); + sinon.assert.calledWith(adjustmentFn, 1, {}); + }); + + describe('when no bidRequest is provided', () => { + Object.entries({ + 'unavailable': undefined, + 'found': {foo: 'bar'} + }).forEach(([t, req]) => { + describe(`and it is ${t} in the index`, () => { + beforeEach(() => { + bs.get.callsFake(() => adjustmentFn); + index.getBidRequest.callsFake(() => req) + }); + + it('provides it to the adjustment fn', () => { + const bidResponse = {bidderCode}; + adjustCpm(1, bidResponse, undefined, {index, bs}); + sinon.assert.calledWith(index.getBidRequest, bidResponse); + sinon.assert.calledWith(adjustmentFn, 1, bidResponse, req); + }) + }) + }) + }); + + Object.entries({ + 'bidResponse': [{bidderCode}], + 'bidRequest': [null, {bidder: bidderCode}], + }).forEach(([t, [bidResp, bidReq]]) => { + describe(`when passed ${t}`, () => { + beforeEach(() => { + bs.get.callsFake((bidder) => { if (bidder === bidderCode) return adjustmentFn }); + }); + it('retrieves the correct bidder code', () => { + expect(adjustCpm(1, bidResp, bidReq, {bs, index})).to.eql(2); + }); + it('passes them to the adjustment fn', () => { + adjustCpm(1, bidResp, bidReq, {bs, index}); + sinon.assert.calledWith(adjustmentFn, 1, bidResp == null ? sinon.match.any : bidResp, bidReq); + }); + }); + }) +}); From d56aaba3edff7094072416638d2c0fa039102614 Mon Sep 17 00:00:00 2001 From: Paulius Imbrasas <880130+CremboC@users.noreply.github.com> Date: Fri, 27 Jan 2023 21:53:17 +0000 Subject: [PATCH 042/375] Fixes potential error when reading _pssps localStorage key (#9474) --- modules/permutiveRtdProvider.js | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index c64080f3308..305e175bf2c 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -311,16 +311,19 @@ export function isPermutiveOnPage () { * @return {Object} */ export function getSegments (maxSegs) { - const legacySegs = readSegments('_psegs').map(Number).filter(seg => seg >= 1000000).map(String) - const _ppam = readSegments('_ppam') - const _pcrprs = readSegments('_pcrprs') + const legacySegs = readSegments('_psegs', []).map(Number).filter(seg => seg >= 1000000).map(String) + const _ppam = readSegments('_ppam', []) + const _pcrprs = readSegments('_pcrprs', []) const segments = { ac: [..._pcrprs, ..._ppam, ...legacySegs], - rubicon: readSegments('_prubicons'), - appnexus: readSegments('_papns'), - gam: readSegments('_pdfps'), - ssp: readSegments('_pssps'), + rubicon: readSegments('_prubicons', []), + appnexus: readSegments('_papns', []), + gam: readSegments('_pdfps', []), + ssp: readSegments('_pssps', { + cohorts: [], + ssps: [] + }), } for (const bidder in segments) { @@ -338,15 +341,17 @@ export function getSegments (maxSegs) { /** * Gets an array of segment IDs from LocalStorage - * or returns an empty array + * or return the default value provided. + * @template A * @param {string} key - * @return {string[]|number[]} + * @param {A} defaultValue + * @return {A} */ -function readSegments (key) { +function readSegments (key, defaultValue) { try { - return JSON.parse(storage.getDataFromLocalStorage(key) || '[]') + return JSON.parse(storage.getDataFromLocalStorage(key)) || defaultValue } catch (e) { - return [] + return defaultValue } } From 2c9fc250ffb2f24c77e415a8ea80d68cdd77fa37 Mon Sep 17 00:00:00 2001 From: rishko00 <43280707+rishko00@users.noreply.github.com> Date: Mon, 30 Jan 2023 12:07:39 +0200 Subject: [PATCH 043/375] SmartyadsBidAdapter: update request params (#9472) Co-authored-by: Vasyl Rishko --- modules/smartyadsBidAdapter.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md index f078d905e62..e0d6023a794 100644 --- a/modules/smartyadsBidAdapter.md +++ b/modules/smartyadsBidAdapter.md @@ -10,6 +10,15 @@ Maintainer: supply@smartyads.com Module that connects to SmartyAds' demand sources +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :------------------------ | :------------------- | +| `sourceid` | required (for prebid.js) | placement ID | "0" | +| `host` | required (for prebid-server) | const value, set to "prebid" | "prebid" | +| `accountid` | required (for prebid-server) | partner ID | "1901" | +| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native or video | "banner" | + # Test Parameters ``` var adUnits = [ From aa033bdf1f5092e0083303b7cd36da94bebe9fb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 07:38:24 -0700 Subject: [PATCH 044/375] 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> --- .github/workflows/issue_tracker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml index 29082a4990a..69cf4c5fc7f 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@021a2405c7f990db57f5eae5397423dcc554159c + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: app_id: ${{ secrets.ISSUE_APP_ID }} private_key: ${{ secrets.ISSUE_APP_PEM }} From b1de163ef0171ae2f72f2791ced3a1312601d8d4 Mon Sep 17 00:00:00 2001 From: Espen <2290914+espen-j@users.noreply.github.com> Date: Tue, 31 Jan 2023 10:21:05 +0100 Subject: [PATCH 045/375] 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) --- modules/cwireBidAdapter.js | 411 +++++++-------- modules/cwireBidAdapter.md | 29 +- test/spec/modules/cwireBidAdapter_spec.js | 584 +++++++++------------- 3 files changed, 433 insertions(+), 591 deletions(-) diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index a11899609bc..604d7235d0f 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -1,307 +1,234 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {OUTSTREAM} from '../src/video.js'; -import { - deepAccess, - generateUUID, - getBidIdParameter, - getParameterByName, - getValue, - isArray, - isNumber, - isStr, - logError, - logWarn, - parseSizesInput, -} from '../src/utils.js'; -import {Renderer} from '../src/Renderer.js'; -import {find} from '../src/polyfill.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {generateUUID, getParameterByName, isNumber, logError, logInfo} from '../src/utils.js'; // ------------------------------------ const BIDDER_CODE = 'cwire'; -export const ENDPOINT_URL = 'https://embed.cwi.re/delivery/prebid'; -export const RENDERER_URL = 'https://cdn.cwi.re/prebid/renderer/LATEST/renderer.min.js'; -// ------------------------------------ -export const CW_PAGE_VIEW_ID = generateUUID(); -const LS_CWID_KEY = 'cw_cwid'; -const CW_GROUPS_QUERY = 'cwgroups'; -const CW_CREATIVE_QUERY = 'cwcreative'; +const CWID_KEY = 'cw_cwid'; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const BID_ENDPOINT = 'https://prebid.cwi.re/v1/bid'; +export const EVENT_ENDPOINT = 'https://prebid.cwi.re/v1/event'; /** - * ------------------------------------ - * ------------------------------------ - * @param bid - * @returns {Array} + * Allows limiting ad impressions per site render. Unique per prebid instance ID. */ -export function getSlotSizes(bid) { - return parseSizesInput(getAllMediaSizes(bid)); -} +export const pageViewId = generateUUID(); + +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** - * ------------------------------------ - * ------------------------------------ + * Retrieve dimensions and CSS max height/width from a given slot and attach the properties to the bidRequest. * @param bid - * @returns {*[]} + * @returns {*&{cwExt: {dimensions: {width: number, height: number}, style: {maxWidth: number, maxHeight: number}}}} */ -export function getAllMediaSizes(bid) { - let playerSizes = deepAccess(bid, 'mediaTypes.video.playerSize'); - let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes'); - let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); - - const sizes = []; - - if (isArray(playerSizes)) { - playerSizes.forEach((s) => { - sizes.push(s); - }) - } - - if (isArray(videoSizes)) { - videoSizes.forEach((s) => { - sizes.push(s); - }) +function slotDimensions(bid) { + let adUnitCode = bid.adUnitCode; + let slotEl = document.getElementById(adUnitCode); + + if (slotEl) { + logInfo(`Slot element found: ${adUnitCode}`) + const slotW = slotEl.offsetWidth + const slotH = slotEl.offsetHeight + const cssMaxW = slotEl.style?.maxWidth; + const cssMaxH = slotEl.style?.maxHeight; + logInfo(`Slot dimensions (w/h): ${slotW} / ${slotH}`) + logInfo(`Slot Styles (maxW/maxH): ${cssMaxW} / ${cssMaxH}`) + + bid = { + ...bid, + cwExt: { + dimensions: { + width: slotW, + height: slotH, + }, + style: { + ...(cssMaxW) && { + maxWidth: cssMaxW + }, + ...(cssMaxH) && { + maxHeight: cssMaxH + } + } + } + } } + return bid +} - if (isArray(bannerSizes)) { - bannerSizes.forEach((s) => { - sizes.push(s); - }) +/** + * Extracts feature flags from a comma-separated url parameter `cwfeatures`. + * + * @returns *[] + */ +function getFeatureFlags() { + let ffParam = getParameterByName('cwfeatures') + if (ffParam) { + return ffParam.split(',') } - return sizes; + return [] } -const getQueryVariable = (variable) => { - let value = getParameterByName(variable); - if (value === '') { - value = null; +function getRefGroups() { + const groups = getParameterByName('cwgroups') + if (groups) { + return groups.split(',') } - return value; -}; + return [] +} /** - * ------------------------------------ - * ------------------------------------ - * @param validBidRequests - * @returns {*[]} + * Reads the CWID from local storage. */ -export const mapSlotsData = function(validBidRequests) { - const slots = []; - validBidRequests.forEach(bid => { - const bidObj = {}; - // get testing / debug params - let cwcreative = getValue(bid.params, 'cwcreative'); - let refgroups = getValue(bid.params, 'refgroups'); - let cwapikey = getValue(bid.params, 'cwapikey'); +function getCwid() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(CWID_KEY) : null; +} - // get the pacement and page ids - let placementId = getValue(bid.params, 'placementId'); - let pageId = getValue(bid.params, 'pageId'); - // get the rest of the auction/bid/transaction info - bidObj.auctionId = getBidIdParameter('auctionId', bid); - bidObj.adUnitCode = getBidIdParameter('adUnitCode', bid); - bidObj.bidId = getBidIdParameter('bidId', bid); - bidObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); - bidObj.placementId = placementId; - bidObj.pageId = pageId; - bidObj.mediaTypes = getBidIdParameter('mediaTypes', bid); - bidObj.transactionId = getBidIdParameter('transactionId', bid); - bidObj.sizes = getSlotSizes(bid); - bidObj.cwcreative = cwcreative; - bidObj.refgroups = refgroups; - bidObj.cwapikey = cwapikey; - slots.push(bidObj); - }); +function hasCwid() { + return storage.localStorageIsEnabled() && storage.getDataFromLocalStorage(CWID_KEY); +} - return slots; -}; +/** + * Store the CWID to local storage. + */ +function updateCwid(cwid) { + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage(CWID_KEY, cwid) + } else { + logInfo(`Could not set CWID ${cwid} in localstorage`); + } +} + +/** + * Extract and collect cwire specific extensions. + */ +function getCwExtension() { + const cwId = getCwid(); + const cwCreative = getParameterByName('cwcreative') + const cwGroups = getRefGroups() + const cwFeatures = getFeatureFlags(); + // Enable debug flag by passing ?cwdebug=true as url parameter. + // Note: pbjs_debug=true enables it on prebid level + // More info: https://docs.prebid.org/troubleshooting/troubleshooting-guide.html#turn-on-prebidjs-debug-messages + const debug = getParameterByName('cwdebug'); + + return { + ...(cwId) && { + cwid: cwId + }, + ...(cwGroups.length > 0) && { + refgroups: cwGroups + }, + ...(cwFeatures.length > 0) && { + featureFlags: cwFeatures + }, + ...(cwCreative) && { + cwcreative: cwCreative + }, + ...(debug) && { + debug: true + } + }; +} export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER], + /** - * Determines whether or not the given bid request is valid. + * Determines whether the given bid request is valid. * * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function(bid) { - bid.params = bid.params || {}; - - if (bid.params.cwcreative && !isStr(bid.params.cwcreative)) { - logError('cwcreative must be of type string!'); - return false; - } - - if (!bid.params.placementId || !isNumber(bid.params.placementId)) { - logError('placementId not provided or invalid'); + isBidRequestValid: function (bid) { + if (!bid.params?.placementId || !isNumber(bid.params.placementId)) { + logError('placementId not provided or not a number'); return false; } - if (!bid.params.pageId || !isNumber(bid.params.pageId)) { - logError('pageId not provided'); + if (!bid.params?.pageId || !isNumber(bid.params.pageId)) { + logError('pageId not provided or not a number'); return false; } - return true; }, /** - * ------------------------------------ - * itterate trough slots array and try - * to extract first occurence of a given - * key, if not found - return null - * ------------------------------------ - */ - getFirstValueOrNull: function(slots, key) { - const found = slots.find((item) => { - return (typeof item[key] !== 'undefined'); - }); - - return (found) ? found[key] : null; - }, - - /** - * ------------------------------------ - * Make a server request from the - * list of BidRequests. - * ------------------------------------ - * @param {validBidRequests[]} - an array of bids + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} validBidRequests An array of bids. * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests, bidderRequest) { - let slots = []; - let referer; - try { - referer = bidderRequest?.refererInfo?.page; - slots = mapSlotsData(validBidRequests); - } catch (e) { - logWarn(e); - } + buildRequests: function (validBidRequests, bidderRequest) { + // There are more fields on the refererInfo object + let referrer = bidderRequest?.refererInfo?.page - let refgroups = []; - - const cwCreative = getQueryVariable(CW_CREATIVE_QUERY) || null; - const cwCreativeIdFromConfig = this.getFirstValueOrNull(slots, 'cwcreative'); - const refGroupsFromConfig = this.getFirstValueOrNull(slots, 'refgroups'); - const cwApiKeyFromConfig = this.getFirstValueOrNull(slots, 'cwapikey'); - const rgQuery = getQueryVariable(CW_GROUPS_QUERY); - - if (refGroupsFromConfig !== null) { - refgroups = refGroupsFromConfig.split(','); - } - - if (rgQuery !== null) { - // override if query param is present - refgroups = []; - refgroups = rgQuery.split(','); - } - - const localStorageCWID = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(LS_CWID_KEY) : null; + // process bid requests + let processed = validBidRequests + .map(bid => slotDimensions(bid)) + // Flattens the pageId and placement Id for backwards compatibility. + .map((bid) => ({...bid, pageId: bid.params?.pageId, placementId: bid.params?.placementId})); + const extensions = getCwExtension(); const payload = { - cwid: localStorageCWID, - refgroups, - cwcreative: cwCreative || cwCreativeIdFromConfig, - slots: slots, - cwapikey: cwApiKeyFromConfig, - httpRef: referer || '', - pageViewId: CW_PAGE_VIEW_ID, + slots: processed, + httpRef: referrer, + // TODO: Verify whether the auctionId and the usage of pageViewId make sense. + pageViewId: pageViewId, + sdk: { + version: '$prebid.version$' + }, + ...extensions }; - + const payloadString = JSON.stringify(payload); return { method: 'POST', - url: ENDPOINT_URL, - data: payload + url: BID_ENDPOINT, + data: payloadString, }; }, - /** * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, bidRequest) { - const bidResponses = []; - - try { - if (typeof bidRequest.data === 'string') { - bidRequest.data = JSON.parse(bidRequest.data); + interpretResponse: function (serverResponse, bidRequest) { + if (!hasCwid()) { + const cwid = serverResponse.body?.cwid + if (cwid) { + updateCwid(cwid); } - const serverBody = serverResponse.body; - serverBody.bids.forEach((br) => { - const bidReq = find(bidRequest.data.slots, bid => bid.bidId === br.requestId); - - let mediaType = BANNER; - - const bidResponse = { - requestId: br.requestId, - cpm: br.cpm, - bidderCode: BIDDER_CODE, - width: br.dimensions[0], - height: br.dimensions[1], - creativeId: br.creativeId, - currency: br.currency, - netRevenue: br.netRevenue, - ttl: br.ttl, - meta: { - advertiserDomains: br.adomains ? br.advertiserDomains : [], - }, - - }; - - // ------------------------------------ - // IF BANNER - // ------------------------------------ - - if (deepAccess(bidReq, 'mediaTypes.banner')) { - bidResponse.ad = br.html; - } - // ------------------------------------ - // IF VIDEO - // ------------------------------------ - if (deepAccess(bidReq, 'mediaTypes.video')) { - mediaType = VIDEO; - bidResponse.vastXml = br.vastXml; - bidResponse.videoScript = br.html; - const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context'); - if (mediaTypeContext === OUTSTREAM) { - const r = Renderer.install({ - id: bidResponse.requestId, - adUnitCode: bidReq.adUnitCode, - url: RENDERER_URL, - loaded: false, - config: { - ...deepAccess(bidReq, 'mediaTypes.video'), - ...deepAccess(br, 'outstream', {}) - } - }); + } - // set renderer - try { - bidResponse.renderer = r; - bidResponse.renderer.setRender(function(bid) { - if (window.CWIRE && window.CWIRE.outstream) { - window.CWIRE.outstream.renderAd(bid); - } - }); - } catch (err) { - logWarn('Prebid Error calling setRender on newRenderer', err); - } - } - } + // Rename `html` response property to `ad` as used by prebid. + const bids = serverResponse.body?.bids.map(({html, ...rest}) => ({...rest, ad: html})); + return bids || []; + }, - bidResponse.mediaType = mediaType; - bidResponses.push(bidResponse); - }); - } catch (e) { - logWarn(e); + onBidWon: function (bid) { + logInfo(`Bid won.`) + const event = { + type: 'BID_WON', + payload: { + bid: bid + } } + navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) + }, - return bidResponses; + onBidderError: function (error, bidderRequest) { + logInfo(`Bidder error: ${error}`) + const event = { + type: 'BID_ERROR', + payload: { + error: error, + bidderRequest: bidderRequest + } + } + navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) }, + }; registerBidder(spec); diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md index 188894cd202..026c2943c6b 100644 --- a/modules/cwireBidAdapter.md +++ b/modules/cwireBidAdapter.md @@ -1,25 +1,27 @@ # Overview -Module Name: C-WIRE Bid Adapter -Module Type: Adagio Adapter -Maintainer: publishers@cwire.ch +``` +Module Name: C-WIRE Bid Adapter +Module Type: Bidder Adapter +Maintainer: devs@cwire.ch +``` ## Description -Connects to C-WIRE demand source to fetch bids. +Prebid.js Adapter for C-Wire. ## Configuration - Below, the list of C-WIRE params and where they can be set. -| Param name | Global config | AdUnit config | Type | Required | -| ---------- | ------------- | ------------- |--------| ---------| -| pageId | | x | number | YES | -| placementId | | x | number | YES | -| refgroups | | x | string | NO | -| cwcreative | | x | string | NO | -| cwapikey | | x | string | NO | +| Param name | URL parameter | AdUnit config | Type | Required | +|-------------|:-------------:|:-------------:|:--------:|:-------------:| +| pageId | | x | number | YES | +| placementId | | x | number | YES | +| cwgroups | x | | string | NO | +| cwcreative | x | | string | NO | +| cwdebug | x | | boolean | NO | +| cwfeatures | x | | string | NO | ### adUnit configuration @@ -32,7 +34,7 @@ var adUnits = [ bidder: 'cwire', mediaTypes: { banner: { - sizes: [[1, 1]], + sizes: [[400, 600]], } }, params: { @@ -40,7 +42,6 @@ var adUnits = [ placementId: 2211521, // required - number cwcreative: '42', // optional - id of creative to force refgroups: 'test-user', // optional - name of group or coma separated list of groups to force - cwapikey: 'api_key_xyz', // optional - api key for integration testing } }] } diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index f116b184b8c..88c54212aff 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -1,382 +1,296 @@ -import { expect } from 'chai'; -import * as utils from '../../../src/utils.js'; -import { config } from '../../../src/config.js'; -import { - spec, - CW_PAGE_VIEW_ID, - ENDPOINT_URL, - RENDERER_URL, -} from '../../../modules/cwireBidAdapter.js'; -import * as prebidGlobal from 'src/prebidGlobal.js'; - -// ------------------------------------ -// Bid Request Builder -// ------------------------------------ - -const BID_DEFAULTS = { - request: { - bidder: 'cwire', - auctionId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - transactionId: 'txaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - bidId: 'bid123445', - bidderRequestId: 'brid12345', - code: 'original-div', - }, - params: { - placementId: 123456, - pageId: 777, - }, - sizes: [[300, 250], [1, 1]], -}; - -const BidderRequestBuilder = function BidderRequestBuilder(options) { - const defaults = { - bidderCode: 'cwire', - auctionId: BID_DEFAULTS.request.auctionId, - bidderRequestId: BID_DEFAULTS.request.bidderRequestId, - transactionId: BID_DEFAULTS.request.transactionId, - timeout: 3000, - }; - - const request = { - ...defaults, - ...options - }; - - this.build = () => request; -}; - -const BidRequestBuilder = function BidRequestBuilder(options, deleteKeys) { - const defaults = JSON.parse(JSON.stringify(BID_DEFAULTS)); - - const request = { - ...defaults.request, - ...options - }; - - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request[k]; - }) - } - - this.withParams = (options, deleteKeys) => { - request.params = { - ...defaults.params, - ...options - }; - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request.params[k]; - }) - } - return this; - }; - - this.build = () => request; -}; +import {expect} from 'chai'; +import {newBidder} from '../../../src/adapters/bidderFactory'; +import {BID_ENDPOINT, spec, storage} from '../../../modules/cwireBidAdapter'; +import {deepClone, logInfo} from '../../../src/utils'; +import * as utils from 'src/utils.js'; +import {sandbox, stub} from 'sinon'; +import {config} from '../../../src/config'; describe('C-WIRE bid adapter', () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); + config.setConfig({debug: true}); + const adapter = newBidder(spec); + let bidRequests = [ + { + 'bidder': 'cwire', + 'params': { + 'pageId': '4057', + 'placementId': 'ad-slot-bla' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + const response = { + body: { + 'cwid': '2ef90743-7936-4a82-8acf-e73382a64e94', + 'hash': '17112D98BBF55D3A', + 'bids': [{ + 'html': '

Hello world

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

AD CONTENT

', - currency: 'CHF', - cpm: 43.37, - dimensions: [1, 1], - netRevenue: true, - creativeId: '1337', - requestId: BID_DEFAULTS.request.bidId, - ttl: 3500, - }], - } - }; - - const expectedResponse = [{ - ad: JSON.parse(JSON.stringify(serverResponse.body.bids[0].html)), - bidderCode: BID_DEFAULTS.request.bidder, - cpm: JSON.parse(JSON.stringify(serverResponse.body.bids[0].cpm)), - creativeId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].creativeId)), - currency: JSON.parse(JSON.stringify(serverResponse.body.bids[0].currency)), - height: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[0])), - width: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[1])), - netRevenue: JSON.parse(JSON.stringify(serverResponse.body.bids[0].netRevenue)), - requestId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].requestId)), - ttl: JSON.parse(JSON.stringify(serverResponse.body.bids[0].ttl)), - meta: { - advertiserDomains: [], - }, - mediaType: 'banner', - }] - - it('correctly parses response', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams().build(); + describe('buildRequests maps flattens params for legacy compat', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({}); + }); + it('pageId flattened', function () { + let bidRequest = deepClone(bidRequests[0]); - const bidderRequest01 = new BidderRequestBuilder().build(); - const requests = spec.buildRequests([bid01], bidderRequest01); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - const response = spec.interpretResponse(serverResponse, requests); - expect(response).to.deep.equal(expectedResponse); + logInfo(JSON.stringify(payload)) + + expect(payload.slots[0].pageId).to.exist; + }); + after(function () { + sandbox.restore() }); + }) - it('attaches renderer', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'outstream', - } - } - }).withParams().build(); - const bidderRequest01 = new BidderRequestBuilder().build(); + describe('pageId and placementId are required params', function () { + it('invalid request', function () { + let bidRequest = deepClone(bidRequests[0]); + delete bidRequest.params + + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.false; + }) - const _serverResponse = utils.deepClone(serverResponse); - _serverResponse.body.bids[0].vastXml = ''; + it('valid request', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const _expectedResponse = utils.deepClone(expectedResponse); - _expectedResponse[0].mediaType = 'video'; - _expectedResponse[0].videoScript = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].html)); - _expectedResponse[0].vastXml = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].vastXml)); - delete _expectedResponse[0].ad; + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - const requests = spec.buildRequests([bid01], bidderRequest01); - expect(requests.data.slots[0].sizes).to.deep.equal(['640x480']); + it('cwcreative must be of type string', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const response = spec.interpretResponse(_serverResponse, requests); - expect(response[0].renderer).to.exist; - expect(response[0].renderer.url).to.equals(RENDERER_URL); - expect(response[0].renderer.loaded).to.equals(false); + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - delete response[0].renderer; - expect(response).to.deep.equal(_expectedResponse); - }); + it('build request adds pageId', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.slots[0].pageId).to.exist; + }) + }); + + describe('process serverResponse', function () { + it('html to ad mapping', function () { + let bidResponse = deepClone(response); + const bids = spec.interpretResponse(bidResponse, {}); + + expect(bids[0].ad).to.exist; + }) }); }); From 9be4e4d5e7b1306a1ead728bc6a755a8c98d3e76 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Tue, 31 Jan 2023 20:36:35 +0100 Subject: [PATCH 046/375] bidderCode fix (#9485) --- modules/nexx360BidAdapter.js | 3 +-- test/spec/modules/nexx360BidAdapter_spec.js | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index b04bb47543f..6ead44175e9 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -258,14 +258,13 @@ function interpretResponse(response, req) { currency: respBody.cur || 'USD', netRevenue: true, ttl: 120, - bidderCode: allowAlternateBidderCodes ? `n360-${bid.ssp}` : 'nexx360', mediaType: bid.type === 'banner' ? 'banner' : 'video', meta: { advertiserDomains: bid.adomain, demandSource: ssp, }, }; - // if (bid.dealid) response.dealid = bid.dealid; + if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ssp}`; if (response.mediaType === 'banner') { response.adUrl = bid.adUrl; diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index a5da8b29fbc..7645ee59f63 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -372,7 +372,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); expect(output[0].currency).to.be.eql(response.body.cur); @@ -411,7 +410,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); 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); @@ -454,7 +452,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); expect(output[0].currency).to.be.eql(response.body.cur); @@ -490,7 +487,6 @@ describe('Nexx360 bid adapter tests', function () { } }; const output = spec.interpretResponse(response); - expect(output[0].bidderCode).to.be.eql('nexx360'); 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); From cb1a982182816e8a8b47ec1c769dd40bfd223c37 Mon Sep 17 00:00:00 2001 From: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Tue, 31 Jan 2023 21:42:40 +0200 Subject: [PATCH 047/375] Taboola Bid Adapter: pass nurl to bidResponse (#9482) * nurl-bugfix * nurl-bugfix --- modules/taboolaBidAdapter.js | 3 ++- test/spec/modules/taboolaBidAdapter_spec.js | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 2e10a8d8951..026d5a098df 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -279,7 +279,7 @@ function getBid(bids, currency, bidResponse) { return; } const { - price: cpm, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} + price: cpm, nurl, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} } = bidResponse; let requestId = bids[bidResponse.impid - 1].bidId; if (advertiserDomains && advertiserDomains.length > 0) { @@ -297,6 +297,7 @@ function getBid(bids, currency, bidResponse) { width, height, meta, + nurl, netRevenue: true }; } diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 17cc5fc0213..0c01244033e 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -460,7 +460,9 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + } ], 'seat': '14204545260' @@ -546,7 +548,8 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' }, { 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', @@ -562,7 +565,9 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + } ], 'seat': '14204545260' @@ -586,6 +591,7 @@ describe('Taboola Adapter', function () { ad: multiServerResponse.body.seatbid[0].bid[0].adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -601,6 +607,7 @@ describe('Taboola Adapter', function () { ad: multiServerResponse.body.seatbid[0].bid[1].adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -625,6 +632,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -651,6 +659,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, From 4796d132242ea2a4b6bbeecbded027a861faa79e Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 1 Feb 2023 14:15:26 +0200 Subject: [PATCH 048/375] 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 --- modules/vidazooBidAdapter.js | 78 +++++++++++++------ test/spec/modules/vidazooBidAdapter_spec.js | 83 +++++++++++++++------ 2 files changed, 117 insertions(+), 44 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index bb3e4abd838..96dcc182436 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,8 +1,9 @@ -import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { bidderSettings } from '../src/bidderSettings.js'; +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {bidderSettings} from '../src/bidderSettings.js'; +import { config } from '../src/config.js'; const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; @@ -26,11 +27,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 ''; @@ -58,10 +59,23 @@ function isBidRequestValid(bid) { return !!(extractCID(params) && extractPID(params)); } -function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; - const { ext } = params; - let { bidFloor } = params; +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + auctionId, + transactionId, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + const {ext} = params; + let {bidFloor} = params; const hashUrl = hashCode(topWindowUrl); const dealId = getNextDealId(hashUrl); const uniqueDealId = getUniqueDealId(hashUrl); @@ -110,7 +124,14 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { isStorageAllowed: isStorageAllowed, gpid: gpid, cat: cat, - pagecat: pagecat + pagecat: pagecat, + auctionId: auctionId, + transactionId: transactionId, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout }; appendUserIdsToRequestPayload(data, userId); @@ -127,6 +148,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}`, @@ -169,10 +198,11 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { function buildRequests(validBidRequests, bidderRequest) { // TODO: does the fallback make sense here? const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); requests.push(request); }); return requests; @@ -182,14 +212,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; } @@ -228,8 +258,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 || '')}` @@ -253,7 +283,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; } @@ -310,7 +342,8 @@ export function getCacheOpt() { export function getStorageItem(key) { try { return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { } + } catch (e) { + } return null; } @@ -318,9 +351,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/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 2ddb65469af..134e3e66256 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import { spec as adapter, SUPPORTED_ID_SYSTEMS, @@ -15,9 +15,10 @@ import { getVidazooSessionId, } from 'modules/vidazooBidAdapter.js'; import * as utils from 'src/utils.js'; -import { version } from 'package.json'; -import { useFakeTimers } from 'sinon'; -import { BANNER, VIDEO } from '../../../src/mediaTypes'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; const SUB_DOMAIN = 'openrtb'; @@ -38,6 +39,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], @@ -53,6 +58,10 @@ const VIDEO_BID = { 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { 'subDomain': SUB_DOMAIN, @@ -85,6 +94,8 @@ const BIDDER_REQUEST = { 'consentString': 'consent_string', 'gdprApplies': true }, + 'gppString': 'gpp_string', + 'gppSid': [7], 'uspConsent': 'consent_string', 'refererInfo': { 'page': 'https://www.greatsite.com', @@ -94,6 +105,10 @@ const BIDDER_REQUEST = { 'site': { 'cat': ['IAB2'], 'pagecat': ['IAB2-2'] + }, + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] } }, }; @@ -147,7 +162,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 ''; @@ -226,6 +241,9 @@ describe('VidazooBidAdapter', 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({ @@ -243,6 +261,15 @@ describe('VidazooBidAdapter', function () { gdpr: 1, gdprConsent: 'consent_string', usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + bidderRequestId: '12a8ae9ada9c13', gpid: '', prebidVersion: version, ptrace: '1000', @@ -278,6 +305,9 @@ describe('VidazooBidAdapter', function () { }); it('should build banner request for each size', function () { + config.setConfig({ + bidderTimeout: 3000 + }); const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); @@ -288,6 +318,15 @@ describe('VidazooBidAdapter', function () { gdprConsent: 'consent_string', gdpr: 1, usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', @@ -324,7 +363,7 @@ describe('VidazooBidAdapter', 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', @@ -333,7 +372,7 @@ describe('VidazooBidAdapter', 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.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -341,7 +380,7 @@ describe('VidazooBidAdapter', 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.cootlogix.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -357,12 +396,12 @@ describe('VidazooBidAdapter', 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; }); @@ -422,11 +461,11 @@ describe('VidazooBidAdapter', 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; } @@ -445,18 +484,18 @@ describe('VidazooBidAdapter', 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'); @@ -569,7 +608,7 @@ describe('VidazooBidAdapter', 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'); @@ -585,8 +624,8 @@ describe('VidazooBidAdapter', 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'); }); From 76f00021c4aa5a9248dcdea8be5c90a6e144f8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= <88041828+smart-adserver@users.noreply.github.com> Date: Wed, 1 Feb 2023 13:23:46 +0100 Subject: [PATCH 049/375] 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 --- modules/smartadserverBidAdapter.js | 19 +++++++++++---- .../modules/smartadserverBidAdapter_spec.js | 24 +++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 719d621b056..c07b2abe933 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -160,10 +160,21 @@ export const spec = { sdc: sellerDefinedContext }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + + if (bidderRequest.gppConsent) { + payload.gpp = bidderRequest.gppConsent.gppString; + payload.gpp_sid = bidderRequest.gppConsent.applicableSections; + } + + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } } if (bid && bid.userId) { diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 4dacb356894..97250ad0ebc 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -443,6 +443,30 @@ describe('Smart bid adapter tests', function () { }); }); + describe('GPP', function () { + it('should be added to payload when gppConsent available in bidder request', function () { + const options = { + gppConsent: { + gppString: 'some-gpp-string', + applicableSections: [3, 5] + } + }; + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, options); + const payload = JSON.parse(request[0].data); + + expect(payload).to.have.property('gpp').and.to.equal(options.gppConsent.gppString); + expect(payload).to.have.property('gpp_sid').and.to.be.an('array'); + expect(payload.gpp_sid).to.have.lengthOf(2).and.to.deep.equal(options.gppConsent.applicableSections); + }); + + it('should be undefined on payload when gppConsent unavailable in bidder request', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, {}); + const payload = JSON.parse(request[0].data); + + expect(payload.gpp).to.be.undefined; + }); + }); + describe('ccpa/us privacy tests', function () { afterEach(function () { config.resetConfig(); From fc5d01439423f3a3f78dffe4d545c37c0247445f Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Wed, 1 Feb 2023 04:46:11 -0800 Subject: [PATCH 050/375] 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 --- src/prebid.js | 8 ++++++++ test/spec/unit/pbjs_api_spec.js | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/prebid.js b/src/prebid.js index 3fb131fb02c..9ad72409990 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -915,6 +915,14 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { } }; +/** + * @alias module:pbjs.aliasRegistry + */ +$$PREBID_GLOBAL$$.aliasRegistry = adapterManager.aliasRegistry; +config.getConfig('aliasRegistry', config => { + if (config.aliasRegistry === 'private') delete $$PREBID_GLOBAL$$.aliasRegistry; +}); + /** * The bid response object returned by an external bidder adapter during the auction. * @typedef {Object} AdapterBidResponse diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index ae9ea09908a..0867cb0e399 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -3017,6 +3017,20 @@ describe('Unit: Prebid Module', function () { }); }); + describe('aliasRegistry', function () { + it('should return the same value as adapterManager.aliasRegistry by default', function () { + const adapterManagerAliasRegistry = adapterManager.aliasRegistry; + const pbjsAliasRegistry = $$PREBID_GLOBAL$$.aliasRegistry; + assert.equal(adapterManagerAliasRegistry, pbjsAliasRegistry); + }); + + it('should return undefined if the aliasRegistry config option is set to private', function () { + configObj.setConfig({ aliasRegistry: 'private' }); + const pbjsAliasRegistry = $$PREBID_GLOBAL$$.aliasRegistry; + assert.equal(pbjsAliasRegistry, undefined); + }); + }); + describe('setPriceGranularity', function () { it('should log error when not passed granularity', function () { const logErrorSpy = sinon.spy(utils, 'logError'); From be7bd91ff86273aa0eceb8dfa945a49f4ff31560 Mon Sep 17 00:00:00 2001 From: guiann Date: Wed, 1 Feb 2023 13:55:38 +0100 Subject: [PATCH 051/375] 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 --- modules/adyoulikeBidAdapter.js | 2 ++ test/spec/modules/adyoulikeBidAdapter_spec.js | 3 +++ 2 files changed, 5 insertions(+) diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index f7473b3bad4..e6cdc7698bf 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -126,6 +126,8 @@ export const spec = { payload.userId = createEidsArray(bidderRequest.userId); } + payload.pbjs_version = '$prebid.version$'; + const data = JSON.stringify(payload); const options = { withCredentials: true diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index e6a153d501a..24cede20352 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -722,6 +722,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -736,6 +737,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -758,6 +760,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Bids['bid_id_1'].TransactionID).to.be.equal('bid_id_1_transaction_id'); expect(payload.Bids['bid_id_3'].TransactionID).to.be.equal('bid_id_3_transaction_id'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); }); it('sends bid request to endpoint setted by parameters', function () { From c925e50c6b191c8141f3593c66c10587d3863162 Mon Sep 17 00:00:00 2001 From: Zachary Carlin Date: Wed, 1 Feb 2023 15:54:23 -0500 Subject: [PATCH 052/375] 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 --- integrationExamples/gpt/hello_world.html | 0 modules/sonobiBidAdapter.js | 27 ++++++++++++++-------- test/spec/modules/sonobiBidAdapter_spec.js | 3 ++- 3 files changed, 19 insertions(+), 11 deletions(-) mode change 100755 => 100644 integrationExamples/gpt/hello_world.html diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html old mode 100755 new mode 100644 diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 3c841cc4d8a..f9cc1f3b353 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -295,22 +295,29 @@ function _findBidderRequest(bidderRequests, bidId) { } } +// This function takes all the possible sizes. +// returns string csv. function _validateSize(bid) { - if (deepAccess(bid, 'mediaTypes.video')) { - return ''; // Video bids arent allowed to override sizes via the trinity request + let size = []; + if (deepAccess(bid, 'mediaTypes.video.playerSize')) { + size.push(deepAccess(bid, 'mediaTypes.video.playerSize')) } - - if (bid.params.sizes) { - return parseSizesInput(bid.params.sizes).join(','); + if (deepAccess(bid, 'mediaTypes.video.sizes')) { + size.push(deepAccess(bid, 'mediaTypes.video.sizes')) + } + if (deepAccess(bid, 'params.sizes')) { + size.push(deepAccess(bid, 'params.sizes')); } if (deepAccess(bid, 'mediaTypes.banner.sizes')) { - return parseSizesInput(deepAccess(bid, 'mediaTypes.banner.sizes')).join(','); + size.push(deepAccess(bid, 'mediaTypes.banner.sizes')) } - - // Handle deprecated sizes definition - if (bid.sizes) { - return parseSizesInput(bid.sizes).join(','); + if (deepAccess(bid, 'sizes')) { + size.push(deepAccess(bid, 'sizes')) } + // Pass the 2d sizes array into parseSizeInput to flatten it into an array of x separated sizes. + // Then throw it into Set to uniquify it. + // Then spread it to an array again. Then join it into a csv of sizes. + return [...new Set(parseSizesInput(...size))].join(','); } function _validateSlot(bid) { diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 7803cfff394..a9382f092e2 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -287,6 +287,7 @@ describe('SonobiBidAdapter', function () { }, mediaTypes: { video: { + sizes: [[300, 250], [300, 600]], context: 'outstream' } } @@ -331,7 +332,7 @@ describe('SonobiBidAdapter', function () { }]; let keyMakerData = { - '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d||f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', '30b31c1838de1d': '1a2b3c4d5e6f1a2b3c4e|300x250,300x600|f=0.42,gpid=/123123/gpt_publisher/adunit-code-3,c=d,', '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600|gpid=/7780971/sparks_prebid_LB,c=d,', }; From 3b396cfa8228402fe14c3bb4fcba49f17a15b570 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Wed, 1 Feb 2023 16:02:03 -0500 Subject: [PATCH 053/375] Add ESLint Plugin Recommendation for VSCODE (#9498) Just a quality of life improvement for any VSCode users. --- .devcontainer/devcontainer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0176b8317b3..104d9a38132 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,8 @@ // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "nickdodd79.gulptasks" + "nickdodd79.gulptasks", + "dbaeumer.vscode-eslint" ], // 9999 is web server, 9876 is karma From fef5c5a62c465fb8982c286e6f158dd28f57aed0 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:03:57 -0500 Subject: [PATCH 054/375] Deprecate zeusPrimeRtdProvider submodule (#9358) Deprecates the zeusPrimeRtdProvider submodule and updates the associated documentation and tests. --- modules/zeusPrimeRtdProvider.js | 326 +------------- modules/zeusPrimeRtdProvider.md | 3 + .../spec/modules/zeusPrimeRtdProvider_spec.js | 410 ------------------ 3 files changed, 6 insertions(+), 733 deletions(-) delete mode 100644 test/spec/modules/zeusPrimeRtdProvider_spec.js diff --git a/modules/zeusPrimeRtdProvider.js b/modules/zeusPrimeRtdProvider.js index 28c46957c50..2a455a14f7b 100644 --- a/modules/zeusPrimeRtdProvider.js +++ b/modules/zeusPrimeRtdProvider.js @@ -1,328 +1,8 @@ -/** - * This module adds Zeus Insights For Publishers (ZIP) provider to the real time data module - * The {@link module:modules/realTimeData} module is required - * - * This module will request the article topics for the current page and add them as page keyvalues - * for the ad requests. - * - * @module modules/zeusInsightsForPublishersRtdProvider - * @requires module:modules/realTimeData - */ - -import { logInfo, logError, logWarn, logMessage } from '../src/utils.js' +import { logWarn } from '../src/utils.js' import { submodule } from '../src/hook.js' -import { ajaxBuilder } from '../src/ajax.js' - -class Logger { - get showDebug() { - if (this._showDebug === true || this._showDebug === false) { - return this._showDebug - } - - return window.zeusPrime?.debug || false - } - set showDebug(shouldShow) { - this._showDebug = shouldShow - } - - get error() { - return logError.bind(this, 'zeusPrimeRtdProvider: ') - } - get warn() { - return logWarn.bind(this, 'zeusPrimeRtdProvider: ') - } - get info() { - return logInfo.bind(this, 'zeusPrimeRtdProvider: ') - } - get debug() { - if (this.showDebug) { - return logMessage.bind(this, 'zeusPrimeRtdProvider: ') - } - - return () => {} - } -} - -var logger = new Logger() - -function loadCommandQueue() { - window.zeusPrime = window.zeusPrime || { cmd: [] } - const queue = [...window.zeusPrime.cmd] - - window.zeusPrime.cmd = [] - window.zeusPrime.cmd.push = (callback) => { - callback(window.zeusPrime) - } - - queue.forEach((callback) => callback(window.zeusPrime)) -} - -function markStatusComplete(key) { - const status = window?.zeusPrime?.status - if (status) { - status[key] = true - } -} - -function createStatus() { - if (window.zeusPrime && !window.zeusPrime.status) { - Object.defineProperty(window.zeusPrime, 'status', { - enumerable: false, - value: { - initComplete: false, - primeKeyValueSet: false, - insightsReqSent: false, - insightsReqReceived: false, - insightsKeyValueSet: false, - scriptComplete: false, - }, - }) - } -} - -function loadPrimeQueryParams() { - try { - const params = new URLSearchParams(window.location.search) - params.forEach((paramValue, paramKey) => { - if (!paramKey.startsWith('zeus_prime_')) { - return - } - - let key = paramKey.replace('zeus_prime_', '') - let value = paramValue.toLowerCase() - - if (value === 'true' || value === '1') { - value = true - } else if (value === 'false' || value === '0') { - value = false - } - - window.zeusPrime[key] = value - }) - } catch (_) {} -} - -const DEFAULT_API = 'https://insights.zeustechnology.com' - -function init(gamId = null, options = {}) { - window.zeusPrime = window.zeusPrime || { cmd: [] } - - window.zeusPrime.gamId = gamId || options.gamId || window.zeusPrime.gamId || undefined - window.zeusPrime.api = DEFAULT_API - window.zeusPrime.hostname = options.hostname || window.location?.hostname || '' - window.zeusPrime.pathname = options.pathname || window.location?.pathname || '' - window.zeusPrime.pageUrl = `${window.zeusPrime.hostname}${window.zeusPrime.pathname}` - window.zeusPrime.pageHash = options.pageHash || null - window.zeusPrime.debug = window.zeusPrime.debug || options.debug === true || false - window.zeusPrime.disabled = window.zeusPrime.disabled || options.disabled === true || false - - loadPrimeQueryParams() - - logger.showDebug = window.zeusPrime.debug - - createStatus() - markStatusComplete('initComplete') -} - -function setTargeting() { - const { gamId, hostname } = window.zeusPrime - - if (typeof gamId !== 'string') { - throw new Error(`window.zeusPrime.gamId must be a string. Received: ${String(gamId)}`) - } - - addKeyValueToGoogletag(`zeus_${gamId}`, hostname) - logger.debug(`Setting zeus_${gamId}=${hostname}`) - markStatusComplete('primeKeyValueSet') -} -function setPrimeAsDisabled() { - addKeyValueToGoogletag('zeus_prime', 'false') - logger.debug('Disabling prime; Setting key-value zeus_prime to false') -} - -function addKeyValueToGoogletag(key, value) { - window.googletag = window.googletag || { cmd: [] } - window.googletag.cmd.push(function () { - window.googletag.pubads().setTargeting(key, value) - }) -} - -function isInsightsPage(pathname = '') { - const NOT_SECTIONS = [ - { - test: /\/search/, - type: 'search', - }, - { - test: /\/author/, - type: 'author', - }, - { - test: /\/event/, - type: 'event', - }, - { - test: /\/homepage/, - type: 'front', - }, - { - test: /^\/?$/, - type: 'front', - }, - ] - - const typeObj = NOT_SECTIONS.find((pg) => pathname.match(pg.test)) - return typeObj === undefined -} - -async function getUrlHash(canonical) { - try { - const buf = await window.crypto.subtle.digest( - 'SHA-1', - new TextEncoder('utf-8').encode(canonical) - ) - const hashed = Array.prototype.map - .call(new Uint8Array(buf), (x) => `00${x.toString(16)}`.slice(-2)) - .join('') - - return hashed - } catch (e) { - logger.error('Failed to load hash', e.message) - logger.debug('Exception', e) - return '' - } -} - -async function sendPrebidRequest(url) { - return new Promise((resolve, reject) => { - const ajax = ajaxBuilder() - ajax(url, { - success: (responseText, response) => { - resolve({ - ...response, - status: response.status, - json: () => JSON.parse(responseText), - }) - }, - - error: (responseText, response) => { - if (!response.status) { - reject(response) - } - - let json = responseText - if (responseText) { - try { - json = JSON.parse(responseText) - } catch (_) { - json = null - } - } - - resolve({ - status: response.status, - json: () => json || null, - responseValue: json, - }) - }, - }) - }) -} - -async function requestTopics() { - const { api, hostname, pageUrl } = window.zeusPrime - - if (!window.zeusPrime.pageHash) { - window.zeusPrime.pageHash = await getUrlHash(pageUrl) - } - - const pageHash = window.zeusPrime.pageHash - const zeusInsightsUrl = `${api}/${hostname}/${pageHash}?article_location=${pageUrl}` - - logger.debug('Requesting topics', zeusInsightsUrl) - try { - markStatusComplete('insightsReqSent') - const response = await sendPrebidRequest(zeusInsightsUrl) - if (response.status === 200) { - logger.debug('topics found') - markStatusComplete('insightsReqReceived') - return await response.json() - } else if ( - response.status === 204 || - response.status < 200 || - (response.status >= 300 && response.status <= 399) - ) { - logger.debug('no topics found') - markStatusComplete('insightsReqReceived') - return null - } else { - logger.error(`Topics request returned error: ${response.status}`) - markStatusComplete('insightsReqReceived') - return null - } - } catch (e) { - logger.error('failed to request topics', e) - return null - } -} - -function setTopicsTargeting(topics = []) { - if (topics.length === 0) { - return - } - - window.googletag = window.googletag || { cmd: [] } - window.googletag.cmd.push(function () { - window.googletag.pubads().setTargeting('zeus_insights', topics) - }) - - markStatusComplete('insightsKeyValueSet') -} - -async function startTopicsRequest() { - if (isInsightsPage(window.zeusPrime.pathname)) { - const response = await requestTopics() - if (response) { - setTopicsTargeting(response?.topics) - } - } else { - logger.debug('This page is not eligible for topics, request will be skipped') - } -} - -async function run(gamId, options = {}) { - logger.showDebug = options.debug || false - - try { - init(gamId, options) - loadCommandQueue() - - if (window.zeusPrime.disabled) { - setPrimeAsDisabled() - } else { - setTargeting() - await startTopicsRequest() - } - } catch (e) { - logger.error('Failed to run.', e.message || e) - } finally { - markStatusComplete('scriptComplete') - } -} - -/** - * @preserve - * Initializes the ZeusPrime RTD Submodule. The config provides the GamID for this - * site that is used to configure Prime. - * @param {object} config The Prebid configuration for this module. - * @param {object} config.params The parameters for this module. - * @param {string} config.params.gamId The Gam ID (or Network Code) in GAM for this site. - */ -function initModule(config) { - const { params } = config || {} - const { gamId, ...rest } = params || {} - run(gamId, rest) +function initModule() { + logWarn('Zeus Prime has been deprecated. This module will be removed in Prebid 8.') } /** diff --git a/modules/zeusPrimeRtdProvider.md b/modules/zeusPrimeRtdProvider.md index f3a6c5018d5..40b44e76f1c 100644 --- a/modules/zeusPrimeRtdProvider.md +++ b/modules/zeusPrimeRtdProvider.md @@ -1,5 +1,8 @@ # Overview +# NOTE: ZEUS PRIME HAS BEEN DEPRECATED! +# THIS MODULE WILL BE REMOVED IN PREBID 8. + Module Name: Zeus Prime RTD Provider Module Type: Rtd Provider Maintainer: support@zeustechnology.com diff --git a/test/spec/modules/zeusPrimeRtdProvider_spec.js b/test/spec/modules/zeusPrimeRtdProvider_spec.js deleted file mode 100644 index 294d98df377..00000000000 --- a/test/spec/modules/zeusPrimeRtdProvider_spec.js +++ /dev/null @@ -1,410 +0,0 @@ -import { zeusPrimeSubmodule } from 'modules/zeusPrimeRtdProvider'; -import { server } from 'test/mocks/xhr.js'; -import * as utils from 'src/utils'; - -async function waitForStatus(statusVar) { - const MAX_COUNT = 20; - let count = 0; - while ( - count <= MAX_COUNT && - window.zeusPrime.status && - window.zeusPrime.status[statusVar] !== true - ) { - count += 1; - await new Promise((resolve) => setTimeout(resolve, 10)); - } - - if (count === MAX_COUNT) { - throw new Error('Timeout waiting for zeusPrimeRtdProvider to complete'); - } -} - -/** - * Execute all the commands in the googletag.cmd queue. - */ -function executeGoogletagTargeting() { - window.googletag.cmd.forEach((cmd) => cmd()); -} - -describe('Zeus Prime RTD submodule', () => { - let logErrorSpy; - let logMessageSpy; - let setTargetingStub; - let originalGtag; - - beforeEach(() => { - logErrorSpy = sinon.spy(utils, 'logError'); - logMessageSpy = sinon.spy(utils, 'logMessage'); - setTargetingStub = sinon.stub(); - window.zeusPrime = { cmd: [] }; - originalGtag = window.googletag; - window.googletag = { - cmd: [], - pubads: () => ({ - setTargeting: setTargetingStub, - }), - }; - - // Mock subtle since this doesnt exists in some test environments due to security in newer browsers. - if (typeof window.crypto.subtle === 'undefined') { - Object.defineProperty(crypto, 'subtle', { value: { digest: () => 'mockHash' } }) - } - }); - - afterEach(() => { - logErrorSpy.restore(); - logMessageSpy.restore(); - window.googletag = originalGtag; - }); - - it('should init and set key-value for zeus_', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/', - }, - }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.callCount(setTargetingStub, 1); - sinon.assert.calledWith(setTargetingStub, 'zeus_1234', 'www.example.com'); - }); - - it('should init and set key-value for zeus_ from command queue', async () => { - window.zeusPrime.cmd.push((prime) => (prime.gamId = '9876')); - zeusPrimeSubmodule.init({ - params: { - hostname: 'www.example.com', - pathname: '/', - }, - }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.callCount(setTargetingStub, 1); - sinon.assert.calledWith(setTargetingStub, 'zeus_9876', 'www.example.com'); - }); - - it('should init with values from location and set key-value for zeus_', async () => { - zeusPrimeSubmodule.init({ params: { gamId: '1234' } }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.callCount(setTargetingStub, 1); - sinon.assert.calledWith(setTargetingStub, 'zeus_1234', 'localhost'); - expect(window.zeusPrime.pathname).to.equal('/context.html'); - }); - - it('should emit error when gamId is not set', async () => { - zeusPrimeSubmodule.init({}); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(0); - sinon.assert.callCount(setTargetingStub, 0); - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'Failed to run.', - 'window.zeusPrime.gamId must be a string. Received: undefined' - ); - }); - - it('should not make a call to the server when url is a homepage', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/', - }, - }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - expect(server.requests).to.have.length(0); - }); - - it('should make a call to the server and set key-vlaue when url is an article page and returns topics', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - '{"topics": ["bs0"]}' - ); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(2); - sinon.assert.calledTwice(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.calledWith(setTargetingStub.secondCall, 'zeus_insights', [ - 'bs0', - ]); - sinon.assert.notCalled(logErrorSpy); - }); - - it('should not set insights keyvalue when server returns 204', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].respond(204, { 'Content-Type': 'application/json' }); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.notCalled(logErrorSpy); - }); - - it('should not set insights keyvalue when server returns empty topics array', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Respond - server.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - '{ "topics": [] }' - ); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.notCalled(logErrorSpy); - }); - - it('should not set insights keyvalue and emit error when server returns error status (400)', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].respond( - 404, - { 'Content-Type': 'application/json' }, - '{"message": "Not found"}' - ); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'Topics request returned error: 404' - ); - }); - - it('should not set insights keyvalue and emit error when response is not received (network request)', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].error(); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'failed to request topics' - ); - }); - - it('fails gracefully when crypto fails', async () => { - const digestStub = sinon.stub(window.crypto.subtle, 'digest'); - digestStub.throwsException('Failed to generate digest.'); - - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'Failed to load hash' - ); - - digestStub.restore(); - }); - - it('script should add zeus_prime key and not send request when disabled is set', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - disabled: true, - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(0); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledWith(setTargetingStub, 'zeus_prime', 'false'); - }); - - it('debug true enables debug logging', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - debug: true, - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - sinon.assert.called(logMessageSpy); - sinon.assert.notCalled(logErrorSpy); - }); - - it('debug false disables debug logging', async () => { - window.zeusPrime.disabled = false; - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - sinon.assert.notCalled(logMessageSpy); - sinon.assert.notCalled(logErrorSpy); - }); -}); From b04f56d7b76b2493803939948e6096227decc3f0 Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Thu, 2 Feb 2023 05:46:13 -0800 Subject: [PATCH 055/375] 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 --- modules/consentManagement.js | 38 +++++++++++++--- test/spec/modules/consentManagement_spec.js | 50 ++++++++++++++++++++- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 217ceecb1c4..bdf6649bcba 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -18,12 +18,16 @@ const CMP_VERSION = 2; export let userCMP; export let consentTimeout; +export let actionTimeout; export let gdprScope; export let staticConsentData; let consentData; let addedConsentHook = false; let provisionalConsent; +let onTimeout; +let timer = null; +let actionTimer = null; // add new CMPs here, with their dedicated lookup function const cmpCallMap = { @@ -39,6 +43,12 @@ function lookupStaticConsentData({onSuccess, onError}) { processCmpData(staticConsentData, {onSuccess, onError}) } +export function setActionTimeout(timeout = setTimeout) { + clearTimeout(timer); + timer = null; + actionTimer = timeout(onTimeout, actionTimeout); +} + /** * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function @@ -84,6 +94,7 @@ function lookupIabConsent({onSuccess, onError}) { processCmpData(tcfData, {onSuccess, onError}); } else { provisionalConsent = tcfData; + if (!isNaN(actionTimeout) && actionTimer === null && timer != null) setActionTimeout(); } } else { onError('CMP unable to register callback function. Please check CMP setup.'); @@ -162,14 +173,20 @@ function lookupIabConsent({onSuccess, onError}) { * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra * error arguments that will be undefined if there's no error. */ -function loadConsentData(cb) { +export function loadConsentData(cb, callMap = cmpCallMap, timeout = setTimeout) { let isDone = false; - let timer = null; function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { if (timer != null) { clearTimeout(timer); + timer = null; + } + + if (actionTimer != null) { + clearTimeout(actionTimer); + actionTimer = null; } + isDone = true; gdprDataHandler.setConsentData(consentData); if (typeof cb === 'function') { @@ -188,10 +205,11 @@ function loadConsentData(cb) { done(null, true, msg, ...extraArgs); } } - cmpCallMap[userCMP](callbacks); + + callMap[userCMP](callbacks); if (!isDone) { - const onTimeout = () => { + onTimeout = () => { const continueToAuction = (data) => { done(data, false, 'CMP did not load, continuing auction...'); } @@ -200,10 +218,16 @@ function loadConsentData(cb) { onError: () => continueToAuction(storeConsentData(undefined)) }) } + if (consentTimeout === 0) { onTimeout(); } else { - timer = setTimeout(onTimeout, consentTimeout); + if (timer != null) { + clearTimeout(timer); + timer = null; + } + + timer = timeout(onTimeout, consentTimeout); } } } @@ -328,6 +352,10 @@ export function setConsentConfig(config) { logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); } + if (isNumber(config.actionTimeout)) { + actionTimeout = config.actionTimeout; + } + if (isNumber(config.timeout)) { consentTimeout = config.timeout; } else { diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 47a2e2ab3d9..506b88ad839 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,4 +1,15 @@ -import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData, gdprScope } from 'modules/consentManagement.js'; +import { + setConsentConfig, + requestBidsHook, + resetConsentData, + userCMP, + consentTimeout, + actionTimeout, + staticConsentData, + gdprScope, + loadConsentData, + setActionTimeout +} from 'modules/consentManagement.js'; import { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; @@ -726,4 +737,41 @@ describe('consentManagement', function () { }); }); }); + + describe('actionTimeout', function () { + afterEach(function () { + config.resetConfig(); + resetConsentData(); + }); + + it('should set actionTimeout if present', () => { + setConsentConfig({ + gdpr: { timeout: 5000, actionTimeout: 5500 } + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(5000); + expect(actionTimeout).to.be.equal(5500); + }); + + it('should utilize actionTimeout duration on initial user visit when user action is pending', () => { + const cb = () => {}; + const cmpCallMap = { + 'iab': () => {}, + 'static': () => {} + }; + const timeout = sinon.spy(); + + setConsentConfig({ + gdpr: { timeout: 5000, actionTimeout: 5500 } + }); + loadConsentData(cb, cmpCallMap, timeout); + + sinon.assert.calledWith(timeout, sinon.match.any, 5000); + + setActionTimeout(); + + timeout.lastCall.lastArg === 5500; + }); + }); }); From 04bc713d1aff97af7b1c4806d2daadc9d6fe6fa2 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:29:15 +0100 Subject: [PATCH 056/375] 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 --- modules/zeta_global_sspBidAdapter.js | 15 +++++++++++++++ .../modules/zeta_global_sspBidAdapter_spec.js | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 6c5b9783782..74ddfc0bd06 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -90,6 +90,21 @@ export const spec = { if (!impData.banner && !impData.video) { impData.banner = buildBanner(request); } + + if (typeof request.getFloor === 'function') { + const floorInfo = request.getFloor({ + currency: 'USD', + mediaType: impData.video ? 'video' : 'banner', + size: [ impData.video ? impData.video.w : impData.banner.w, impData.video ? impData.video.h : impData.banner.h ] + }); + if (floorInfo && floorInfo.floor) { + impData.bidfloor = floorInfo.floor; + } + } + if (!impData.bidfloor && params.bidfloor) { + impData.bidfloor = params.bidfloor; + } + return impData; }); diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index d6befa0fc78..f616403cfcd 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -41,6 +41,7 @@ describe('Zeta Ssp Bid Adapter', function () { app: { bundle: 'testBundle' }, + bidfloor: 0.2, test: 1 }; @@ -359,4 +360,11 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.tmax).to.be.undefined; }); + + it('Test provide bidfloor', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].bidfloor).to.eql(params.bidfloor); + }); }); From 3be964e4854bafbc1ef224407cb3a6227c7871cf Mon Sep 17 00:00:00 2001 From: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:34:50 +0100 Subject: [PATCH 057/375] OneTag Bid Adapter: add gppConsent fetch (#9487) Co-authored-by: federico --- modules/onetagBidAdapter.js | 13 ++++++- test/spec/modules/onetagBidAdapter_spec.js | 43 ++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index b5217e77cd6..70238294c38 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -61,6 +61,12 @@ function buildRequests(validBidRequests, bidderRequest) { consentRequired: bidderRequest.gdprConsent.gdprApplies }; } + if (bidderRequest && bidderRequest.gppConsent) { + payload.gppConsent = { + consentString: bidderRequest.gppConsent.gppString, + applicableSections: bidderRequest.gppConsent.applicableSections + } + } if (bidderRequest && bidderRequest.uspConsent) { payload.usPrivacy = bidderRequest.uspConsent; } @@ -340,7 +346,7 @@ function getSizes(sizes) { return ret; } -function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { let syncs = []; let params = ''; if (gdprConsent) { @@ -351,6 +357,11 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { params += '&gdpr_consent=' + gdprConsent.consentString; } } + if (gppConsent) { + if (typeof gppConsent.gppString === 'string') { + params += '&gpp_consent=' + gppConsent.gppString; + } + } if (uspConsent && typeof uspConsent === 'string') { params += '&us_privacy=' + uspConsent; } diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 71e897c7f9e..5bd65cf0fd5 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -266,6 +266,27 @@ describe('onetag', function () { expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); expect(payload.gdprConsent.consentRequired).to.exist.and.to.be.true; }); + it('Should send GPP consent data', function () { + let consentString = 'consentString'; + let applicableSections = [1, 2, 3]; + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + gppString: consentString, + applicableSections: applicableSections + } + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + + expect(payload).to.exist; + expect(payload.gppConsent).to.exist; + expect(payload.gppConsent.consentString).to.exist.and.to.equal(consentString); + expect(payload.gppConsent.applicableSections).to.have.same.members(applicableSections); + }); it('Should send us privacy string', function () { let consentString = 'us_foo'; let bidderRequest = { @@ -375,6 +396,28 @@ describe('onetag', function () { expect(syncs[0].url).to.include(sync_endpoint); expect(syncs[0].url).to.not.match(/(?:[?&](?:gdpr_consent=([^&]*)|gdpr=([^&]*)))+$/); }); + it('Must pass gpp consent string when gppConsent object is available', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: 'foo' + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.match(/(?:[?&](?:gpp_consent=foo([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is null', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: null + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is empty', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, {}); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); it('Should send us privacy string', function () { let usConsentString = 'us_foo'; const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, usConsentString); From 50e107faf3e49e67f7d8bb20eb04ccb5cbe42e5d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 2 Feb 2023 17:08:08 +0000 Subject: [PATCH 058/375] Prebid 7.35.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6249ef52c04..96f6a796918 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.35.0-pre", + "version": "7.35.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6467045121b..9d3fbb66093 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.35.0-pre", + "version": "7.35.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ae21d87dbe50bb9f20fe8ecb29fadc32e80c41c8 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 2 Feb 2023 17:08:08 +0000 Subject: [PATCH 059/375] Increment version to 7.36.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 96f6a796918..d766178d188 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.35.0", + "version": "7.36.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9d3fbb66093..cf3a7703aac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.35.0", + "version": "7.36.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 3e2b9fca2ab41eb8280925c27b5d970ca43cd6f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Feb 2023 12:38:37 -0500 Subject: [PATCH 060/375] 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> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index d766178d188..587b7897394 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.34.0-pre", + "version": "7.36.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -13818,9 +13818,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "node_modules/http-errors": { @@ -36045,9 +36045,9 @@ "dev": true }, "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "http-errors": { From dacbd1b6003000f8b0675f4aae1f9c35c69f5942 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Thu, 2 Feb 2023 12:41:51 -0500 Subject: [PATCH 061/375] 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 --- .devcontainer/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cfb29ebdfa9..69e13850258 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,8 @@ ARG VARIANT="12" FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg + # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends From 62fbcb491b6d40816e84a15ff182dbbe31446e07 Mon Sep 17 00:00:00 2001 From: Nitin Nimbalkar <96475150+nitin0610@users.noreply.github.com> Date: Fri, 3 Feb 2023 01:10:36 +0530 Subject: [PATCH 062/375] Topics Module: Mark Down file added (#9484) * Topics MD file added * Topics MD file changes --- modules/fpdModule/index.md | 7 +++-- modules/topicsFpdModule.md | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 modules/topicsFpdModule.md diff --git a/modules/fpdModule/index.md b/modules/fpdModule/index.md index 638c966883a..238881db96b 100644 --- a/modules/fpdModule/index.md +++ b/modules/fpdModule/index.md @@ -16,9 +16,11 @@ Validation Submodule: - verify that certain OpenRTB attributes are not specified - optionally suppress user FPD based on the existence of _pubcid_optout +Topic Submodule: +- populate first party/third party topics data onto user.data in bid stream. 1. Module initializes on first load and set bidRequestHook -2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations dependant on submodule +2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations/topics dependant on submodule 3. After hook complete, it is disabled - meaning module only runs on first auction 4. To reinitiate the module, run pbjs.refreshFPD(), which allows module to rerun as if initial load @@ -43,4 +45,5 @@ pbjs.setConfig({ At least one of the submodules must be included in order to successfully run the corresponding above operations. enrichmentFpdModule -validationFpdModule \ No newline at end of file +validationFpdModule +topicsFpdModule \ No newline at end of file diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md new file mode 100644 index 00000000000..b1bc3cb0d5b --- /dev/null +++ b/modules/topicsFpdModule.md @@ -0,0 +1,62 @@ +# Overview + +Module Name: topicsFpdModule + +# Description +Purpose of this module is to call the Topics API (document.browsingTopics()) which will fetch the first party domain as well third party domain(Iframe) topics data which will be sent onto user.data in bid stream. + +The intent of the Topics API is to provide callers (including third-party ad-tech or advertising providers on the page that run script) with coarse-grained advertising topics that the page visitor might currently be interested in. + +Topics Module(topicsFpdModule) should be included in prebid final package to call topics API. +Module topicsFpdModule helps to call the Topics API which will send topics data in bid stream (onto user.data) + +``` +try { + if ('browsingTopics' in document && document.featurePolicy.allowsFeature('browsing-topics')) { + topics = document.browsingTopics(); + } +} catch (e) { + console.error('Could not call topics API', e); +} +``` + +# Topics Iframe Configuration + +Topics iframe implementation is the enhancements of existing module under topicsFpdModule.js where different bidders will call the topic API under their domain to fetch the topics for respective domain and the segment data will be part of ORTB request under user.data object. Default config is maintained in the module itself. + +Below are the configuration which can be used to configure and override the default config maintained in the module. + +``` +pbjs.setConfig({ + userSync: { + ..., + topics: { + maxTopicCaller: 3, // SSP rotation + bidders: [{ + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html', + expiry: 7 // Configurable expiry days + },{ + bidder: 'rubicon', + iframeURL: 'https://rubicon.com:8080/topics/fpd/topic.html', // dummy URL + expiry: 7 // Configurable expiry days + },{ + bidder: 'appnexus', + iframeURL: 'https://appnexus.com:8080/topics/fpd/topic.html', // dummy URL + expiry: 7 // Configurable expiry days + }] + } + .... + } +}) +``` + +## Topics Config Descriptions + +| Field | Required? | Type | Description | +|---|---|---|---| +| topics.maxTopicCaller | no | integer | Defines the maximum numbers of Bidders Iframe which needs to be loaded on the publisher page. Default is 1 which is hardcoded in Module. Eg: topics.maxTopicCaller is set to 3. If there are 10 bidders configured along with their iframe URLS, random 3 bidders iframe URL is loaded which will call TOPICS API. If topics.maxTopicCaller is set to 0, it will load random 1(default) bidder iframe atleast. | +| topics.bidders | no | Array of objects | Array of topics callers with the iframe locations and other necessary informations like bidder(Bidder code) and expiry. Default Array of topics in the module itself.| +| topics.bidders[].bidder | yes | string | Bidder Code of the bidder(SSP). | +| topics.bidders[].iframeURL | yes | string | URL which is hosted on bidder/SSP/third-party domains which will call Topics API. | +| topics.bidders[].expiry | no | integer | Max number of days where Topics data will be persist. If Data is stored for more than mentioned expiry day, it will be deleted from storage. Default is 21 days which is hardcoded in Module. | \ No newline at end of file From e9cc90742d332f2c6211414acd88d8ea506994ab Mon Sep 17 00:00:00 2001 From: Alexandru Date: Sat, 4 Feb 2023 17:07:24 +0200 Subject: [PATCH 063/375] BrightcomSSP bid adapter: add new adapter (#9411) * BrightcomSSP: add new adapter * BrightcomSSP: add glvid * BrightcomSSP: add missing gvlid; update isBidRequestValidC --- modules/brightcomBidAdapter.js | 1 + modules/brightcomSSPBidAdapter.js | 323 ++++++++++++++ modules/brightcomSSPBidAdapter.md | 46 ++ .../modules/brightcomSSPBidAdapter_spec.js | 411 ++++++++++++++++++ 4 files changed, 781 insertions(+) create mode 100644 modules/brightcomSSPBidAdapter.js create mode 100644 modules/brightcomSSPBidAdapter.md create mode 100644 test/spec/modules/brightcomSSPBidAdapter_spec.js diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index bbe0203772a..15b4175b59a 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -9,6 +9,7 @@ const URL = 'https://brightcombid.marphezis.com/hb'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], + gvlid: 883, isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js new file mode 100644 index 00000000000..b7a9aa3fdc9 --- /dev/null +++ b/modules/brightcomSSPBidAdapter.js @@ -0,0 +1,323 @@ +import { + getBidIdParameter, + isArray, + getWindowTop, + getUniqueIdentifierStr, + deepSetValue, + logError, + logWarn, + createTrackPixelHtml, + getWindowSelf, + isFn, + isPlainObject, +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {ajax} from '../src/ajax.js'; + +const BIDDER_CODE = 'bcmssp'; +const URL = 'https://rt.marphezis.com/hb'; +const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' + +export const spec = { + code: BIDDER_CODE, + gvlid: 883, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidderError, + onTimeout, + onBidWon, + getUserSyncs, +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + const impressions = bidReqs.map(bid => { + let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; + bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => isArray(size)); + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + + const bidFloor = _getBidFloor(bid); + + if (bidFloor) { + imp.bidfloor = bidFloor; + } + + return imp; + }) + + const referrer = bidderRequest?.refererInfo?.page || ''; + const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); + + const payload = { + id: getUniqueIdentifierStr(), + imp: impressions, + site: { + domain: bidderRequest?.refererInfo?.domain || '', + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(), + w: screen.width, + h: screen.height + }, + tmax: config.getConfig('bidderTimeout') + }; + + if (bidderRequest?.gdprConsent) { + deepSetValue(payload, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest?.uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (config.getConfig('coppa') === true) { + deepSetValue(payload, 'regs.coppa', 1); + } + + if (bidReqs?.[0]?.schain) { + deepSetValue(payload, 'source.ext.schain', bidReqs[0].schain) + } + + if (bidReqs?.[0]?.userIdAsEids) { + deepSetValue(payload, 'user.ext.eids', bidReqs[0].userIdAsEids || []) + } + + if (bidReqs?.[0].userId) { + deepSetValue(payload, 'user.ext.ids', bidReqs[0].userId || []) + } + + return { + method: 'POST', + url: URL, + data: JSON.stringify(payload), + options: {contentType: 'text/plain', withCredentials: false} + }; + } catch (e) { + logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + let response = []; + if (!serverResponse.body || typeof serverResponse.body != 'object') { + logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return response; + } + + const {body: {id, seatbid}} = serverResponse; + + try { + if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { + response = seatbid[0].bid.map(bid => { + return { + requestId: bid.impid, + cpm: parseFloat(bid.price), + width: parseInt(bid.w), + height: parseInt(bid.h), + creativeId: bid.crid || bid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(bid), + ttl: 60, + meta: { + advertiserDomains: bid?.adomain || [] + } + }; + }); + } + } catch (e) { + logError(e, {id, seatbid}); + } + + return response; +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +function onTimeout(timeoutData) { + if (timeoutData === null) { + return; + } + + _trackEvent('timeout', timeoutData); +} + +function onBidderError(errorData) { + if (errorData === null || !errorData.bidderRequest) { + return; + } + + _trackEvent('error', errorData.bidderRequest) +} + +function onBidWon(bid) { + if (bid === null) { + return; + } + + _trackEvent('bidwon', bid) +} + +function _trackEvent(endpoint, data) { + ajax(`${TRACK_EVENT_URL}/${endpoint}`, null, JSON.stringify(data), { + method: 'POST', + withCredentials: false + }); +} + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, {w, h} = {}) { + return getWindowTop().document.visibilityState === 'visible' ? _getPercentInView(element, topWin, {w, h}) : 0; +} + +function _isIframe() { + try { + return getWindowSelf() !== getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +function _getBoundingBox(element, {w, h} = {}) { + let {width, height, left, top, right, bottom} = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return {width, height, left, top, right, bottom}; +} + +function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, right: rects[0].right, top: rects[0].top, bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + +function _getPercentInView(element, topWin, {w, h} = {}) { + const elementBoundingBox = _getBoundingBox(element, {w, h}); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = _getIntersectionOfRects([{ + left: 0, top: 0, right: topWin.innerWidth, bottom: topWin.innerHeight + }, elementBoundingBox]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +function _getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', mediaType: '*', size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + +registerBidder(spec); diff --git a/modules/brightcomSSPBidAdapter.md b/modules/brightcomSSPBidAdapter.md new file mode 100644 index 00000000000..8d0e4ec70dc --- /dev/null +++ b/modules/brightcomSSPBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: Brightcom SSP Bid Adapter +Module Type: Bidder Adapter +Maintainer: alexandruc@brightcom.com +``` + +# Description + +Brightcom's adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [{ + bidder: 'bcmssp', + params: { + publisherId: 2141020, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'bcmssp', + params: { + publisherId: 2141020 + } + }] + } +] +``` diff --git a/test/spec/modules/brightcomSSPBidAdapter_spec.js b/test/spec/modules/brightcomSSPBidAdapter_spec.js new file mode 100644 index 00000000000..6f35a7a290b --- /dev/null +++ b/test/spec/modules/brightcomSSPBidAdapter_spec.js @@ -0,0 +1,411 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/brightcomSSPBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import {config} from '../../../src/config'; + +const URL = 'https://rt.marphezis.com/hb'; + +describe('brightcomSSPBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when publisherId not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].mediaTypes.banner.sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + it('sends gdpr info if exists', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'bidderCode': 'bcmssp', + 'auctionId': '1d1a030790a437', + 'bidderRequestId': '22edbae2744bf5', + 'timeout': 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + page: 'http://example.com/page.html', + domain: 'example.com', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.exist.and.to.be.a('string'); + expect(data.user.ext.consent).to.equal(consentString); + }); + + it('sends us_privacy', function () { + const bidderRequest = { + uspConsent: '1YYY' + }; + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) + + expect(data.regs).to.not.equal(null); + expect(data.regs.ext).to.not.equal(null); + expect(data.regs.ext.us_privacy).to.equal('1YYY'); + }); + + it('sends coppa', function () { + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); + + const data = JSON.parse(spec.buildRequests(bidRequests).data) + expect(data.regs).to.not.be.undefined; + expect(data.regs.coppa).to.equal(1); + }); + + it('sends schain', function () { + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data).to.not.be.undefined; + expect(data.source).to.not.be.undefined; + expect(data.source.ext).to.not.be.undefined; + expect(data.source.ext.schain).to.not.be.undefined; + expect(data.source.ext.schain.complete).to.equal(1); + expect(data.source.ext.schain.ver).to.equal('1.0'); + expect(data.source.ext.schain.nodes).to.not.be.undefined; + expect(data.source.ext.schain.nodes).to.lengthOf(1); + expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); + expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); + expect(data.source.ext.schain.nodes[0].hp).to.equal(1); + expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); + expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); + expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); + }); + + it('sends user eid parameters', function () { + bidRequests[0].userIdAsEids = [{ + source: 'pubcid.org', + uids: [{ + id: 'userid_pubcid' + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'userid_ttd', + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.eids).to.not.be.undefined; + expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); + }); + + it('sends user id parameters', function () { + const userId = { + sharedid: { + id: '01*******', + third: '01E*******' + } + }; + + bidRequests[0].userId = userId; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.ids).is.deep.equal(userId); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250, + 'adomain': ['example.com'] + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); From c8ac9db51d5ff92ba9da054daca2d8bc355d48bc Mon Sep 17 00:00:00 2001 From: AndreaC <67786179+darkstarac@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:36:09 +0100 Subject: [PATCH 064/375] 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 --- modules/aidemBidAdapter.js | 155 +++++++++++++--------- modules/aidemBidAdapter.md | 17 ++- test/spec/modules/aidemBidAdapter_spec.js | 87 ++++++++++-- 3 files changed, 177 insertions(+), 82 deletions(-) diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index 081a0324ddb..e4d5c618b77 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -1,4 +1,4 @@ -import {_each, contains, deepAccess, deepSetValue, getDNT, isBoolean, isStr, logError, logInfo} from '../src/utils.js'; +import {_each, contains, deepAccess, deepSetValue, getDNT, isBoolean, isStr, isNumber, logError, logInfo} from '../src/utils.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -21,6 +21,8 @@ export const ERROR_CODES = { SITE_ID_INVALID_VALUE: 4, MEDIA_TYPE_NOT_SUPPORTED: 5, PUBLISHER_ID_INVALID_VALUE: 6, + INVALID_RATELIMIT: 7, + PLACEMENT_ID_INVALID_VALUE: 8, }; const endpoints = { @@ -30,29 +32,29 @@ const endpoints = { timeout: `${BASE_URL}/notice/timeout`, error: `${BASE_URL}/notice/error`, } -} +}; export function setEndPoints(env = null, path = '', mediaType = BANNER) { switch (env) { case 'local': - endpoints.request = mediaType === BANNER ? `${LOCAL_BASE_URL}${path}/bid/request` : `${LOCAL_BASE_URL}${path}/bid/videorequest` - endpoints.notice.win = `${LOCAL_BASE_URL}${path}/notice/win` - endpoints.notice.error = `${LOCAL_BASE_URL}${path}/notice/error` - endpoints.notice.timeout = `${LOCAL_BASE_URL}${path}/notice/timeout` + endpoints.request = mediaType === BANNER ? `${LOCAL_BASE_URL}${path}/bid/request` : `${LOCAL_BASE_URL}${path}/bid/videorequest`; + endpoints.notice.win = `${LOCAL_BASE_URL}${path}/notice/win`; + endpoints.notice.error = `${LOCAL_BASE_URL}${path}/notice/error`; + endpoints.notice.timeout = `${LOCAL_BASE_URL}${path}/notice/timeout`; break; case 'main': - endpoints.request = mediaType === BANNER ? `${BASE_URL}${path}/bid/request` : `${BASE_URL}${path}/bid/videorequest` - endpoints.notice.win = `${BASE_URL}${path}/notice/win` - endpoints.notice.error = `${BASE_URL}${path}/notice/error` - endpoints.notice.timeout = `${BASE_URL}${path}/notice/timeout` + endpoints.request = mediaType === BANNER ? `${BASE_URL}${path}/bid/request` : `${BASE_URL}${path}/bid/videorequest`; + endpoints.notice.win = `${BASE_URL}${path}/notice/win`; + endpoints.notice.error = `${BASE_URL}${path}/notice/error`; + endpoints.notice.timeout = `${BASE_URL}${path}/notice/timeout`; break; } - return endpoints + return endpoints; } config.getConfig('aidem', function (config) { - if (config.aidem.env) { setEndPoints(config.aidem.env, config.aidem.path, config.aidem.mediaType) } -}) + if (config.aidem.env) { setEndPoints(config.aidem.env, config.aidem.path, config.aidem.mediaType); } +}); // AIDEM Custom FN function recur(obj) { @@ -121,8 +123,8 @@ function getDevice() { function getRegs() { let regs = {}; - const consentManagement = config.getConfig('consentManagement') - const coppa = config.getConfig('coppa') + const consentManagement = config.getConfig('consentManagement'); + const coppa = config.getConfig('coppa'); if (consentManagement && !!(consentManagement.gdpr)) { deepSetValue(regs, 'gdpr_applies', !!consentManagement.gdpr); } else { @@ -145,11 +147,15 @@ function getRegs() { } function getPageUrl(bidderRequest) { - return bidderRequest?.refererInfo?.page + return bidderRequest?.refererInfo?.page; } function buildWinNotice(bid) { + const params = bid.params[0]; return { + publisherId: params.publisherId, + siteId: params.siteId, + placementId: params.placementId, burl: deepAccess(bid, 'meta.burl'), cpm: bid.cpm, currency: bid.currency, @@ -161,7 +167,7 @@ function buildWinNotice(bid) { ttl: bid.ttl, requestTimestamp: bid.requestTimestamp, responseTimestamp: bid.responseTimestamp, - } + }; } function buildErrorNotice(prebidErrorResponse) { @@ -171,29 +177,29 @@ function buildErrorNotice(prebidErrorResponse) { auctionId: prebidErrorResponse.auctionId, bidderRequestId: prebidErrorResponse.bidderRequestId, metrics: {} - } + }; } function hasValidFloor(obj) { - if (!obj) return false - const hasValue = !isNaN(Number(obj.value)) - const hasCurrency = contains(AVAILABLE_CURRENCIES, obj.currency) - return hasValue && hasCurrency + if (!obj) return false; + const hasValue = !isNaN(Number(obj.value)); + const hasCurrency = contains(AVAILABLE_CURRENCIES, obj.currency); + return hasValue && hasCurrency; } function getMediaType(bidRequest) { - if ((bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('video')) || bidRequest.params.hasOwnProperty('video')) { return VIDEO } - return BANNER + if ((bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('video')) || bidRequest.params.hasOwnProperty('video')) { return VIDEO; } + return BANNER; } function getPrebidRequestFields(bidderRequest, bidRequests) { - const payload = {} + const payload = {}; // Base Payload Data deepSetValue(payload, 'id', bidderRequest.auctionId); // Impressions - setPrebidImpressionObject(bidRequests, payload) + setPrebidImpressionObject(bidRequests, payload); // Device - deepSetValue(payload, 'device', getDevice()) + deepSetValue(payload, 'device', getDevice()); // Timeout deepSetValue(payload, 'tmax', bidderRequest.timeout); // Currency @@ -203,13 +209,13 @@ function getPrebidRequestFields(bidderRequest, bidRequests) { // Privacy Regs deepSetValue(payload, 'regs', getRegs()); // Site - setPrebidSiteObject(bidderRequest, payload) + setPrebidSiteObject(bidderRequest, payload); // Environment - setPrebidRequestEnvironment(payload) + setPrebidRequestEnvironment(payload); // AT auction type deepSetValue(payload, 'at', 1); - return payload + return payload; } function setPrebidImpressionObject(bidRequests, payload) { @@ -220,24 +226,24 @@ function setPrebidImpressionObject(bidRequests, payload) { deepSetValue(impressionObject, 'id', bidRequest.bidId); // Transaction id deepSetValue(impressionObject, 'tid', deepAccess(bidRequest, 'transactionId')); - // Placement id + // placement id deepSetValue(impressionObject, 'tagid', deepAccess(bidRequest, 'params.placementId', null)); // Publisher id deepSetValue(payload, 'site.publisher.id', deepAccess(bidRequest, 'params.publisherId')); // Site id deepSetValue(payload, 'site.id', deepAccess(bidRequest, 'params.siteId')); - const mediaType = getMediaType(bidRequest) + const mediaType = getMediaType(bidRequest); switch (mediaType) { case 'banner': - setPrebidImpressionObjectBanner(bidRequest, impressionObject) + setPrebidImpressionObjectBanner(bidRequest, impressionObject); break; case 'video': - setPrebidImpressionObjectVideo(bidRequest, impressionObject) + setPrebidImpressionObjectVideo(bidRequest, impressionObject); break; } // Floor (optional) - setPrebidImpressionObjectFloor(bidRequest, impressionObject) + setPrebidImpressionObjectFloor(bidRequest, impressionObject); impressionObject.imp_ext = {}; @@ -272,10 +278,10 @@ function setPrebidRequestEnvironment(payload) { } function setPrebidImpressionObjectFloor(bidRequest, impressionObject) { - const floor = deepAccess(bidRequest, 'params.floor') + const floor = deepAccess(bidRequest, 'params.floor'); if (hasValidFloor(floor)) { - deepSetValue(impressionObject, 'floor.value', floor.value) - deepSetValue(impressionObject, 'floor.currency', floor.currency) + deepSetValue(impressionObject, 'floor.value', floor.value); + deepSetValue(impressionObject, 'floor.currency', floor.currency); } } @@ -322,7 +328,7 @@ function getPrebidResponseBidObject(openRTBResponseBidObject) { deepSetValue(prebidResponseBidObject, 'currency', openRTBResponseBidObject.cur ? openRTBResponseBidObject.cur.toUpperCase() : DEFAULT_CURRENCY); deepSetValue(prebidResponseBidObject, 'width', openRTBResponseBidObject.w); deepSetValue(prebidResponseBidObject, 'height', openRTBResponseBidObject.h); - deepSetValue(prebidResponseBidObject, 'dealId', openRTBResponseBidObject.dealid) + deepSetValue(prebidResponseBidObject, 'dealId', openRTBResponseBidObject.dealid); deepSetValue(prebidResponseBidObject, 'netRevenue', true); deepSetValue(prebidResponseBidObject, 'ttl', 60000); @@ -335,13 +341,13 @@ function getPrebidResponseBidObject(openRTBResponseBidObject) { deepSetValue(prebidResponseBidObject, 'mediaType', BANNER); deepSetValue(prebidResponseBidObject, 'ad', openRTBResponseBidObject.adm); } - setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) - return prebidResponseBidObject + setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject); + return prebidResponseBidObject; } function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) { logInfo('AIDEM Bid Adapter meta', openRTBResponseBidObject); - deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', openRTBResponseBidObject.adomain); + deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', deepAccess(openRTBResponseBidObject, 'meta.advertiserDomains')); if (openRTBResponseBidObject.cat && Array.isArray(openRTBResponseBidObject.cat)) { const primaryCatId = openRTBResponseBidObject.cat.shift(); deepSetValue(prebidResponseBidObject, 'meta.primaryCatId', primaryCatId); @@ -357,28 +363,28 @@ function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponse } function hasValidMediaType(bidRequest) { - const supported = hasBannerMediaType(bidRequest) || hasVideoMediaType(bidRequest) + const supported = hasBannerMediaType(bidRequest) || hasVideoMediaType(bidRequest); if (!supported) { logError('AIDEM Bid Adapter: media type not supported', { bidder: BIDDER_CODE, code: ERROR_CODES.MEDIA_TYPE_NOT_SUPPORTED }); } - return supported + return supported; } function hasBannerMediaType(bidRequest) { - return !!deepAccess(bidRequest, 'mediaTypes.banner') + return !!deepAccess(bidRequest, 'mediaTypes.banner'); } function hasVideoMediaType(bidRequest) { - return !!deepAccess(bidRequest, 'mediaTypes.video') + return !!deepAccess(bidRequest, 'mediaTypes.video'); } function hasValidBannerMediaType(bidRequest) { - const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes') + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); if (!sizes) { logError('AIDEM Bid Adapter: media type sizes missing', { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); return false; } - return true + return true; } function hasValidVideoMediaType(bidRequest) { @@ -387,23 +393,38 @@ function hasValidVideoMediaType(bidRequest) { logError('AIDEM Bid Adapter: media type playerSize missing', { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); return false; } - return true + return true; } function hasValidVideoParameters(bidRequest) { - let valid = true + let valid = true; const adUnitsParameters = deepAccess(bidRequest, 'mediaTypes.video'); const bidderParameter = deepAccess(bidRequest, 'params.video'); for (let property of REQUIRED_VIDEO_PARAMS) { - const hasAdUnitParameter = adUnitsParameters.hasOwnProperty(property) - const hasBidderParameter = bidderParameter && bidderParameter.hasOwnProperty(property) + const hasAdUnitParameter = adUnitsParameters.hasOwnProperty(property); + const hasBidderParameter = bidderParameter && bidderParameter.hasOwnProperty(property); if (!hasAdUnitParameter && !hasBidderParameter) { logError(`AIDEM Bid Adapter: ${property} is not included in either the adunit or params level`, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); - valid = false + valid = false; } } - return valid + return valid; +} + +function passesRateLimit(bidRequest) { + const rateLimit = deepAccess(bidRequest, 'params.rateLimit', 1); + if (!isNumber(rateLimit) || rateLimit > 1 || rateLimit < 0) { + logError('AIDEM Bid Adapter: invalid rateLimit (must be a number between 0 and 1)', { bidder: BIDDER_CODE, code: ERROR_CODES.INVALID_RATELIMIT }); + return false; + } + if (rateLimit !== 1) { + const randomRateValue = Math.random(); + if (randomRateValue > rateLimit) { + return false; + } + } + return true; } function hasValidParameters(bidRequest) { @@ -421,7 +442,7 @@ function hasValidParameters(bidRequest) { return false; } - return true + return true; } export const spec = { @@ -431,28 +452,32 @@ export const spec = { logInfo('bid: ', bidRequest); // check if request has valid mediaTypes - if (!hasValidMediaType(bidRequest)) return false + if (!hasValidMediaType(bidRequest)) return false; // check if request has valid media type parameters at adUnit level if (hasBannerMediaType(bidRequest) && !hasValidBannerMediaType(bidRequest)) { - return false + return false; } if (hasVideoMediaType(bidRequest) && !hasValidVideoMediaType(bidRequest)) { - return false + return false; } if (hasVideoMediaType(bidRequest) && !hasValidVideoParameters(bidRequest)) { - return false + return false; } - return hasValidParameters(bidRequest) + if (!hasValidParameters(bidRequest)) { + return false; + } + + return passesRateLimit(bidRequest); }, buildRequests: function(validBidRequests, bidderRequest) { logInfo('validBidRequests: ', validBidRequests); logInfo('bidderRequest: ', bidderRequest); - const prebidRequest = getPrebidRequestFields(bidderRequest, validBidRequests) + const prebidRequest = getPrebidRequestFields(bidderRequest, validBidRequests); const payloadString = JSON.stringify(prebidRequest); return { @@ -474,7 +499,7 @@ export const spec = { return; } logInfo('CPM OK'); - const bid = getPrebidResponseBidObject(bidObject) + const bid = getPrebidResponseBidObject(bidObject); bids.push(bid); }); return bids; @@ -483,14 +508,14 @@ export const spec = { onBidWon: function(bid) { // Bidder specific code logInfo('onBidWon bid: ', bid); - const notice = buildWinNotice(bid) + const notice = buildWinNotice(bid); ajax(endpoints.notice.win, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); }, onBidderError: function({ bidderRequest }) { // Bidder specific code - const notice = buildErrorNotice(bidderRequest) + const notice = buildErrorNotice(bidderRequest); ajax(endpoints.notice.error, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); }, -} +}; registerBidder(spec); diff --git a/modules/aidemBidAdapter.md b/modules/aidemBidAdapter.md index 342a264da01..b59014c76ed 100644 --- a/modules/aidemBidAdapter.md +++ b/modules/aidemBidAdapter.md @@ -14,11 +14,12 @@ This module is GDPR and CCPA compliant, and no 3rd party userIds are allowed. ## Global Bid Params -| Name | Scope | Description | Example | Type | -|---------------|----------|---------------------|---------------|----------| -| `siteId` | required | Unique site ID | `'ABCDEF'` | `String` | -| `publisherId` | required | Unique publisher ID | `'ABCDEF'` | `String` | -| `placementId` | optional | Unique publisher tag ID | `'ABCDEF'` | `String` | +| Name | Scope | Description | Example | Type | +|---------------|----------|-------------------------|------------|----------| +| `siteId` | required | Unique site ID | `'ABCDEF'` | `String` | +| `publisherId` | required | Unique publisher ID | `'ABCDEF'` | `String` | +| `placementId` | optional | Unique publisher tag ID | `'ABCDEF'` | `String` | +| `rateLimit` | optional | Limit the volume sent to AIDEM. Must be between 0 and 1 | `0.6` | `Number` | ### Banner Bid Params @@ -67,7 +68,8 @@ var adUnits = [{ bids: [{ bidder: 'aidem', params: { - siteId: 'prebid-test-site', + siteId: 'prebid-test-siteId', + publisherId: 'prebid-test-publisherId', }, }] }]; @@ -90,7 +92,8 @@ var adUnits = [{ bids: [{ bidder: 'aidem', params: { - siteId: 'prebid-test-site', + siteId: 'prebid-test-siteId', + publisherId: 'prebid-test-publisherId', }, }] }]; diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index 71edfcf82fb..f58e49eb364 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -13,7 +13,7 @@ const VALID_BIDS = [ params: { siteId: '301491', publisherId: '3021491', - placementId: 13144370, + placementId: '13144370', }, mediaTypes: { banner: { @@ -26,7 +26,7 @@ const VALID_BIDS = [ params: { siteId: '301491', publisherId: '3021491', - placementId: 13144370, + placementId: '13144370', }, mediaTypes: { video: { @@ -110,7 +110,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', }, }, { @@ -126,7 +126,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', }, }, { @@ -143,7 +143,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', video: { size: [480, 40] } @@ -167,8 +167,8 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ } }, params: { - siteId: 1, - placementId: 13144370 + siteId: '1', + placementId: '13144370' }, src: 'client', transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' @@ -192,8 +192,8 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ } }, params: { - siteId: 1, - placementId: 13144370 + siteId: '1', + placementId: '13144370' }, src: 'client', transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' @@ -289,14 +289,25 @@ const WIN_NOTICE = { 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', - 'hb_adomain': 'tenutabene.it' + 'hb_adomain': 'example.com' }, + 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', 'currency': [ 'USD' ], 'mediaType': 'banner', + 'advertiserDomains': [ + 'abc.com' + ], 'size': '300x250', + 'params': [ + { + 'placementId': '13144370', + 'siteId': '23434', + 'publisherId': '7689670753' + } + ], 'width': 300, 'height': 250, 'status': 'rendered', @@ -365,6 +376,62 @@ describe('Aidem adapter', () => { deepSetValue(validVideoRequest.params, 'video.size', [640, 480]) expect(spec.isBidRequestValid(validVideoRequest)).to.be.true }); + + it('BANNER: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.true + }); + + it('BANNER: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.true + }); + + it('VIDEO: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); }); describe('buildRequests', () => { From 3d3b0d7dcdd18e67ca6a168275bc49a276af1580 Mon Sep 17 00:00:00 2001 From: Maxim Schuwalow <16665913+mschuwalow@users.noreply.github.com> Date: Mon, 6 Feb 2023 15:43:08 +0100 Subject: [PATCH 065/375] LiveIntent Id module: Update live-connect-js version (#9505) * Update live-connect-js version * fix eslint comment --- modules/liveIntentIdSystem.js | 2 +- package-lock.json | 14 +++++++------- package.json | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index de70b0eaccd..9f45daeea29 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -7,7 +7,7 @@ import { triggerPixel, logError } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js/esm/initializer.js'; +import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; diff --git a/package-lock.json b/package-lock.json index 587b7897394..dca60dbf37e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.5" + "live-connect-js": "^4.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16229,9 +16229,9 @@ "dev": true }, "node_modules/live-connect-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.5.tgz", - "integrity": "sha512-KqxE+V/050nK2tUx8PnAtQBOK4E29WVasQTrLkkCwSebmV5uqMu+VMcwhJSbnyh/g+GhDAE/LL9RB6X9vcmLrg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", + "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", "dependencies": { "tiny-hashes": "1.0.1" }, @@ -37865,9 +37865,9 @@ "dev": true }, "live-connect-js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-3.0.5.tgz", - "integrity": "sha512-KqxE+V/050nK2tUx8PnAtQBOK4E29WVasQTrLkkCwSebmV5uqMu+VMcwhJSbnyh/g+GhDAE/LL9RB6X9vcmLrg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", + "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", "requires": { "tiny-hashes": "1.0.1" } diff --git a/package.json b/package.json index cf3a7703aac..7163c732634 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "3.0.5" + "live-connect-js": "^4.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" From d694fe01fe9781ceecb41d71508638a733d1f61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Udi=20Talias=20=E2=9A=9B=EF=B8=8F?= Date: Mon, 6 Feb 2023 16:44:42 +0200 Subject: [PATCH 066/375] 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 --- modules/vidazooBidAdapter.js | 4 +++- test/spec/modules/vidazooBidAdapter_spec.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 96dcc182436..3f3bb66d8ae 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -27,6 +27,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; +export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { @@ -131,7 +132,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { bidRequestsCount: bidRequestsCount, bidderRequestsCount: bidderRequestsCount, bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout + bidderTimeout: bidderTimeout, + webSessionId: webSessionId }; appendUserIdsToRequestPayload(data, userId); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 134e3e66256..24d565805d3 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -13,6 +13,7 @@ import { getUniqueDealId, getNextDealId, getVidazooSessionId, + webSessionId } from 'modules/vidazooBidAdapter.js'; import * as utils from 'src/utils.js'; import {version} from 'package.json'; @@ -283,6 +284,7 @@ describe('VidazooBidAdapter', function () { uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), isStorageAllowed: true, + webSessionId: webSessionId, mediaTypes: { video: { api: [2], @@ -350,7 +352,8 @@ describe('VidazooBidAdapter', function () { isStorageAllowed: true, gpid: '1234567890', cat: ['IAB2'], - pagecat: ['IAB2-2'] + pagecat: ['IAB2-2'], + webSessionId: webSessionId } }); }); From 2d31a525ac1659a5d4ccd1aa1d5921e826ba70be Mon Sep 17 00:00:00 2001 From: anthonyjl92 Date: Mon, 6 Feb 2023 09:46:12 -0500 Subject: [PATCH 067/375] pass referer to ortb request (#9475) Co-authored-by: Anthony Lin --- modules/33acrossBidAdapter.js | 17 +++-- test/spec/modules/33acrossBidAdapter_spec.js | 69 +++++++++++++++----- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index d9524a281f8..e9901794ff9 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -167,7 +167,8 @@ function buildRequests(bidRequests, bidderRequest) { ttxSettings, gdprConsent, uspConsent, - pageUrl + pageUrl, + referer } = _buildRequestParams(bidRequests, bidderRequest); const groupedRequests = _buildRequestGroups(ttxSettings, bidRequests); @@ -181,6 +182,7 @@ function buildRequests(bidRequests, bidderRequest) { gdprConsent, uspConsent, pageUrl, + referer, ttxSettings }) ) @@ -199,7 +201,9 @@ function _buildRequestParams(bidRequests, bidderRequest) { const uspConsent = bidderRequest && bidderRequest.uspConsent; - const pageUrl = bidderRequest?.refererInfo?.page + const pageUrl = bidderRequest?.refererInfo?.page; + + const referer = bidderRequest?.refererInfo?.ref; adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); @@ -207,7 +211,8 @@ function _buildRequestParams(bidRequests, bidderRequest) { ttxSettings, gdprConsent, uspConsent, - pageUrl + pageUrl, + referer } } @@ -241,7 +246,7 @@ function _getMRAKey(bidRequest) { } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request -function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, ttxSettings }) { +function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, referer, ttxSettings }) { const ttxRequest = {}; const firstBidRequest = bidRequests[0]; const { siteId, test } = firstBidRequest.params; @@ -262,6 +267,10 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU ttxRequest.site.page = pageUrl; } + if (referer) { + ttxRequest.site.ref = referer; + } + ttxRequest.id = firstBidRequest.auctionId; if (gdprConsent.consentString) { diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 2680544d00b..3b3c05660df 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -218,6 +218,14 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withReferer = referer => { + Object.assign(ttxRequest.site, { + ref: referer + }); + + return this; + }; + this.withSchain = schain => { Object.assign(ttxRequest, { source: { @@ -1187,26 +1195,51 @@ describe('33acrossBidAdapter:', function () { }); }); - context('when referer value is available', function() { - it('returns corresponding server requests with site.page set', function() { - const bidderRequest = { - refererInfo: { - page: 'http://foo.com/bar' - } - }; + context('when refererInfo values are available', function() { + context('when refererInfo.page is defined', function() { + it('returns corresponding server requests with site.page set', function() { + const bidderRequest = { + refererInfo: { + page: 'http://foo.com/bar' + } + }; - const ttxRequest = new TtxRequestBuilder() - .withBanner() - .withProduct() - .withPageUrl('http://foo.com/bar') - .build(); - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withPageUrl('http://foo.com/bar') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + + context('when refererInfo.ref is defined', function() { + it('returns corresponding server requests with site.ref set', function() { + const bidderRequest = { + refererInfo: { + ref: 'google.com' + } + }; + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withReferer('google.com') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); }); }); @@ -1246,7 +1279,7 @@ describe('33acrossBidAdapter:', function () { }); context('when referer value is not available', function() { - it('returns corresponding server requests without site.page set', function() { + it('returns corresponding server requests without site.page and site.ref set', function() { const bidderRequest = { refererInfo: {} }; From e5d1c92259d6af59d8b756cbd03312f3d14df245 Mon Sep 17 00:00:00 2001 From: Scott Floam Date: Mon, 6 Feb 2023 11:46:45 -0500 Subject: [PATCH 068/375] 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 --- modules/freewheel-sspBidAdapter.js | 6 +++++- test/spec/modules/freewheel-sspBidAdapter_spec.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 764e3d6e6c0..b4d8f69d1b4 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -348,7 +348,11 @@ export const spec = { // Add schain object var schain = currentBidRequest.schain; if (schain) { - requestParams.schain = schain; + try { + requestParams.schain = JSON.stringify(schain); + } catch (error) { + logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the schain: ' + error); + } } var vastParams = currentBidRequest.params.vastUrlParams; diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 7ef576fc7ec..d1e0b055239 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -128,7 +128,7 @@ describe('freewheelSSP BidAdapter Test', () => { it('should return a properly formatted request with schain defined', function () { const request = spec.buildRequests(bidRequests); const payload = request[0].data; - expect(payload.schain).to.deep.equal(bidRequests[0].schain) + expect(payload.schain).to.deep.equal('{\"ver\":\"1.0\",\"complete\":1,\"nodes\":[{\"asi\":\"example.com\",\"sid\":\"0\",\"hp\":1,\"rid\":\"bidrequestid\",\"domain\":\"example.com\"}]}'); }); it('sends bid request to ENDPOINT via GET', () => { From d984cd865906fdfbdebdbfa7ff7ebbe9a215c486 Mon Sep 17 00:00:00 2001 From: Muki Seiler Date: Tue, 7 Feb 2023 15:25:43 +0100 Subject: [PATCH 069/375] Add GVLID to smartx (#9512) --- modules/smartxBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index bfc664180a3..d91b62729bc 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -10,8 +10,10 @@ import { } from '../src/mediaTypes.js'; const BIDDER_CODE = 'smartx'; const URL = 'https://bid.sxp.smartclip.net/bid/1000'; +const GVLID = 115; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [VIDEO], /** * Determines whether or not the given bid request is valid. From 58d79e5291ded734b36d106d2bfb14a0db685d82 Mon Sep 17 00:00:00 2001 From: Tomasz Kogut Date: Tue, 7 Feb 2023 17:46:43 +0100 Subject: [PATCH 070/375] Scattered Bid Adapter: New Adapter (#9295) * Scattered module skeleton * Review fixes * Remove netRevenue check * Fix test * Review fixes --- modules/scatteredBidAdapter.js | 72 ++++++ modules/scatteredBidAdapter.md | 36 +++ test/spec/modules/scatteredBidAdapter_spec.js | 210 ++++++++++++++++++ 3 files changed, 318 insertions(+) create mode 100644 modules/scatteredBidAdapter.js create mode 100644 modules/scatteredBidAdapter.md create mode 100644 test/spec/modules/scatteredBidAdapter_spec.js diff --git a/modules/scatteredBidAdapter.js b/modules/scatteredBidAdapter.js new file mode 100644 index 00000000000..47dc09cd1b2 --- /dev/null +++ b/modules/scatteredBidAdapter.js @@ -0,0 +1,72 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { deepAccess, logInfo } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; + +const BIDDER_CODE = 'scattered'; +const GVLID = 1179; +export const converter = ortbConverter({ + context: { + mediaType: BANNER, + ttl: 360, + netRevenue: true + } +}) + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + // 1. + isBidRequestValid: function (bid) { + const bidderDomain = deepAccess(bid, 'params.bidderDomain') + if (bidderDomain === undefined || bidderDomain === '') { + return false + } + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes') + if (sizes === undefined || sizes.length < 1) { + return false + } + + return true + }, + + // 2. + buildRequests: function (bidRequests, bidderRequest) { + return { + method: 'POST', + url: 'https://' + getKeyOnAny(bidRequests, 'params.bidderDomain'), + data: converter.toORTB({ bidderRequest, bidRequests }), + options: { + contentType: 'application/json' + }, + }; + }, + + // 3. + interpretResponse: function (response, request) { + if (!response.body) return; + return converter.fromORTB({ response: response.body, request: request.data }).bids; + }, + + // 4 + onBidWon: function (bid) { + logInfo('onBidWon', bid) + } +} + +function getKeyOnAny(collection, key) { + for (let i = 0; i < collection.length; i++) { + const result = deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +registerBidder(spec); diff --git a/modules/scatteredBidAdapter.md b/modules/scatteredBidAdapter.md new file mode 100644 index 00000000000..031d953e32b --- /dev/null +++ b/modules/scatteredBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Scattered Adapter +Module Type: Bidder Adapter +Maintainer: office@scattered.pl +``` + +# Description + +Module that connects to Scattered's demand sources. +It uses OpenRTB standard to communicate between the adapter and bidding servers. + +# Test Parameters + +```javascript + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "scattered", + params: { + bidderDomain: "prebid-test.scattered.eu/bid", + test: 0 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/scatteredBidAdapter_spec.js b/test/spec/modules/scatteredBidAdapter_spec.js new file mode 100644 index 00000000000..609a570f579 --- /dev/null +++ b/test/spec/modules/scatteredBidAdapter_spec.js @@ -0,0 +1,210 @@ +import { spec, converter } from 'modules/scatteredBidAdapter.js'; +import { assert } from 'chai'; +import { config } from 'src/config.js'; +import { deepClone, mergeDeep } from '../../../src/utils'; +describe('Scattered adapter', function () { + describe('isBidRequestValid', function () { + // A valid bid + let validBid = { + bidder: 'scattered', + mediaTypes: { + banner: { + sizes: [[300, 250], [760, 400]] + } + }, + params: { + bidderDomain: 'https://prebid.scattered.eu', + test: 0 + } + }; + + // Because this valid bid is modified to create invalid bids in following tests we first check it. + // We must be sure it is a valid one, not to get false negatives. + it('should accept a valid bid', function () { + assert.isTrue(spec.isBidRequestValid(validBid)); + }); + + it('should skip if bidderDomain info is missing', function () { + let bid = deepClone(validBid); + + delete bid.params.bidderDomain; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should expect at least one banner size', function () { + let bid = deepClone(validBid); + + delete bid.mediaTypes.banner; + assert.isFalse(spec.isBidRequestValid(bid)); + + // empty sizes array + bid.mediaTypes = { + banner: { + sizes: [] + } + }; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests', function () { + let arrayOfValidBidRequests, validBidderRequest; + + beforeEach(function () { + arrayOfValidBidRequests = [{ + bidder: 'scattered', + params: { + bidderDomain: 'https://prebid.scattered.eu', + test: 0 + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [760, 400]] + }, + adUnitCode: 'test-div', + transactionId: '32d09c47-c6b8-40b0-9605-2e251a472ea4', + bidId: '21adc5d8765aa1', + bidderRequestId: '130728f7662afc', + auctionId: 'b4a45a23-8371-4d87-9308-39146b29ca32', + }, + }]; + + validBidderRequest = { + bidderCode: 'scattered', + auctionId: 'b4a45a23-8371-4d87-9308-39146b29ca32', + gdprConsent: { consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', gdprApplies: true }, + refererInfo: { + domain: 'localhost', + page: 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + }, + ortb2: { + site: { + publisher: { + name: 'publisher1 INC.' + } + } + } + }; + }); + + it('should validate request format', function () { + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + assert.equal(request.method, 'POST'); + assert.deepEqual(request.options, { contentType: 'application/json' }); + assert.ok(request.data); + }); + + it('has the right fields filled', function () { + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + const bidderRequest = request.data; + assert.equal(bidderRequest.id, validBidderRequest.auctionId); + assert.ok(bidderRequest.site); + assert.ok(bidderRequest.device); + assert.ok(bidderRequest.source); + assert.lengthOf(bidderRequest.imp, 1); + }); + + it('should configure the site object', function () { + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + const site = request.data.site; + assert.equal(site.domain, 'localhost'); + assert.equal(site.page, 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true'); + }); + + it('should configure site with ortb2', function () { + mergeDeep(validBidderRequest.ortb2.site, { + id: '876', + publisher: { + domain: 'publisher1.eu' + } + }); + + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + const site = request.data.site; + assert.deepEqual(site, { + domain: 'localhost', + id: '876', + page: 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + publisher: { + domain: 'publisher1.eu', + name: 'publisher1 INC.' + } + }); + }); + + it('should send device info', function () { + it('should send info about device', function () { + config.setConfig({ + device: { w: 375, h: 273 } + }); + + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + + assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 375); + assert.equal(request.device.h, 273); + }); + }) + }) +}) + +describe('interpretResponse', function () { + const serverResponse = { + body: { + id: 'b4a45a23-8371-4d87-9308-39146b29ca32', + bidid: '11111111-2222-2222-2222-333333333333', + cur: 'PLN', + seatbid: [{ + bid: [ + { + id: '234234-234234-234234', // bidder generated + impid: '123', + price: '34.2', + nurl: 'https://scattered.eu/nurl', + adm: '
', + cpm: '34.2', + creativeId: '2345-2345-23', + currency: 'PLN', + height: 456, + width: 345, + mediaType: 'banner', + requestId: '123', + ttl: 360, + }; + expect(results.length).to.equal(1); + sinon.assert.match(results[0], expected); + }); +}); From 324764f91072ef65e77b15f88e05252f4a7b08f9 Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Tue, 7 Feb 2023 13:38:04 -0500 Subject: [PATCH 071/375] update amxIdSystem (#9508) --- modules/amxIdSystem.md | 12 ++---------- modules/aolBidAdapter.js | 2 +- modules/userId/eids.js | 2 +- modules/yahoosspBidAdapter.js | 2 +- test/spec/modules/aolBidAdapter_spec.js | 4 ++-- test/spec/modules/eids_spec.js | 2 +- test/spec/modules/tripleliftBidAdapter_spec.js | 4 ++-- test/spec/modules/userId_spec.js | 2 +- test/spec/modules/yahoosspBidAdapter_spec.js | 2 +- 9 files changed, 12 insertions(+), 20 deletions(-) diff --git a/modules/amxIdSystem.md b/modules/amxIdSystem.md index 9de93c761a1..f67fefe261e 100644 --- a/modules/amxIdSystem.md +++ b/modules/amxIdSystem.md @@ -1,6 +1,6 @@ -# AMX RTB ID +# AMX ID -For help adding this module, please contact [prebid@amxrtb.com](prebid@amxrtb.com). +For help adding this module, please contact [info@amxdt.net](info@amxdt.net). ### Prebid Configuration @@ -41,11 +41,3 @@ The following settings are available for the `storage` property in the `userSync | name | Required | String | Where the ID will be stored | `"amxId"` | | type | Required | String | This must be `"html5"` | `"html5"` | | expires | Required | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` | - -### Params - -The following options are available in the `params` property in `userSync.userIds[]`: - -| Param under `params` | Scope | Type | Description | Example | -| -------------------- | -------- | ------ | ------------------------------------------------------------------------- | ---------------- | -| tagId | Optional | String | Your AMX tagId (optional) | `cHJlYmlkLm9yZw` | diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index 6b471ac6de2..d15a5434e0c 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -35,7 +35,7 @@ const SUPPORTED_USER_ID_SOURCES = [ 'adserver.org', 'adtelligent.com', 'akamai.com', - 'amxrtb.com', + 'amxdt.net', 'audigent.com', 'britepool.com', 'criteo.com', diff --git a/modules/userId/eids.js b/modules/userId/eids.js index bae5071e4b2..199168e12bb 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -282,7 +282,7 @@ export const USER_IDS_CONFIG = { }, amxId: { - source: 'amxrtb.com', + source: 'amxdt.net', atype: 1, }, diff --git a/modules/yahoosspBidAdapter.js b/modules/yahoosspBidAdapter.js index 9dd635a990f..40677f90064 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahoosspBidAdapter.js @@ -24,7 +24,7 @@ const SUPPORTED_USER_ID_SOURCES = [ 'adserver.org', 'adtelligent.com', 'akamai.com', - 'amxrtb.com', + 'amxdt.net', 'audigent.com', 'britepool.com', 'criteo.com', diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 471c76f55cf..92246f76c7a 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -84,7 +84,7 @@ describe('AolAdapter', function () { 'admixer.net': '100', 'adserver.org': '200', 'adtelligent.com': '300', - 'amxrtb.com': '500', + 'amxdt.net': '500', 'audigent.com': '600', 'britepool.com': '700', 'criteo.com': '800', @@ -110,7 +110,7 @@ describe('AolAdapter', function () { const USER_ID_DATA = { admixerId: SUPPORTED_USER_ID_SOURCES['admixer.net'], adtelligentId: SUPPORTED_USER_ID_SOURCES['adtelligent.com'], - amxId: SUPPORTED_USER_ID_SOURCES['amxrtb.com'], + amxId: SUPPORTED_USER_ID_SOURCES['amxdt.net'], britepoolid: SUPPORTED_USER_ID_SOURCES['britepool.com'], criteoId: SUPPORTED_USER_ID_SOURCES['criteo.com'], connectid: SUPPORTED_USER_ID_SOURCES['verizonmedia.com'], diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 78c8f2b3148..13f89b054bd 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -392,7 +392,7 @@ describe('eids array generation for known sub-modules', function() { const [eid] = createEidsArray(userId); expect(eid).to.deep.equal({ - source: 'amxrtb.com', + source: 'amxdt.net', uids: [{ atype: 1, id, diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 0447cb4d5d6..a20719d89e5 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -725,11 +725,11 @@ describe('triplelift adapter', function () { it('should add amxRtbId to the payload if included', function () { const id = 'Ok9JQkBM-UFlAXEZQ-UUNBQlZOQzgrUFhW-UUNBQkRQTUBPQVpVWVxNXlZUUF9AUFhAUF9PXFY/'; - bidRequests[0].userIdAsEids = [{ source: 'amxrtb.com', uids: [{ id }] }]; + bidRequests[0].userIdAsEids = [{ source: 'amxdt.net', uids: [{ id }] }]; const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.exist; - expect(payload.user).to.deep.equal({ext: {eids: [{source: 'amxrtb.com', uids: [{id, ext: {rtiPartner: 'amxrtb.com'}}]}]}}); + expect(payload.user).to.deep.equal({ext: {eids: [{source: 'amxdt.net', uids: [{id, ext: {rtiPartner: 'amxdt.net'}}]}]}}); }); it('should add tdid, idl_env and criteoId to the payload if both are included', function () { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 5403d842e02..bf27ef0ff81 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1298,7 +1298,7 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.amxId'); expect(bid.userId.amxId).to.equal('test_amxid_id'); expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'amxrtb.com', + source: 'amxdt.net', uids: [{ id: 'test_amxid_id', atype: 1, diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahoosspBidAdapter_spec.js index 01583ac30dc..a326b22ecb4 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahoosspBidAdapter_spec.js @@ -872,7 +872,7 @@ describe('YahooSSP Bid Adapter:', () => { expect(data.user.ext.eids).to.deep.equal([ {source: 'admixer.net', uids: [{id: 'admixerId_FROM_USER_ID_MODULE', atype: 3}]}, {source: 'adtelligent.com', uids: [{id: 'adtelligentId_FROM_USER_ID_MODULE', atype: 3}]}, - {source: 'amxrtb.com', uids: [{id: 'amxId_FROM_USER_ID_MODULE', atype: 1}]}, + {source: 'amxdt.net', uids: [{id: 'amxId_FROM_USER_ID_MODULE', atype: 1}]}, {source: 'britepool.com', uids: [{id: 'britepoolid_FROM_USER_ID_MODULE', atype: 3}]}, {source: 'deepintent.com', uids: [{id: 'deepintentId_FROM_USER_ID_MODULE', atype: 3}]}, {source: 'epsilon.com', uids: [{id: 'publinkId_FROM_USER_ID_MODULE', atype: 3}]}, From ec182d0e974c6b745855b259e9655fb23a7be044 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 7 Feb 2023 12:34:19 -0700 Subject: [PATCH 072/375] Scattered bid adapter: fix tests (#9514) --- test/spec/modules/scatteredBidAdapter_spec.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/spec/modules/scatteredBidAdapter_spec.js b/test/spec/modules/scatteredBidAdapter_spec.js index 609a570f579..7f0b13bc07b 100644 --- a/test/spec/modules/scatteredBidAdapter_spec.js +++ b/test/spec/modules/scatteredBidAdapter_spec.js @@ -99,7 +99,6 @@ describe('Scattered adapter', function () { const bidderRequest = request.data; assert.equal(bidderRequest.id, validBidderRequest.auctionId); assert.ok(bidderRequest.site); - assert.ok(bidderRequest.device); assert.ok(bidderRequest.source); assert.lengthOf(bidderRequest.imp, 1); }); @@ -107,24 +106,25 @@ describe('Scattered adapter', function () { it('should configure the site object', function () { let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); const site = request.data.site; - assert.equal(site.domain, 'localhost'); - assert.equal(site.page, 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true'); + assert.equal(site.publisher.name, validBidderRequest.ortb2.site.publisher.name) }); it('should configure site with ortb2', function () { - mergeDeep(validBidderRequest.ortb2.site, { - id: '876', - publisher: { - domain: 'publisher1.eu' + const req = mergeDeep({}, validBidderRequest, { + ortb2: { + site: { + id: '876', + publisher: { + domain: 'publisher1.eu' + } + } } }); - let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + let request = spec.buildRequests(arrayOfValidBidRequests, req); const site = request.data.site; assert.deepEqual(site, { - domain: 'localhost', id: '876', - page: 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', publisher: { domain: 'publisher1.eu', name: 'publisher1 INC.' From 723560d7b1e9d59f6c5458d16decc3ece1a3d568 Mon Sep 17 00:00:00 2001 From: moquity <33487117+moquity@users.noreply.github.com> Date: Tue, 7 Feb 2023 22:20:27 +0200 Subject: [PATCH 073/375] 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) --- .../gpt/neuwoRtdProvider_example.html | 190 ++++++++++++++++++ modules/neuwoRtdProvider.js | 171 ++++++++++++++++ modules/neuwoRtdProvider.md | 40 ++++ test/spec/modules/neuwoRtdProvider_spec.js | 123 ++++++++++++ 4 files changed, 524 insertions(+) create mode 100644 integrationExamples/gpt/neuwoRtdProvider_example.html create mode 100644 modules/neuwoRtdProvider.js create mode 100644 modules/neuwoRtdProvider.md create mode 100644 test/spec/modules/neuwoRtdProvider_spec.js diff --git a/integrationExamples/gpt/neuwoRtdProvider_example.html b/integrationExamples/gpt/neuwoRtdProvider_example.html new file mode 100644 index 00000000000..f33b9f94c67 --- /dev/null +++ b/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -0,0 +1,190 @@ + + + + + + + + + + +

Basic Prebid.js Example using neuwoRtdProvider

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

Add token and url to use for Neuwo extension configuration

+ + + +
+ +
Div-1
+
+ Ad spot div-1: This content will be replaced by prebid.js and/or related components once you click "Update" +
+ +
+ +
Div-2
+
+ Ad spot div-2: Replaces this text as well, if everything goes to plan + + +
+ + + \ No newline at end of file diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js new file mode 100644 index 00000000000..a2549a243d1 --- /dev/null +++ b/modules/neuwoRtdProvider.js @@ -0,0 +1,171 @@ +import { deepAccess, deepSetValue, generateUUID, logError, logInfo, mergeDeep } from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; + +export const DATA_PROVIDER = 'neuwo.ai'; +const SEGTAX_IAB = 6 // IAB - Content Taxonomy version 2 +const CATTAX_IAB = 6 // IAB Tech Lab Content Taxonomy 2.2 +const RESPONSE_IAB_TIER_1 = 'marketing_categories.iab_tier_1' +const RESPONSE_IAB_TIER_2 = 'marketing_categories.iab_tier_2' + +function init(config = {}, userConsent = '') { + config.params = config.params || {} + // ignore module if publicToken is missing (module setup failure) + if (!config.params.publicToken) { + logError('publicToken missing', 'NeuwoRTDModule', 'config.params.publicToken') + return false; + } + return true; +} + +export function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + config.params = config.params || {}; + logInfo('NeuwoRTDModule', 'starting getBidRequestData') + + const wrappedArgUrl = encodeURIComponent(config.params.argUrl || getRefererInfo().page); + const url = 'https://m1apidev.neuwo.ai/edge/GetAiTopics?' + [ + 'token=' + config.params.publicToken, + 'lang=en', + 'url=' + wrappedArgUrl + ].join('&') + const billingId = generateUUID(); + + const success = (responseContent) => { + logInfo('NeuwoRTDModule', 'GetAiTopics: response', responseContent) + try { + const jsonContent = JSON.parse(responseContent); + if (jsonContent.marketing_categories) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { type: 'request', billingId, vendor: neuwoRtdModule.name }) + } + injectTopics(jsonContent, reqBidsConfigObj, billingId) + } catch (ex) { + logError('NeuwoRTDModule', 'Response to JSON parse error', ex) + } + callback() + } + + const error = (err) => { + logError('xhr error', null, err); + callback() + } + + ajax(url, {success, error}, null, { + // could assume Origin header is set, or + // customHeaders: { 'Origin': 'Origin' } + }) +} + +export function addFragment(base, path, addition) { + const container = {} + deepSetValue(container, path, addition) + mergeDeep(base, container) +} + +/** + * Concatenate a base array and an array within an object + * non-array bases will be arrays, non-arrays at object key will be discarded + * @param {array} base base array to add to + * @param {object} source object to get an array from + * @param {string} key dot-notated path to array within object + * @returns base + source[key] if that's an array + */ +function combineArray(base, source, key) { + if (Array.isArray(base) === false) base = [] + const addition = deepAccess(source, key, []) + if (Array.isArray(addition)) return base.concat(addition) + else return base +} + +export function injectTopics(topics, bidsConfig) { + topics = topics || {} + + // join arrays of IAB category details to single array + const combinedTiers = combineArray( + combineArray([], topics, RESPONSE_IAB_TIER_1), + topics, RESPONSE_IAB_TIER_2) + + const segment = pickSegments(combinedTiers) + // effectively gets topics.marketing_categories.iab_tier_1, topics.marketing_categories.iab_tier_2 + // used as FPD segments content + + const IABSegments = { + name: DATA_PROVIDER, + ext: { segtax: SEGTAX_IAB }, + segment + } + + addFragment(bidsConfig.ortb2Fragments.global, 'site.content.data', [IABSegments]) + + // upgrade category taxonomy to IAB 2.2, inject result to page categories + if (segment.length > 0) { + addFragment(bidsConfig.ortb2Fragments.global, 'site.cattax', CATTAX_IAB) + addFragment(bidsConfig.ortb2Fragments.global, 'site.pagecat', segment.map(s => s.id)) + } + + logInfo('NeuwoRTDModule', 'injectTopics: post-injection bidsConfig', bidsConfig) +} + +/* eslint-disable object-property-newline */ +const D_IAB_ID = { // Content Taxonomy version 2.0 final release November 2017 [sic] (Taxonomy ID Mapping, IAB versions 2.0 - 2.2) + 'IAB19-1': '603', 'IAB6-1': '193', 'IAB5-2': '133', 'IAB20-1': '665', 'IAB20-2': '656', 'IAB23-2': '454', 'IAB3-2': '102', 'IAB20-3': '672', 'IAB8-5': '211', + 'IAB8-18': '211', 'IAB7-4': '288', 'IAB7-5': '233', 'IAB17-12': '484', 'IAB19-3': '608', 'IAB21-1': '442', 'IAB9-2': '248', 'IAB15-1': '456', 'IAB9-17': '265', 'IAB20-4': '658', + 'IAB2-3': '30', 'IAB2-1': '32', 'IAB17-1': '518', 'IAB2-2': '34', 'IAB2': '1', 'IAB8-2': '215', 'IAB17-2': '545', 'IAB17-26': '547', 'IAB9-3': '249', 'IAB18-1': '553', 'IAB20-5': '674', + 'IAB15-2': '465', 'IAB3-3': '119', 'IAB16-2': '423', 'IAB9-4': '259', 'IAB9-5': '270', 'IAB18-2': '574', 'IAB17-4': '549', 'IAB7-33': '312', 'IAB1-1': '42', 'IAB17-5': '485', 'IAB23-3': '458', + 'IAB20-6': '675', 'IAB3': '53', 'IAB20-7': '676', 'IAB19-5': '633', 'IAB20-9': '677', 'IAB9-6': '250', 'IAB17-6': '499', 'IAB2-4': '25', 'IAB9-7': '271', 'IAB4-11': '125', 'IAB4-1': '126', + 'IAB4': '123', 'IAB16-3': '424', 'IAB2-5': '18', 'IAB17-7': '486', 'IAB15-3': '466', 'IAB23-5': '459', 'IAB9-9': '260', 'IAB2-22': '19', 'IAB17-8': '500', 'IAB9-10': '261', 'IAB5-5': '137', + 'IAB9-11': '262', 'IAB2-21': '3', 'IAB19-2': '610', 'IAB19-8': '600', 'IAB19-9': '601', 'IAB3-5': '121', 'IAB9-15': '264', 'IAB2-6': '8', 'IAB2-7': '9', 'IAB22-2': '474', 'IAB17-9': '491', + 'IAB2-8': '10', 'IAB20-12': '678', 'IAB17-3': '492', 'IAB19-12': '611', 'IAB14-1': '188', 'IAB6-3': '194', 'IAB7-17': '316', 'IAB19-13': '612', 'IAB8-8': '217', 'IAB9-1': '205', 'IAB19-22': '613', + 'IAB8-9': '218', 'IAB14-2': '189', 'IAB16-4': '425', 'IAB9-12': '251', 'IAB5': '132', 'IAB6-9': '190', 'IAB19-15': '623', 'IAB17-17': '496', 'IAB20-14': '659', 'IAB6': '186', 'IAB20-26': '666', + 'IAB17-10': '510', 'IAB13-4': '396', 'IAB1-3': '201', 'IAB16-1': '426', 'IAB17-11': '511', 'IAB17-13': '511', 'IAB17-32': '511', 'IAB7-1': '225', 'IAB8': '210', 'IAB8-10': '219', 'IAB9-13': '266', + 'IAB10-4': '275', 'IAB9-14': '273', 'IAB15-8': '469', 'IAB15-4': '470', 'IAB17-15': '512', 'IAB3-7': '77', 'IAB19-16': '614', 'IAB3-8': '78', 'IAB2-10': '22', 'IAB2-12': '22', 'IAB2-11': '11', + 'IAB8-12': '221', 'IAB7-35': '223', 'IAB7-38': '223', 'IAB7-24': '296', 'IAB13-5': '411', 'IAB7-25': '234', 'IAB23-6': '460', 'IAB9': '239', 'IAB7-26': '235', 'IAB10': '274', 'IAB10-1': '278', + 'IAB10-2': '279', 'IAB19-17': '634', 'IAB10-5': '280', 'IAB5-10': '145', 'IAB5-11': '146', 'IAB20-17': '667', 'IAB17-16': '497', 'IAB20-18': '668', 'IAB3-9': '55', 'IAB1-4': '440', 'IAB17-18': '514', + 'IAB17-27': '515', 'IAB10-3': '282', 'IAB19-25': '618', 'IAB17-19': '516', 'IAB13-6': '398', 'IAB10-7': '283', 'IAB12-1': '382', 'IAB19-24': '624', 'IAB6-4': '195', 'IAB23-7': '461', 'IAB9-19': '252', + 'IAB4-4': '128', 'IAB4-5': '127', 'IAB23-8': '462', 'IAB10-8': '284', 'IAB5-8': '147', 'IAB16-5': '427', 'IAB11-2': '383', 'IAB12-3': '384', 'IAB3-10': '57', 'IAB2-13': '23', 'IAB9-20': '241', + 'IAB3-1': '58', 'IAB3-11': '58', 'IAB14-4': '191', 'IAB17-20': '520', 'IAB7-31': '228', 'IAB7-37': '301', 'IAB3-12': '107', 'IAB2-14': '13', 'IAB17-25': '519', 'IAB2-15': '27', 'IAB1-5': '324', + 'IAB1-6': '338', 'IAB9-16': '243', 'IAB13-8': '412', 'IAB12-2': '385', 'IAB9-21': '253', 'IAB8-6': '222', 'IAB7-32': '229', 'IAB2-16': '14', 'IAB17-23': '521', 'IAB13-9': '413', 'IAB17-24': '501', + 'IAB9-22': '254', 'IAB15-5': '244', 'IAB6-2': '196', 'IAB6-5': '197', 'IAB6-6': '198', 'IAB2-17': '24', 'IAB13-2': '405', 'IAB13': '391', 'IAB13-7': '410', 'IAB13-12': '415', 'IAB16': '422', + 'IAB9-23': '255', 'IAB7-36': '236', 'IAB15-6': '471', 'IAB2-18': '15', 'IAB11-4': '386', 'IAB1-2': '432', 'IAB5-9': '139', 'IAB6-7': '305', 'IAB5-12': '149', 'IAB5-13': '134', 'IAB19-4': '631', + 'IAB19-19': '631', 'IAB19-20': '631', 'IAB19-32': '631', 'IAB9-24': '245', 'IAB21': '441', 'IAB21-3': '451', 'IAB23': '453', 'IAB10-9': '276', 'IAB4-9': '130', 'IAB16-6': '429', 'IAB4-6': '129', + 'IAB13-10': '416', 'IAB2-19': '28', 'IAB17-28': '525', 'IAB9-25': '272', 'IAB17-29': '527', 'IAB17-30': '227', 'IAB17-31': '530', 'IAB22-1': '481', 'IAB15': '464', 'IAB9-26': '246', 'IAB9-27': '256', + 'IAB9-28': '267', 'IAB17-33': '502', 'IAB19-35': '627', 'IAB2-20': '4', 'IAB7-39': '307', 'IAB19-30': '605', 'IAB22': '473', 'IAB17-34': '503', 'IAB17-35': '531', 'IAB7-19': '309', 'IAB7-40': '310', + 'IAB19-6': '635', 'IAB7-41': '237', 'IAB17-36': '504', 'IAB17-44': '533', 'IAB20-23': '662', 'IAB15-7': '472', 'IAB20-24': '671', 'IAB5-14': '136', 'IAB6-8': '199', 'IAB17': '483', 'IAB9-29': '263', + 'IAB2-23': '5', 'IAB13-11': '414', 'IAB4-3': '395', 'IAB18': '552', 'IAB7-42': '311', 'IAB17-37': '505', 'IAB17-38': '537', 'IAB17-39': '538', 'IAB19-26': '636', 'IAB19': '596', 'IAB1-7': '640', + 'IAB17-40': '539', 'IAB7-43': '293', 'IAB20': '653', 'IAB8-16': '212', 'IAB8-17': '213', 'IAB16-7': '430', 'IAB9-30': '680', 'IAB17-41': '541', 'IAB17-42': '542', 'IAB17-43': '506', 'IAB15-10': '390', + 'IAB19-23': '607', 'IAB19-34': '629', 'IAB14-7': '165', 'IAB7-44': '231', 'IAB7-45': '238', 'IAB9-31': '257', 'IAB5-1': '135', 'IAB7-2': '301', 'IAB18-6': '580', 'IAB7-3': '297', 'IAB23-1': '453', + 'IAB8-1': '214', 'IAB7-6': '312', 'IAB7-7': '300', 'IAB7-8': '301', 'IAB13-1': '410', 'IAB7-9': '301', 'IAB15-9': '465', 'IAB7-10': '313', 'IAB3-4': '602', 'IAB20-8': '660', 'IAB8-3': '214', + 'IAB20-10': '660', 'IAB7-11': '314', 'IAB20-11': '660', 'IAB23-4': '459', 'IAB9-8': '270', 'IAB8-4': '214', 'IAB7-12': '306', 'IAB7-13': '313', 'IAB7-14': '287', 'IAB18-5': '575', 'IAB7-15': '315', + 'IAB8-7': '214', 'IAB19-11': '616', 'IAB7-16': '289', 'IAB7-18': '301', 'IAB7-20': '290', 'IAB20-13': '659', 'IAB7-21': '313', 'IAB18-3': '579', 'IAB13-3': '52', 'IAB20-15': '659', 'IAB8-11': '214', + 'IAB7-22': '318', 'IAB20-16': '659', 'IAB7-23': '313', 'IAB7': '223', 'IAB10-6': '634', 'IAB7-27': '318', 'IAB11-1': '388', 'IAB7-29': '318', 'IAB7-30': '304', 'IAB19-18': '619', 'IAB8-13': '214', + 'IAB20-19': '659', 'IAB20-20': '657', 'IAB8-14': '214', 'IAB18-4': '565', 'IAB23-9': '459', 'IAB11': '379', 'IAB8-15': '214', 'IAB20-21': '662', 'IAB17-21': '492', 'IAB17-22': '518', 'IAB12': '379', + 'IAB23-10': '453', 'IAB7-34': '301', 'IAB4-8': '395', 'IAB26-3': '608', 'IAB20-25': '151', 'IAB20-27': '659' +} + +export function convertSegment(segment) { + if (!segment) return {} + return { + id: D_IAB_ID[segment.id || segment.ID] + } +} + +/** + * map array of objects to segments + * @param {Array[{ID: string}]} normalizable + * @returns array of IAB "segments" + */ +export function pickSegments(normalizable) { + if (Array.isArray(normalizable) === false) return [] + return normalizable.map(convertSegment) + .filter(t => t.id) +} + +export const neuwoRtdModule = { + name: 'NeuwoRTDModule', + init, + getBidRequestData +} + +submodule('realTimeData', neuwoRtdModule) diff --git a/modules/neuwoRtdProvider.md b/modules/neuwoRtdProvider.md new file mode 100644 index 00000000000..3552a018563 --- /dev/null +++ b/modules/neuwoRtdProvider.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: Neuwo Rtd Provider +Module Type: Rtd Provider +Maintainer: neuwo.ai + +# Description + +Real-time data provider for Neuwo AI. Contact neuwo.ai [https://neuwo.ai/contact-us] for more information. + +# Configuration + +```javascript + +const neuwoDataProvider = { + name: 'NeuwoRTDModule', + params: { + publicToken: '' + } +} +pbjs.setConfig({realTimeData: { dataProviders: [ neuwoDataProvider ]}}) + +``` + +# Testing + +## Add development tools if necessary + +- Install node for npm +- run in prebid.js source folder: +`npm ci` +`npm i -g gulp-cli` + +## Serve + +`gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter` + +- in your browser, navigate to: + +`http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html` diff --git a/test/spec/modules/neuwoRtdProvider_spec.js b/test/spec/modules/neuwoRtdProvider_spec.js new file mode 100644 index 00000000000..7f47fba072b --- /dev/null +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -0,0 +1,123 @@ +import { server } from 'test/mocks/xhr.js'; +import * as neuwo from 'modules/neuwoRtdProvider'; + +const PUBLIC_TOKEN = 'public_key_0000'; +const config = () => ({ + params: { + publicToken: PUBLIC_TOKEN + } +}) + +const apiReturns = () => ({ + somethingExtra: { object: true }, + marketing_categories: { + iab_tier_1: [ + { ID: 'IAB21', label: 'Real Estate', relevance: '0.45699' } + ] + } +}) + +const TAX_ID = '441' + +/** + * Object generator, like above, written using alternative techniques + * @returns object with predefined (expected) bidsConfig fields + */ +function bidsConfiglike() { + return Object.assign({}, { + ortb2Fragments: { global: {} } + }) +} + +describe('neuwoRtdProvider', function () { + describe('neuwoRtdModule', function () { + it('initializes', function () { + expect(neuwo.neuwoRtdModule.init(config())).to.be.true; + }) + it('init needs that public token', function () { + expect(neuwo.neuwoRtdModule.init()).to.be.false; + }) + + describe('segment picking', function () { + it('handles bad inputs', function () { + expect(neuwo.pickSegments()).to.be.an('array').that.is.empty; + expect(neuwo.pickSegments('technically also an array')).to.be.an('array').that.is.empty; + expect(neuwo.pickSegments({ bad_object: 'bad' })).to.be.an('array').that.is.empty; + }) + it('handles malformations', function () { + let result = neuwo.pickSegments([{something_wrong: true}, null, { ID: 'IAB19-20' }, { id: 'IAB3-1', ID: 'IAB9-20' }]) + expect(result[0].id).to.equal('631') + expect(result[1].id).to.equal('58') + expect(result.length).to.equal(2) + }) + }) + + describe('topic injection', function () { + it('mutates bidsConfig', function () { + let topics = apiReturns() + let bidsConfig = bidsConfiglike() + neuwo.injectTopics(topics, bidsConfig, () => { }) + expect(bidsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bidsConfig.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) + expect(bidsConfig.ortb2Fragments.global.site.cattax, 'category taxonomy code for pagecat').to.equal(6) // CATTAX_IAB + expect(bidsConfig.ortb2Fragments.global.site.pagecat[0], 'category taxonomy code for pagecat').to.equal(TAX_ID) + }) + + it('handles malformed responses', function () { + let topics = { message: 'Forbidden' } + let bidsConfig = bidsConfiglike() + neuwo.injectTopics(topics, bidsConfig, () => { }) + expect(bidsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bidsConfig.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; + + topics = '404 wouldn\'t really even show up for injection' + let bdsConfig = bidsConfiglike() + neuwo.injectTopics(topics, bdsConfig, () => { }) + expect(bdsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bdsConfig.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; + + topics = undefined + let bdsConfigE = bidsConfiglike() + neuwo.injectTopics(topics, bdsConfigE, () => { }) + expect(bdsConfigE.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bdsConfigE.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; + }) + }) + + describe('fragment addition', function () { + it('mutates input objects', function () { + let alphabet = { a: { b: { c: {} } } } + neuwo.addFragment(alphabet.a.b.c, 'd.e.f', { g: 'h' }) + expect(alphabet.a.b.c.d.e.f.g).to.equal('h') + }) + }) + + describe('getBidRequestData', function () { + it('forms requests properly and mutates input bidsConfig', function () { + let bids = bidsConfiglike() + let conf = config() + // control xhr api request target for testing + conf.params.argUrl = 'https://publisher.works/article.php?get=horrible_url_for_testing&id=5' + + neuwo.getBidRequestData(bids, () => { }, conf, 'any consent data works, clearly') + + let request = server.requests[0]; + expect(request.url).to.be.a('string').that.includes(conf.params.publicToken) + expect(request.url).to.include(encodeURIComponent(conf.params.argUrl)) + request.respond(200, { 'Content-Type': 'application/json; encoding=UTF-8' }, JSON.stringify(apiReturns())); + + expect(bids.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bids.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) + }) + + it('accepts detail not available result', function () { + let bidsConfig = bidsConfiglike() + let comparison = bidsConfiglike() + neuwo.getBidRequestData(bidsConfig, () => { }, config(), 'consensually') + let request = server.requests[0]; + request.respond(404, { 'Content-Type': 'application/json; encoding=UTF-8' }, JSON.stringify({ detail: 'Basically first time seeing this' })); + expect(bidsConfig).to.deep.equal(comparison) + }) + }) + }) +}) From 1e91c1c6704de9dbef194441e4266dcf5b0249de Mon Sep 17 00:00:00 2001 From: nouchy <33549554+nouchy@users.noreply.github.com> Date: Wed, 8 Feb 2023 13:24:27 +0100 Subject: [PATCH 074/375] 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 --- modules/sirdataRtdProvider.js | 546 +++++++++---------- test/spec/modules/sirdataRtdProvider_spec.js | 138 ++++- 2 files changed, 378 insertions(+), 306 deletions(-) diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 369276e7638..40ee3d8b973 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -1,12 +1,13 @@ /** * This module adds Sirdata provider to the real time data module + * and now supports Seller Defined Audience * The {@link module:modules/realTimeData} module is required * The module will fetch segments (user-centric) and categories (page-centric) from Sirdata server * The module will automatically handle user's privacy and choice in California (IAB TL CCPA Framework) and in Europe (IAB EU TCF FOR GDPR) * @module modules/sirdataRtdProvider * @requires module:modules/realTimeData */ -import {deepAccess, deepEqual, deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; +import {deepAccess, deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; import {findIndex} from '../src/polyfill.js'; @@ -16,6 +17,52 @@ import {config} from '../src/config.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'SirdataRTDModule'; +const ORTB2_NAME = 'sirdata.com'; + +const partnerIds = { + 'criteo': 27443, + 'openx': 30342, + 'pubmatic': 30345, + 'smaato': 27520, + 'triplelift': 27518, + 'yahoossp': 30339, + 'rubicon': 27452, + 'appnexus': 27446, + 'appnexusAst': 27446, + 'brealtime': 27446, + 'emxdigital': 27446, + 'pagescience': 27446, + 'gourmetads': 33394, + 'matomy': 27446, + 'featureforward': 27446, + 'oftmedia': 27446, + 'districtm': 27446, + 'adasta': 27446, + 'beintoo': 27446, + 'gravity': 27446, + 'msq_classic': 27878, + 'msq_max': 27878, + '366_apx': 27878, + 'mediasquare': 27878, + 'smartadserver': 27440, + 'smart': 27440, + 'proxistore': 27484, + 'ix': 27248, + 'sdRtdForGpt': 27449, + 'smilewanted': 28690, + 'taboola': 33379, + 'ttd': 33382, + 'zeta_global': 33385, + 'teads': 33388, + 'conversant': 33391, + 'improvedigital': 33397, + 'invibes': 33400, + 'sublime': 33403, + 'rtbhouse': 33406, + 'zeta_global_ssp': 33385, +}; + +let CONTEXT_ONLY = true; export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { moduleConfig.params = moduleConfig.params || {}; @@ -46,9 +93,10 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, if (!sirdataDomain || !gdprApplies || (deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { sirdataDomain = 'sddan.com'; sendWithCredentials = true; + CONTEXT_ONLY = false; } - // TODO: is 'page' the right value here? - var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().page; + + var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().stack.pop() || getRefererInfo().page; const url = 'https://kvt.' + sirdataDomain + '/api/v1/public/p/' + moduleConfig.params.partnerId + '/d/' + moduleConfig.params.key + '/s?callback=&gdpr=' + gdprApplies + '&gdpr_consent=' + tcString + (actualUrl ? '&url=' + encodeURIComponent(actualUrl) : ''); @@ -86,39 +134,74 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, }); } -export function setGlobalOrtb2(ortb2, segments, categories) { +export function setGlobalOrtb2Sda(ortb2Fragments, data, segtaxid, cattaxid) { try { - let addOrtb2 = {}; - let testGlobal = ortb2 || {} - if (!deepAccess(testGlobal, 'user.ext.data.sd_rtd') || !deepEqual(testGlobal.user.ext.data.sd_rtd, segments)) { - deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + if (!isEmpty(data.segments)) { + applyGlobalOrtb2Sda(ortb2Fragments, 'user', data.segments, segtaxid); } - if (!deepAccess(testGlobal, 'site.ext.data.sd_rtd') || !deepEqual(testGlobal.site.ext.data.sd_rtd, categories)) { - deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); - } - if (!isEmpty(addOrtb2)) { - mergeDeep(ortb2, addOrtb2); + if (!isEmpty(data.categories)) { + applyGlobalOrtb2Sda(ortb2Fragments, 'site', data.categories, cattaxid); } } catch (e) { logError(e) } + return true; +} +export function applyGlobalOrtb2Sda(ortb2Fragments, type, segments, segtaxValue) { + try { + let ortb2Data = [{ + name: ORTB2_NAME, + segment: segments.map((segmentId) => ({ id: segmentId })), + }]; + if (segtaxValue) { + ortb2Data[0].ext = { segtax: segtaxValue }; + } + let ortb2Conf = (type == 'site' ? {site: {content: {data: ortb2Data}}} : {user: {data: ortb2Data}}); + mergeDeep(ortb2Fragments, ortb2Conf); + } catch (e) { + logError(e) + } return true; } -export function setBidderOrtb2(bidderOrtb2, bidder, segments, categories) { +export function setBidderOrtb2Sda(ortb2Fragments, bidder, data, segtaxid, cattaxid) { try { - let addOrtb2 = {}; - let testBidder = bidderOrtb2[bidder]; - if (!deepAccess(testBidder, 'user.ext.data.sd_rtd') || !deepEqual(testBidder.user.ext.data.sd_rtd, segments)) { - deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + if (!isEmpty(data.segments)) { + applyBidderOrtb2Sda(ortb2Fragments, bidder, 'user', data.segments, segtaxid); } - if (!deepAccess(testBidder, 'site.ext.data.sd_rtd') || !deepEqual(testBidder.site.ext.data.sd_rtd, categories)) { - deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); + if (!isEmpty(data.categories)) { + applyBidderOrtb2Sda(ortb2Fragments, bidder, 'site', data.categories, cattaxid); } - if (!isEmpty(addOrtb2)) { - mergeDeep(bidderOrtb2[bidder], addOrtb2) + } catch (e) { + logError(e) + } + return true; +} + +export function applyBidderOrtb2Sda(ortb2Fragments, bidder, type, segments, segtaxValue) { + try { + let ortb2Data = [{ + name: ORTB2_NAME, + segment: segments.map((segmentId) => ({ id: segmentId })), + }]; + if (segtaxValue) { + ortb2Data[0].ext = { segtax: segtaxValue }; } + let ortb2Conf = (type == 'site' ? {site: {content: {data: ortb2Data}}} : {user: {data: ortb2Data}}); + mergeDeep(ortb2Fragments, {[bidder]: ortb2Conf}); + } catch (e) { + logError(e) + } + return true; +} + +export function setBidderOrtb2(bidderOrtb2Fragments, bidder, path, segments) { + try { + if (isEmpty(segments)) { return; } + let ortb2Conf = {}; + deepSetValue(ortb2Conf, path, segments || {}); + mergeDeep(bidderOrtb2Fragments, {[bidder]: ortb2Conf}); } catch (e) { logError(e) } @@ -137,16 +220,16 @@ export function loadCustomFunction(todo, adUnit, list, data, bid) { return true; } -export function getSegAndCatsArray(data, minScore) { - var sirdataData = {'segments': [], 'categories': []}; +export function getSegAndCatsArray(data, minScore, pid) { + let sirdataData = {'segments': [], 'categories': []}; minScore = minScore && typeof minScore == 'number' ? minScore : 30; try { if (data && data.contextual_categories) { for (let catId in data.contextual_categories) { - if (data.contextual_categories.hasOwnProperty(catId)) { + if (data.contextual_categories.hasOwnProperty(catId) && data.contextual_categories[catId]) { let value = data.contextual_categories[catId]; if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { - sirdataData.categories.push(catId.toString()); + sirdataData.categories.push((pid ? pid.toString() + 'cc' : '') + catId.toString()); } } } @@ -157,8 +240,11 @@ export function getSegAndCatsArray(data, minScore) { try { if (data && data.segments) { for (let segId in data.segments) { - if (data.segments.hasOwnProperty(segId)) { - sirdataData.segments.push(data.segments[segId].toString()); + if (data.segments.hasOwnProperty(segId) && data.segments[segId]) { + sirdataData.segments.push((pid ? pid.toString() + 'us' : '') + data.segments[segId].toString()); + if (pid && CONTEXT_ONLY) { + sirdataData.categories.push(pid.toString() + 'uc' + data.segments[segId].toString()); + } } } } @@ -168,34 +254,77 @@ export function getSegAndCatsArray(data, minScore) { return sirdataData; } +export function applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit) { + // only share SDA data if whitelisted + if (!biddersParamsExist || indexFound) { + // SDA Publisher + let sirdataDataForSDA = getSegAndCatsArray(data, minScore, moduleConfig.params.partnerId); + setBidderOrtb2Sda(reqBids.ortb2Fragments?.bidder, bid.bidder, sirdataDataForSDA, data.segtaxid, data.cattaxid); + } + + // always share SDA for curation + let curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : (partnerIds[bid.bidder] ? partnerIds[bid.bidder] : null)); + if (curationId) { + // seller defined audience & bidder specific data + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + // Get Bidder Specific Data + let curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore, null); + sirdataList = sirdataList.concat(curationData.segments).concat(curationData.categories); + + // SDA Partners + let curationDataForSDA = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore, curationId); + setBidderOrtb2Sda(reqBids.ortb2Fragments?.bidder, bid.bidder, curationDataForSDA, data.shared_taxonomy[curationId].segtaxid, data.shared_taxonomy[curationId].cattaxid); + } + } + + // Apply custom function or return Bidder Specific Data if publisher is ok + if (sirdataList && sirdataList.length > 0 && (!biddersParamsExist || indexFound)) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + return loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList, data, bid); + } else { + return sirdataList; + } + } +} + +export function applySdaAndDefaultSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit) { + let specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.ext.data', {sd_rtd: specificData}); + } +} + export function addSegmentData(reqBids, data, moduleConfig, onDone) { const adUnits = reqBids.adUnits; moduleConfig = moduleConfig || {}; moduleConfig.params = moduleConfig.params || {}; const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30; - var sirdataData = getSegAndCatsArray(data, globalMinScore); + var sirdataData = getSegAndCatsArray(data, globalMinScore, null); const sirdataList = sirdataData.segments.concat(sirdataData.categories); - var sirdataMergedList = []; - var curationData = {'segments': [], 'categories': []}; - var curationId = '1'; const biddersParamsExist = (!!(moduleConfig.params && moduleConfig.params.bidders)); - // Global ortb2 - if (!biddersParamsExist) { - setGlobalOrtb2(reqBids.ortb2Fragments?.global, sirdataData.segments, sirdataData.categories); + // Global ortb2 SDA + if (data.global_taxonomy && !isEmpty(data.global_taxonomy)) { + let globalData = {'segments': [], 'categories': []}; + for (let i in data.global_taxonomy) { + if (!isEmpty(data.global_taxonomy[i])) { + globalData = getSegAndCatsArray(data.global_taxonomy[i], globalMinScore, null); + setGlobalOrtb2Sda(reqBids.ortb2Fragments?.global, globalData, data.global_taxonomy[i].segtaxid, data.global_taxonomy[i].cattaxid); + } + } } // Google targeting if (typeof window.googletag !== 'undefined' && (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues'))) { try { - // For curation Google is pid 27449 - curationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : '27449'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], globalMinScore); + let gptCurationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : (partnerIds['sdRtdForGpt'] ? partnerIds['sdRtdForGpt'] : null)); + let sirdataMergedList = sirdataList; + if (gptCurationId && data.shared_taxonomy && data.shared_taxonomy[gptCurationId]) { + let gamCurationData = getSegAndCatsArray(data.shared_taxonomy[gptCurationId], globalMinScore, null); + sirdataMergedList = sirdataMergedList.concat(gamCurationData.segments).concat(gamCurationData.categories); } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); window.googletag.pubads().getSlots().forEach(function (n) { if (typeof n.setTargeting !== 'undefined' && sirdataMergedList && sirdataMergedList.length > 0) { n.setTargeting('sd_rtd', sirdataMergedList); @@ -221,259 +350,108 @@ export function addSegmentData(reqBids, data, moduleConfig, onDone) { }) : false); indexFound = (!!(typeof bidderIndex == 'number' && bidderIndex >= 0)); try { - curationData = {'segments': [], 'categories': []}; - sirdataMergedList = []; - let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore); + let specificData = null; + + switch (bid.bidder) { + case 'appnexus': + case 'appnexusAst': + case 'brealtime': + case 'emxdigital': + case 'pagescience': + case 'gourmetads': + case 'matomy': + case 'featureforward': + case 'oftmedia': + case 'districtm': + case 'adasta': + case 'beintoo': + case 'gravity': + case 'msq_classic': + case 'msq_max': + case '366_apx': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { + deepSetValue(bid, 'params.keywords.sd_rtd', specificData); + } + break; - if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { - switch (bid.bidder) { - case 'appnexus': - case 'appnexusAst': - case 'brealtime': - case 'emxdigital': - case 'pagescience': - case 'gourmetads': - case 'matomy': - case 'featureforward': - case 'oftmedia': - case 'districtm': - case 'adasta': - case 'beintoo': - case 'gravity': - case 'msq_classic': - case 'msq_max': - case '366_apx': - // For curation Xandr is pid 27446 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27446'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - deepSetValue(bid, 'params.keywords.sd_rtd', sirdataMergedList); - } - } - break; - - case 'smartadserver': - case 'smart': + case 'smartadserver': + case 'smart': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { var target = []; if (bid.hasOwnProperty('params') && bid.params.hasOwnProperty('target')) { target.push(bid.params.target); } - // For curation Smart is pid 27440 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27440'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - sirdataMergedList.forEach(function (entry) { - if (target.indexOf('sd_rtd=' + entry) === -1) { - target.push('sd_rtd=' + entry); - } - }); - deepSetValue(bid, 'params.target', target.join(';')); - } - } - break; - - case 'rubicon': - // For curation Magnite is pid 27518 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27452'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'ix': - var ixConfig = config.getConfig('ix.firstPartyData.sd_rtd'); - if (!ixConfig) { - // For curation index is pid 27248 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27248'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - var cappIxCategories = []; - var ixLength = 0; - var ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); - // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters - sirdataMergedList.forEach(function (entry) { - if (ixLength < ixLimit) { - cappIxCategories.push(entry); - ixLength += entry.toString().length; - } - }); - config.setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); - } - } - } - break; - - case 'proxistore': - // For curation Proxistore is pid 27484 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27484'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } else { - data.shared_taxonomy[curationId] = {contextual_categories: {}}; - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - deepSetValue(bid, 'ortb2.user.ext.data', { - segments: sirdataData.segments.concat(curationData.segments), - contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories} - }); - } - } - break; - - case 'criteo': - // For curation Smart is pid 27443 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27443'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'triplelift': - // For curation Triplelift is pid 27518 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27518'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'avct': - case 'avocet': - // For curation Avocet is pid 27522 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27522'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'smaato': - // For curation Smaato is pid 27520 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27520'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'yahoossp': - // For curation Yahoo is pid 30339 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30339'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'openx': - // For curation OpenX is pid 30342 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30342'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + specificData.forEach(function (entry) { + if (target.indexOf('sd_rtd=' + entry) === -1) { + target.push('sd_rtd=' + entry); } - } - break; - - case 'pubmatic': - // For curation Pubmatic is pid 30345 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30345'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - default: - if (!biddersParamsExist || indexFound) { - if (!deepAccess(bid, 'ortb2.site.ext.data.sd_rtd')) { - deepSetValue(bid, 'ortb2.site.ext.data.sd_rtd', sirdataData.categories); - } - if (!deepAccess(bid, 'ortb2.user.ext.data.sd_rtd')) { - deepSetValue(bid, 'ortb2.user.ext.data.sd_rtd', sirdataData.segments); + }); + deepSetValue(bid, 'params.target', target.join(';')); + } + break; + + case 'ix': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + let ixConfig = config.getConfig('ix.firstPartyData.sd_rtd'); + if (!ixConfig && specificData && specificData.length > 0) { + let cappIxCategories = []; + let ixLength = 0; + let ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); + // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters + specificData.forEach(function (entry) { + if (ixLength < ixLimit) { + cappIxCategories.push(entry); + ixLength += entry.toString().length; } + }); + config.setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); + } + break; + + case 'proxistore': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { + let psCurationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : (partnerIds[bid.bidder] ? partnerIds[bid.bidder] : null)); + if (!data.shared_taxonomy || !data.shared_taxonomy[psCurationId]) { + data.shared_taxonomy[psCurationId] = {segments: [], contextual_categories: {}, segtaxid: null, cattaxid: null}; } - } + let psCurationData = getSegAndCatsArray(data.shared_taxonomy[psCurationId], minScore, null); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.ext.data', { + segments: sirdataData.segments.concat(psCurationData.segments), + contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[psCurationId].contextual_categories} + }); + } + break; + + case 'rubicon': + case 'criteo': + case 'triplelift': + case 'smaato': + case 'yahoossp': + case 'openx': + case 'pubmatic': + case 'smilewanted': + case 'taboola': + case 'ttd': + case 'zeta_global': + case 'zeta_global_ssp': + case 'teads': + case 'conversant': + case 'improvedigital': + case 'invibes': + case 'sublime': + case 'rtbhouse': + case 'mediasquare': + applySdaAndDefaultSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + break; + + default: + if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { + applySdaAndDefaultSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + } } } catch (e) { logError(e); diff --git a/test/spec/modules/sirdataRtdProvider_spec.js b/test/spec/modules/sirdataRtdProvider_spec.js index 9cf392ebd62..eccc4777906 100644 --- a/test/spec/modules/sirdataRtdProvider_spec.js +++ b/test/spec/modules/sirdataRtdProvider_spec.js @@ -1,17 +1,17 @@ -import { addSegmentData, getSegmentsAndCategories, sirdataSubmodule } from 'modules/sirdataRtdProvider.js'; -import { server } from 'test/mocks/xhr.js'; +import {addSegmentData, getSegmentsAndCategories, sirdataSubmodule} from 'modules/sirdataRtdProvider.js'; +import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; -describe('sirdataRtdProvider', function() { - describe('sirdataSubmodule', function() { +describe('sirdataRtdProvider', function () { + describe('sirdataSubmodule', function () { it('successfully instantiates', function () { - expect(sirdataSubmodule.init()).to.equal(true); + expect(sirdataSubmodule.init()).to.equal(true); }); }); - describe('Add Segment Data', function() { - it('adds segment data', function() { + describe('Add Segment Data', function () { + it('adds segment data', function () { const config = { params: { setGptKeyValues: false, @@ -42,23 +42,34 @@ describe('sirdataRtdProvider', function() { contextual_categories: {'333333': 100} }; - addSegmentData({adUnits}, data, config, () => {}); + addSegmentData({adUnits}, data, config, () => { + }); expect(adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); - expect(adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); - expect(adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); }); }); - describe('Get Segments And Categories', function() { - it('gets data from async request and adds segment data', function() { + describe('Get Segments And Categories', function () { + it('gets data from async request and adds segment data', function () { + const overrideAppnexus = function (adUnit, list, data, bid) { + deepSetValue(bid, 'params.keywords.custom', list); + } + const config = { params: { setGptKeyValues: false, contextualMinRelevancyScore: 50, bidders: [{ - bidder: 'appnexus' + bidder: 'appnexus', + customFunction: overrideAppnexus }, { - bidder: 'other' + bidder: 'smartadserver' + }, { + bidder: 'ix', + sizeLimit: 1200, + }, { + bidder: 'rubicon', + }, { + bidder: 'proxistore', }] } }; @@ -71,24 +82,107 @@ describe('sirdataRtdProvider', function() { placementId: 13144370 } }, { - bidder: 'other' + bidder: 'smartadserver', + params: { + siteId: 207435, + pageId: 896536, + formatId: 62913 + } + }, { + bidder: 'proxistore', + params: {website: 'demo.sirdata.com', language: 'fr'}, + adUnitCode: 'HALFPAGE_CENTER_LOADER', + transactionId: '92ac333a-a569-4827-abf1-01fc9d19278a', + sizes: [[300, 600]], + mediaTypes: { + banner: { + filteredSizeConfig: [ + {minViewPort: [1600, 0], sizes: [[300, 600]]}, + ], + sizeConfig: [ + {minViewPort: [0, 0], sizes: [[300, 600]]}, + {minViewPort: [768, 0], sizes: [[300, 600]]}, + {minViewPort: [1200, 0], sizes: [[300, 600]]}, + {minViewPort: [1600, 0], sizes: [[300, 600]]}, + ], + sizes: [[300, 600]], + }, + }, + bidId: '190bab495bc5f6e', + bidderRequestId: '18c0b0f0c91cd88', + auctionId: '9bdd917b-908d-4d9f-8f2f-d443277a62fc', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + }, { + bidder: 'ix', + params: { + siteId: '12345', + size: [300, 600] + } + }, { + bidder: 'rubicon', + params: { + accountId: 14062, + siteId: 70608, + zoneId: 498816 + } }] - }] + }], + ortb2Fragments: { + global: {} + } }; let data = { - segments: [111111, 222222], - contextual_categories: {'333333': 100} + 'segments': [111111, 222222], + 'segtaxid': null, + 'cattaxid': null, + 'contextual_categories': {'333333': 100}, + 'shared_taxonomy': { + '27440': { + 'segments': [444444, 555555], + 'segtaxid': 552, + 'cattaxid': 553, + 'contextual_categories': {'666666': 100} + } + }, + 'global_taxonomy': { + '9998': { + 'segments': [123, 234], + 'segtaxid': 4, + 'cattaxid': 7, + 'contextual_categories': {'345': 100, '456': 100} + } + } }; - getSegmentsAndCategories(reqBidsConfigObj, () => {}, config, {}); + getSegmentsAndCategories(reqBidsConfigObj, () => { + }, config, {}); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(data)); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); - expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); - expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.have.deep.property('target', 'sd_rtd=111111;sd_rtd=222222;sd_rtd=333333;sd_rtd=444444;sd_rtd=555555;sd_rtd=666666'); + + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].name).to.equal( + 'sirdata.com' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + {id: '345'}, + {id: '456'} + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(7); + + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].name).to.equal( + 'sirdata.com' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].segment).to.eql([ + {id: '123'}, + {id: '234'} + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].ext.segtax).to.equal(4); }); }); }); From 045eb801bb6891eb3a1dedf8430e7e2ac592fe18 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Wed, 8 Feb 2023 15:12:06 +0100 Subject: [PATCH 075/375] Nexx360 Bid Adapter: League-m alias added (#9518) * bidderCode fix * League-m added as alias --- modules/nexx360BidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 6ead44175e9..397196ee7a9 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -23,13 +23,13 @@ export const spec = { { code: 'revenuemaker' }, { code: 'first-id', gvlid: 1178 }, { code: 'adwebone' }, + { code: 'league-m', gvlid: 965 } ], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, interpretResponse, getUserSyncs, - // onBidWon, }; registerBidder(spec); From e88c872c691387554151e589ed75fde7ddf3aa42 Mon Sep 17 00:00:00 2001 From: couchcrew-thomas Date: Wed, 8 Feb 2023 15:44:06 +0100 Subject: [PATCH 076/375] 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 --- modules/feedadBidAdapter.js | 50 ++++++--- test/spec/modules/feedadBidAdapter_spec.js | 124 ++++++++------------- 2 files changed, 82 insertions(+), 92 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 7b684efab3c..34e14cd674a 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -7,7 +7,23 @@ import {ajax} from '../src/ajax.js'; * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.5'; +const VERSION = '1.0.6'; + +/** + * @typedef {object} FeedAdUserSync + * @inner + * + * @property {string} type + * @property {string} url + */ + +/** + * @typedef {object} FeedAdBidExtension + * @inner + * + * @property {FeedAdUserSync[]} pixels + * @property {FeedAdUserSync[]} iframes + */ /** * @typedef {object} FeedAdApiBidRequest @@ -41,7 +57,7 @@ const VERSION = '1.0.5'; * @property {string} requestId - bids[].bidId * @property {number} ttl - Time to live for this ad * @property {number} width - Width of creative returned in [].ad - * @property {object} [ext] - an extension object + * @property {FeedAdBidExtension} [ext] - an extension object */ /** @@ -63,6 +79,14 @@ const VERSION = '1.0.5'; * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows */ +/** + * @typedef {object} FeedAdServerResponse + * @extends ServerResponse + * @inner + * + * @property {FeedAdApiBidResponse[]} body - the body of a FeedAd server response + */ + /** * The IAB TCF 2.0 vendor ID for the FeedAd GmbH */ @@ -228,7 +252,7 @@ function buildRequests(validBidRequests, bidderRequest) { /** * Adapts the FeedAd server response to Prebid format - * @param {ServerResponse} serverResponse - the FeedAd server response + * @param {FeedAdServerResponse} serverResponse - the FeedAd server response * @param {BidRequest} request - the initial bid request * @returns {Bid[]} the FeedAd bids */ @@ -296,24 +320,20 @@ function trackingHandlerFactory(klass) { /** * Reads the user syncs off the server responses and converts them into Prebid.JS format * @param {SyncOptions} syncOptions - * @param {ServerResponse[]} serverResponses + * @param {FeedAdServerResponse[]} serverResponses * @param gdprConsent * @param uspConsent */ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - return serverResponses.map(response => { - // validate response format - const ext = deepAccess(response, 'body.ext', []); - if (ext == null) { - return null; - } - return ext; + return serverResponses.flatMap(response => { + // merge all response bodies into one + const body = response.body; + return isArray(body) ? body : []; }) - .filter(ext => ext != null) - .flatMap(extension => { + .flatMap(/** @param {FeedAdApiBidResponse} bidResponse */ bidResponse => { // extract user syncs from extension - const pixels = syncOptions.pixelEnabled && extension.pixels ? extension.pixels : []; - const iframes = syncOptions.iframeEnabled && extension.iframes ? extension.iframes : []; + const pixels = (syncOptions.pixelEnabled && bidResponse?.ext?.pixels) ? bidResponse.ext.pixels : []; + const iframes = (syncOptions.iframeEnabled && bidResponse?.ext?.iframes) ? bidResponse.ext.iframes : []; return pixels.concat(...iframes); }) .reduce((syncs, sync) => { diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 8cbd6907890..5789361d2a1 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -4,7 +4,7 @@ import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import {server} from 'test/mocks/xhr.js'; const CODE = 'feedad'; -const EXPECTED_ADAPTER_VERSION = '1.0.5'; +const EXPECTED_ADAPTER_VERSION = '1.0.6'; describe('FeedAdAdapter', function () { describe('Public API', function () { @@ -373,81 +373,67 @@ describe('FeedAdAdapter', function () { const pixelSync2 = {type: 'image', url: 'the pixel url 2'}; const iFrameSync1 = {type: 'iframe', url: 'the iFrame url 1'}; const iFrameSync2 = {type: 'iframe', url: 'the iFrame url 2'}; - const mockServerResponse = (content) => { - if (!(content instanceof Array)) { - content = [content]; - } - return content.map(it => ({body: it})); - }; - - it('should pass through the syncs out of the extension fields of the server response', function () { - const serverResponse = mockServerResponse([{ + const response1 = { + body: [{ ext: { pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], + iframes: [iFrameSync1] + }, + }] + }; + const response2 = { + body: [{ + ext: { + pixels: [pixelSync1], + iframes: [iFrameSync1], } - }]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse) + }, { + ext: { + pixels: [pixelSync2], + iframes: [iFrameSync2], + } + }] + }; + it('should pass through the syncs out of the extension fields of the server response', function () { + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response1]) expect(result).to.deep.equal([ pixelSync1, pixelSync2, iFrameSync1, - iFrameSync2, ]); }); it('should concat the syncs of all responses', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1], - iframes: [iFrameSync2], - }, - ad: 'ad html', - cpm: 100 - }, { - ext: { - iframes: [iFrameSync1], - } - }, { - ext: { - pixels: [pixelSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response1, response2]); expect(result).to.deep.equal([ pixelSync1, + pixelSync2, + iFrameSync1, iFrameSync2, + ]); + }); + + it('should concat the syncs of all bids', function () { + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response2]); + expect(result).to.deep.equal([ + pixelSync1, iFrameSync1, pixelSync2, + iFrameSync2, ]); }); it('should filter out duplicates', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1, pixelSync1], - iframes: [iFrameSync2, iFrameSync2], - } - }, { - ext: { - iframes: [iFrameSync2, iFrameSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response1, response1]); expect(result).to.deep.equal([ pixelSync1, - iFrameSync2, + pixelSync2, + iFrameSync1, ]); }); it('should not include iFrame syncs if the option is disabled', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [response1]); expect(result).to.deep.equal([ pixelSync1, pixelSync2, @@ -455,52 +441,36 @@ describe('FeedAdAdapter', function () { }); it('should not include pixel syncs if the option is disabled', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [response1]); expect(result).to.deep.equal([ iFrameSync1, - iFrameSync2, ]); }); it('should not include any syncs if the sync options are disabled or missing', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, [response1]); expect(result).to.deep.equal([]); }); it('should handle empty responses', function () { - const serverResponse = mockServerResponse([]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse) + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, []) expect(result).to.deep.equal([]); }); it('should not throw if the server response is weird', function () { const responses = [ - mockServerResponse(null), - mockServerResponse('null'), - mockServerResponse(1234), - mockServerResponse({}), - mockServerResponse([{}, 123]), + {body: null}, + {body: 'null'}, + {body: 1234}, + {body: {}}, + {body: [{}, 123]}, ]; - responses.forEach(it => { - expect(() => spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, it)).not.to.throw; - }); + expect(() => spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, responses)).to.not.throw(); }); it('should return empty array if the body extension is null', function () { - const response = mockServerResponse({ext: null}); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, response); + const response = {body: [{ext: null}]}; + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response]); expect(result).to.deep.equal([]); }); }); From 7e41def99c556b88b12fb6857ff28dc4ac6fbebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20DEYM=C3=88S?= <47388595+MaxSmileWanted@users.noreply.github.com> Date: Wed, 8 Feb 2023 15:45:32 +0100 Subject: [PATCH 077/375] Adding support of eids for smilewanted (#9440) --- modules/smilewantedBidAdapter.js | 5 ++ .../modules/smilewantedBidAdapter_spec.js | 57 ++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index f82e7c9258f..fe14c57d641 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -63,6 +63,11 @@ export const spec = { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side } + + if (bid && bid.userIdAsEids) { + payload.eids = bid.userIdAsEids; + } + var payloadString = JSON.stringify(payload); return { method: 'POST', diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index b9a816cf3d5..44d2c7c6507 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -1,9 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/smilewantedBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import * as utils from 'src/utils.js'; -import { requestBidsHook } from 'modules/consentManagement.js'; const DISPLAY_REQUEST = [{ adUnitCode: 'sw_300x250', @@ -20,6 +17,37 @@ const DISPLAY_REQUEST = [{ transactionId: 'trans_abcd1234' }]; +const DISPLAY_REQUEST_WITH_EIDS = [{ + adUnitCode: 'sw_300x250', + bidId: '12345', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smilewanted', + params: { + zoneId: 1 + }, + requestId: 'request_abcd1234', + transactionId: 'trans_abcd1234', + userIdAsEids: [{ + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }] +}]; + const DISPLAY_REQUEST_WITH_POSITION_TYPE = [{ adUnitCode: 'sw_300x250', bidId: '12345', @@ -170,6 +198,29 @@ describe('smilewantedBidAdapterTests', function () { expect(requestContent).to.have.property('pageDomain').and.to.equal('https://localhost/Prebid.js/integrationExamples/gpt/hello_world.html'); }); + it('Verify external ids in request and ids found', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DISPLAY_REQUEST_WITH_EIDS, {}); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('eids'); + expect(requestContent.eids).to.not.equal(null).and.to.not.be.undefined; + expect(requestContent.eids.length).to.greaterThan(0); + for (let index in requestContent.eids) { + let eid = requestContent.eids[index]; + expect(eid.source).to.not.equal(null).and.to.not.be.undefined; + expect(eid.uids).to.not.equal(null).and.to.not.be.undefined; + for (let uidsIndex in eid.uids) { + let uid = eid.uids[uidsIndex]; + expect(uid.id).to.not.equal(null).and.to.not.be.undefined; + } + } + }); + describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); From cd829a5f03e30288681c0903ce137bf7a5f2dcdc Mon Sep 17 00:00:00 2001 From: ccorbo Date: Wed, 8 Feb 2023 09:51:33 -0500 Subject: [PATCH 078/375] 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 --- modules/ixBidAdapter.js | 3 ++- test/spec/modules/ixBidAdapter_spec.js | 17 +++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index bd598cf2d04..29cda657f5b 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -76,7 +76,8 @@ const SOURCE_RTI_MAPPING = { 'epsilon.com': '', // Publisher Link, publinkId 'audigent.com': '', // Hadron ID from Audigent, hadronId 'pubcid.org': '', // SharedID, pubcid - 'trustpid.com': '' // Trustpid + 'trustpid.com': '', // Trustpid + 'intimatemerger.com': '' }; const PROVIDERS = [ 'britepoolid', diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 2327376d9ac..f681e7786ae 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -750,7 +750,8 @@ describe('IndexexchangeAdapter', function () { // so structured because when calling createEidsArray, UID2's getValue func takes .id to set in uids uid2: { id: 'testuid2' }, // UID 2.0 // similar to uid2, but id5's getValue takes .uid - id5id: { uid: 'testid5id' } // ID5 + id5id: { uid: 'testid5id' }, // ID5 + imuid: 'testimuid' }; const DEFAULT_USERIDASEIDS_DATA = createEidsArray(DEFAULT_USERID_DATA); @@ -801,6 +802,11 @@ describe('IndexexchangeAdapter', function () { uids: [{ id: DEFAULT_USERID_DATA.id5id.uid }] + }, { + source: 'intimatemerger.com', + uids: [{ + id: DEFAULT_USERID_DATA.imuid, + }] } ]; @@ -1193,7 +1199,7 @@ describe('IndexexchangeAdapter', function () { const payload = extractPayload(request[0]); expect(request).to.be.an('array'); expect(request).to.have.lengthOf.above(0); // should be 1 or more - expect(payload.user.eids).to.have.lengthOf(6); + expect(payload.user.eids).to.have.lengthOf(7); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); }); }); @@ -1381,8 +1387,7 @@ describe('IndexexchangeAdapter', function () { cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = extractPayload(request); - - expect(payload.user.eids).to.have.lengthOf(6); + expect(payload.user.eids).to.have.lengthOf(7); expect(payload.user.eids).to.have.deep.members(DEFAULT_USERID_PAYLOAD); }); @@ -1515,7 +1520,7 @@ describe('IndexexchangeAdapter', function () { }) expect(payload.user).to.exist; - expect(payload.user.eids).to.have.lengthOf(8); + expect(payload.user.eids).to.have.lengthOf(9); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); @@ -1557,7 +1562,7 @@ describe('IndexexchangeAdapter', function () { }); const payload = extractPayload(request); - expect(payload.user.eids).to.have.lengthOf(7); + expect(payload.user.eids).to.have.lengthOf(8); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); }); From c5f62215e7ea69fdbd6549792abd3150218ea985 Mon Sep 17 00:00:00 2001 From: Krzysztof Desput Date: Wed, 8 Feb 2023 16:03:03 +0100 Subject: [PATCH 079/375] Admaru Bid Adapter: Add user sync (#9444) * AdmaruBidAdapter: add user sync * AdmaruBidAdapter: Use https in user sync --- modules/admaruBidAdapter.js | 20 ++++++++- test/spec/modules/admaruBidAdapter_spec.js | 48 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/modules/admaruBidAdapter.js b/modules/admaruBidAdapter.js index 65f62c77e26..f681a9a4191 100644 --- a/modules/admaruBidAdapter.js +++ b/modules/admaruBidAdapter.js @@ -5,6 +5,7 @@ const ADMARU_ENDPOINT = 'https://p1.admaru.net/AdCall'; const BIDDER_CODE = 'admaru'; const DEFAULT_BID_TTL = 360; +const SYNC_URL = 'https://p2.admaru.net/UserSync/sync' function parseBid(rawBid, currency) { const bid = {}; @@ -75,7 +76,24 @@ export const spec = { } return bidResponses; - } + }, + + getUserSyncs: function (syncOptions, responses) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: SYNC_URL + }]; + } + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: SYNC_URL + }]; + } + + return []; + }, } registerBidder(spec); diff --git a/test/spec/modules/admaruBidAdapter_spec.js b/test/spec/modules/admaruBidAdapter_spec.js index a45ddae108f..813a4ed8b29 100644 --- a/test/spec/modules/admaruBidAdapter_spec.js +++ b/test/spec/modules/admaruBidAdapter_spec.js @@ -121,4 +121,52 @@ describe('Admaru Adapter', function () { expect(result.length).to.equal(0); }); }); + + describe('getUserSyncs()', () => { + it('should return iframe user sync if iframe sync is enabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: true, + }, + null + ); + + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://p2.admaru.net/UserSync/sync', + }, + ]); + }); + + it('should return image syncs if they are enabled and iframe is disabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: false, + }, + null + ); + + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://p2.admaru.net/UserSync/sync', + }, + ]); + }); + + it('should not return user syncs if syncs are disabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: false, + iframeEnabled: false, + }, + null + ); + + expect(syncs).to.deep.equal([]); + }); + }); }); From 0442955a6f3e965c38040232415206063a8a70cb Mon Sep 17 00:00:00 2001 From: Alexander Pykhteyev Date: Wed, 8 Feb 2023 22:15:30 +0700 Subject: [PATCH 080/375] 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 --- modules/limelightDigitalBidAdapter.js | 7 +++- modules/limelightDigitalBidAdapter.md | 14 ++++++- .../limelightDigitalBidAdapter_spec.js | 40 ++++++++++++++++--- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index 87cf6e4fe21..601d78578b3 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -158,7 +158,12 @@ function buildPlacement(bidRequest) { type: bidRequest.params.adUnitType.toUpperCase(), publisherId: bidRequest.params.publisherId, userIdAsEids: bidRequest.userIdAsEids, - supplyChain: bidRequest.schain + supplyChain: bidRequest.schain, + custom1: bidRequest.params.custom1, + custom2: bidRequest.params.custom2, + custom3: bidRequest.params.custom3, + custom4: bidRequest.params.custom4, + custom5: bidRequest.params.custom5 } } } diff --git a/modules/limelightDigitalBidAdapter.md b/modules/limelightDigitalBidAdapter.md index a4abb6f1411..2c773859a7f 100644 --- a/modules/limelightDigitalBidAdapter.md +++ b/modules/limelightDigitalBidAdapter.md @@ -24,7 +24,12 @@ var adUnits = [{ params: { host: 'exchange-9qao.ortb.net', adUnitId: 0, - adUnitType: 'banner' + adUnitType: 'banner', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' } }] }]; @@ -40,7 +45,12 @@ var videoAdUnit = [{ params: { host: 'exchange-9qao.ortb.net', adUnitId: 0, - adUnitType: 'video' + adUnitType: 'video', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' } }] }]; diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 4efb4f4e84d..5a92110abb4 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -10,7 +10,12 @@ describe('limelightDigitalAdapter', function () { host: 'exchange.ortb.net', adUnitId: 123, adUnitType: 'banner', - publisherId: 'perfectPublisher' + publisherId: 'perfectPublisher', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', @@ -49,7 +54,12 @@ describe('limelightDigitalAdapter', function () { params: { host: 'ads.project-limelight.com', adUnitId: 456, - adUnitType: 'banner' + adUnitType: 'banner', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', @@ -90,7 +100,12 @@ describe('limelightDigitalAdapter', function () { host: 'exchange.ortb.net', adUnitId: 789, adUnitType: 'video', - publisherId: 'secondPerfectPublisher' + publisherId: 'secondPerfectPublisher', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', @@ -128,7 +143,12 @@ describe('limelightDigitalAdapter', function () { params: { host: 'exchange.ortb.net', adUnitId: 789, - adUnitType: 'video' + adUnitType: 'video', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', @@ -196,7 +216,12 @@ describe('limelightDigitalAdapter', function () { 'transactionId', 'publisherId', 'userIdAsEids', - 'supplyChain' + 'supplyChain', + 'custom1', + 'custom2', + 'custom3', + 'custom4', + 'custom5' ); expect(adUnit.id).to.be.a('number'); expect(adUnit.bidId).to.be.a('string'); @@ -205,6 +230,11 @@ describe('limelightDigitalAdapter', function () { expect(adUnit.sizes).to.be.an('array'); expect(adUnit.userIdAsEids).to.be.an('array'); expect(adUnit.supplyChain).to.be.an('object'); + expect(adUnit.custom1).to.be.a('string'); + expect(adUnit.custom2).to.be.a('string'); + expect(adUnit.custom3).to.be.a('string'); + expect(adUnit.custom4).to.be.a('string'); + expect(adUnit.custom5).to.be.a('string'); }) }) }) From 9bda8871c780fc6b4b23e24c3c1511210f64a3b2 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:20:27 -0500 Subject: [PATCH 081/375] Appnexus Bid Adapter : support alternate format for bid.params properties (#9503) * appnexus bid adapter - support alternate format for params * fix lint error --- modules/appnexusBidAdapter.js | 58 ++++++---- test/spec/modules/appnexusBidAdapter_spec.js | 109 ++++++++++++++++--- 2 files changed, 128 insertions(+), 39 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index b64334b0d77..5d233756522 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -44,7 +44,7 @@ const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset']; const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api', 'startdelay']; -const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; +const USER_PARAMS = ['age', 'externalUid', 'external_uid', 'segments', 'gender', 'dnt', 'language']; const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; const DEBUG_QUERY_PARAM_MAP = { @@ -120,7 +120,9 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); + return !!( + (bid.params.placementId || bid.params.placement_id) || + (bid.params.member && (bid.params.invCode || bid.params.inv_code))); }, /** @@ -484,9 +486,6 @@ export const spec = { }, params); if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - if (isPopulatedArray(params.keywords)) { params.keywords.forEach(deleteValues); } @@ -498,6 +497,9 @@ export const spec = { delete params[paramKey]; } }); + + params.use_pmt_rule = (typeof params.use_payment_rule === 'boolean') ? params.use_payment_rule : false; + if (params.use_payment_rule) { delete params.use_payment_rule; } } return params; @@ -771,17 +773,25 @@ function newBid(serverBid, rtbBid, bidderRequest) { function bidToTag(bid) { const tag = {}; + Object.keys(bid.params).forEach(paramKey => { + let convertedKey = convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + bid.params[convertedKey] = bid.params[paramKey]; + delete bid.params[paramKey]; + } + }); tag.sizes = transformSizes(bid.sizes); tag.primary_size = tag.sizes[0]; tag.ad_types = []; tag.uuid = bid.bidId; - if (bid.params.placementId) { - tag.id = parseInt(bid.params.placementId, 10); + if (bid.params.placement_id) { + tag.id = parseInt(bid.params.placement_id, 10); } else { - tag.code = bid.params.invCode; + tag.code = bid.params.inv_code; } - tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false; + tag.allow_smaller_sizes = bid.params.allow_smaller_sizes || false; + tag.use_pmt_rule = (typeof bid.params.use_payment_rule === 'boolean') ? bid.params.use_payment_rule + : (typeof bid.params.use_pmt_rule === 'boolean') ? bid.params.use_pmt_rule : false; tag.prebid = true; tag.disable_psa = true; let bidFloor = getBidFloor(bid); @@ -798,26 +808,26 @@ function bidToTag(bid) { tag.position = (mediaTypePos === 3) ? 2 : mediaTypePos; } } - if (bid.params.trafficSourceCode) { - tag.traffic_source_code = bid.params.trafficSourceCode; + if (bid.params.traffic_source_code) { + tag.traffic_source_code = bid.params.traffic_source_code; } - if (bid.params.privateSizes) { - tag.private_sizes = transformSizes(bid.params.privateSizes); + if (bid.params.private_sizes) { + tag.private_sizes = transformSizes(bid.params.private_sizes); } - if (bid.params.supplyType) { - tag.supply_type = bid.params.supplyType; + if (bid.params.supply_type) { + tag.supply_type = bid.params.supply_type; } - if (bid.params.pubClick) { - tag.pubclick = bid.params.pubClick; + if (bid.params.pub_click) { + tag.pubclick = bid.params.pub_click; } - if (bid.params.extInvCode) { - tag.ext_inv_code = bid.params.extInvCode; + if (bid.params.ext_inv_code) { + tag.ext_inv_code = bid.params.ext_inv_code; } - if (bid.params.publisherId) { - tag.publisher_id = parseInt(bid.params.publisherId, 10); + if (bid.params.publisher_id) { + tag.publisher_id = parseInt(bid.params.publisher_id, 10); } - if (bid.params.externalImpId) { - tag.external_imp_id = bid.params.externalImpId; + if (bid.params.external_imp_id) { + tag.external_imp_id = bid.params.external_imp_id; } let ortb2ImpKwStr = deepAccess(bid, 'ortb2Imp.ext.data.keywords'); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index a5bd9a3592e..d4644039885 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -36,14 +36,31 @@ describe('AppNexusAdapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let bid1 = deepClone(bid); + bid1.params = { + 'placement_id': 123423 + } + expect(spec.isBidRequestValid(bid1)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid1 = deepClone(bid); + bid1.params = { 'member': '1234', 'invCode': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(bid1)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid1 = deepClone(bid); + bid1.params = { + 'member': '1234', + 'inv_code': 'ABCD' + }; + + expect(spec.isBidRequestValid(bid1)).to.equal(true); }); it('should return false when required params are not passed', function () { @@ -54,6 +71,15 @@ describe('AppNexusAdapter', function () { }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placement_id': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -101,6 +127,24 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].private_sizes).to.deep.equal([{ width: 300, height: 250 }]); }); + it('should parse out private sizes', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + private_sizes: [300, 250] + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].private_sizes).to.exist; + expect(payload.tags[0].private_sizes).to.deep.equal([{ width: 300, height: 250 }]); + }); + it('should add position in request', function () { // set from bid.params let bidRequest = deepClone(bidRequests[0]); @@ -168,6 +212,24 @@ describe('AppNexusAdapter', function () { expect(payload.publisher_id).to.deep.equal(1231234); }); + it('should add publisher_id in request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placement_id: '10433394', + publisher_id: '1231234' + } + }); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].publisher_id).to.exist; + expect(payload.tags[0].publisher_id).to.deep.equal(1231234); + expect(payload.publisher_id).to.exist; + expect(payload.publisher_id).to.deep.equal(1231234); + }); + it('should add source and verison to the tag', function () { const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -189,7 +251,7 @@ describe('AppNexusAdapter', function () { bids: [{ bidder: 'appnexus', params: { - placementId: '10433394' + placement_id: '10433394' } }], transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' @@ -351,7 +413,7 @@ describe('AppNexusAdapter', function () { bidRequests[0], { params: { - placementId: '10433394', + placement_id: '10433394', user: { externalUid: '123', segments: [123, { id: 987, value: 876 }], @@ -407,7 +469,7 @@ describe('AppNexusAdapter', function () { // 2 -> reserve is defined, getFloor not defined > reserve is used bidRequest.params = { - 'placementId': '10433394', + 'placement_id': '10433394', 'reserve': 0.5 }; request = spec.buildRequests([bidRequest]); @@ -428,7 +490,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -461,7 +523,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -484,7 +546,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -526,7 +588,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -557,7 +619,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -585,7 +647,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -610,7 +672,7 @@ describe('AppNexusAdapter', function () { mediaType: 'banner', params: { sizes: [[300, 250], [300, 600]], - placementId: 13144370 + placement_id: 13144370 } } ); @@ -786,7 +848,7 @@ describe('AppNexusAdapter', function () { bidRequests[0], { params: { - placementId: '10433394', + placement_id: '10433394', keywords: { single: 'val', singleArr: ['val'], @@ -931,6 +993,23 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].use_pmt_rule).to.equal(true); }); + it('should add payment rules to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placement_id: '10433394', + use_payment_rule: true + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].use_pmt_rule).to.equal(true); + }); + it('should add gpid to the request', function () { let testGpid = '/12345/my-gpt-tag-0'; let bidRequest = deepClone(bidRequests[0]); From 79436ef83878c5bef8dc7613bbd7d0104b5b6610 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:31:42 -0500 Subject: [PATCH 082/375] appnexus bid adapter - userid support update (#9507) --- modules/appnexusBidAdapter.js | 35 ++++++-------------- test/spec/modules/appnexusBidAdapter_spec.js | 30 ++++++++++++++++- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 5d233756522..f354eb053b1 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -345,20 +345,18 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; - - addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); - addUserId(eids, deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); - if (bidRequests[0].userId.pubProvidedId) { - bidRequests[0].userId.pubProvidedId.forEach(ppId => { - ppId.uids.forEach(uid => { - eids.push({ source: ppId.source, id: uid.id }); - }); + bidRequests[0].userIdAsEids.forEach(eid => { + if (!eid || !eid.uids || eid.uids.length < 1) { return; } + eid.uids.forEach(uid => { + let tmp = {'source': eid.source, 'id': uid.id}; + if (eid.source == 'adserver.org') { + tmp.rti_partner = 'TDID'; + } else if (eid.source == 'uidapi.com') { + tmp.rti_partner = 'UID2'; + } + eids.push(tmp); }); - } - + }); if (eids.length) { payload.eids = eids; } @@ -1228,17 +1226,6 @@ function parseMediaType(rtbBid) { } } -function addUserId(eids, id, source, rti) { - if (id) { - if (rti) { - eids.push({ source, id, rti_partner: rti }); - } else { - eids.push({ source, id }); - } - } - return eids; -} - function getBidFloor(bid) { if (!isFn(bid.getFloor)) { return (bid.params.reserve) ? bid.params.reserve : null; diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index d4644039885..9e88e2875c7 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1318,9 +1318,33 @@ describe('AppNexusAdapter', function () { source: 'puburl2.com', uids: [{ id: 'pubid2' + }, { + id: 'pubid2-123' }] }] - } + }, + userIdAsEids: [{ + source: 'adserver.org', + uids: [{ id: 'sample-userid' }] + }, { + source: 'criteo.com', + uids: [{ id: 'sample-criteo-userid' }] + }, { + source: 'netid.de', + uids: [{ id: 'sample-netId-userid' }] + }, { + source: 'liveramp.com', + uids: [{ id: 'sample-idl-userid' }] + }, { + source: 'uidapi.com', + uids: [{ id: 'sample-uid2-value' }] + }, { + source: 'puburl.com', + uids: [{ id: 'pubid1' }] + }, { + source: 'puburl2.com', + uids: [{ id: 'pubid2' }, { id: 'pubid2-123' }] + }] }); const request = spec.buildRequests([bidRequest]); @@ -1361,6 +1385,10 @@ describe('AppNexusAdapter', function () { source: 'puburl2.com', id: 'pubid2' }); + expect(payload.eids).to.deep.include({ + source: 'puburl2.com', + id: 'pubid2-123' + }); }); it('should populate iab_support object at the root level if omid support is detected', function () { From 49eaea7ac65bcf95c707451a10c050cbefb8b05c Mon Sep 17 00:00:00 2001 From: shahinrahbariasl <56240400+shahinrahbariasl@users.noreply.github.com> Date: Wed, 8 Feb 2023 11:15:19 -0500 Subject: [PATCH 083/375] 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 --- modules/ixBidAdapter.js | 490 ++++++++++++++++--------- test/spec/modules/ixBidAdapter_spec.js | 61 +-- 2 files changed, 329 insertions(+), 222 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 29cda657f5b..360a373ba9a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -152,6 +152,11 @@ const MEDIA_TYPES = { Native: 4 }; +let baseRequestSize = 0; +let currentRequestSize = 0; +let wasAdUnitImpressionsTrimmed = false; +let currentImpressionSize = 0; + /** * Transform valid bid request config object to banner impression object that will be sent to ad server. * @@ -593,34 +598,23 @@ function getEidInfo(allEids) { * */ function buildRequest(validBidRequests, bidderRequest, impressions, version) { + baseRequestSize = 0; + currentRequestSize = 0; + wasAdUnitImpressionsTrimmed = false; + currentImpressionSize = 0; + // Always use secure HTTPS protocol. let baseUrl = SECURE_BID_URL; // Get ids from Prebid User ID Modules let eidInfo = getEidInfo(deepAccess(validBidRequests, '0.userIdAsEids')); let userEids = eidInfo.toSend; - const pageUrl = deepAccess(bidderRequest, 'refererInfo.page'); let MAX_REQUEST_SIZE = 8000; - // Modify request size limit if its FT is enabeld. - if (FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_32kb_size_limit')) { - MAX_REQUEST_SIZE = 32000 - } // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist if (window.headertag && typeof window.headertag.getIdentityInfo === 'function') { - let identityInfo = window.headertag.getIdentityInfo(); - if (identityInfo && typeof identityInfo === 'object') { - for (const partnerName in identityInfo) { - if (identityInfo.hasOwnProperty(partnerName)) { - let response = identityInfo[partnerName]; - if (!response.responsePending && response.data && typeof response.data === 'object' && - Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { - userEids.push(response.data); - } - } - } - } + addRTI(userEids, eidInfo); } // If `roundel` alias bidder, only send requests if liveramp ids exist. @@ -628,8 +622,123 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { return []; } + const requests = []; + let r = createRequest(validBidRequests); + + // getting ixdiags for adunits of the video, outstream & multi format (MF) style + let ixdiag = buildIXDiag(validBidRequests); + for (var key in ixdiag) { + r.ext.ixdiag[key] = ixdiag[key]; + } + + r = enrichRequest(r, bidderRequest, impressions, validBidRequests, userEids); + + r = applyRegulations(r, bidderRequest); + + let payload = {}; + createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload, MAX_REQUEST_SIZE); + + let requestSequenceNumber = 0; + const transactionIds = Object.keys(impressions); + let isFpdAdded = false; + + for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { + // buildRequestV2 does not have request spliting logic. + if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { + if (currentRequestSize >= MAX_REQUEST_SIZE) { + break; + } + } + if (requests.length >= MAX_REQUEST_LIMIT) { + break; + } + + r = addImpressions(impressions, transactionIds, r, adUnitIndex, MAX_REQUEST_SIZE); + currentRequestSize += currentImpressionSize; + + const fpd = deepAccess(bidderRequest, 'ortb2') || {}; + const site = { ...(fpd.site || fpd.context) }; + const user = { ...fpd.user }; + if (!isEmpty(fpd) && !isFpdAdded) { + r = addFPD(bidderRequest, r, fpd, site, user); + + const clonedRObject = deepClone(r); + + clonedRObject.site = mergeDeep({}, clonedRObject.site, site); + clonedRObject.user = mergeDeep({}, clonedRObject.user, user); + + const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + + if (requestSize < MAX_REQUEST_SIZE) { + r.site = mergeDeep({}, r.site, site); + r.user = mergeDeep({}, r.user, user); + isFpdAdded = true; + const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; + currentRequestSize += fpdRequestSize; + } else { + logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE }); + } + } + + // add identifiers info to ixDiag + r = addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl, MAX_REQUEST_SIZE); + + const isLastAdUnit = adUnitIndex === transactionIds.length - 1; + + if (wasAdUnitImpressionsTrimmed || isLastAdUnit) { + if (!isLastAdUnit || requestSequenceNumber) { + r.ext.ixdiag.sn = requestSequenceNumber; + } + + requestSequenceNumber++; + + requests.push({ + method: 'POST', + url: baseUrl + '?s=' + siteID, + data: deepClone(r), + option: { + contentType: 'text/plain', + }, + validBidRequests + }); + + currentRequestSize = baseRequestSize; + r.imp = []; + isFpdAdded = false; + } + } + + return requests; +} + +/** + * addRTI adds RTI info of the partner to retrieved user IDs from prebid ID module. + * + * @param {array} userEids userEids info retrieved from prebid + * @param {array} eidInfo eidInfo info from prebid + */ +function addRTI(userEids, eidInfo) { + let identityInfo = window.headertag.getIdentityInfo(); + if (identityInfo && typeof identityInfo === 'object') { + for (const partnerName in identityInfo) { + if (identityInfo.hasOwnProperty(partnerName)) { + let response = identityInfo[partnerName]; + if (!response.responsePending && response.data && typeof response.data === 'object' && + Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { + userEids.push(response.data); + } + } + } + } +} + +/** + * createRequest creates the base request object + * @param {array} validBidRequests A list of valid bid request config objects. + * @return {object} Object describing the request to the server. + */ +function createRequest(validBidRequests) { const r = {}; - const tmax = deepAccess(bidderRequest, 'timeout'); // Since bidderRequestId are the same for different bid request, just use the first one. r.id = validBidRequests[0].bidderRequestId.toString(); r.site = {}; @@ -639,13 +748,21 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext.ixdiag.ls = storage.localStorageIsEnabled(); r.imp = []; r.at = 1; + return r +} - // getting ixdiags for adunits of the video, outstream & multi format (MF) style - let ixdiag = buildIXDiag(validBidRequests); - for (var key in ixdiag) { - r.ext.ixdiag[key] = ixdiag[key]; - } - +/** + * enrichRequest adds userSync configs, source, and referer info to request and ixDiag objects. + * + * @param {object} r Base reuqest object. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {array} impressions A list of impressions to be added to the request. + * @param {array} validBidRequests A list of valid bid request config objects. + * @param {array} userEids User ID info retrieved from Prebid ID module. + * @return {object} Enriched object describing the request to the server. + */ +function enrichRequest(r, bidderRequest, impressions, validBidRequests, userEids) { + const tmax = deepAccess(bidderRequest, 'timeout'); if (tmax) { r.ext.ixdiag.tmax = tmax; } @@ -684,6 +801,17 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.site.ref = document.referrer; } + return r +} + +/** + * applyRegulations applies regulation info such as GDPR and GPP to the reqeust obejct. + * + * @param {object} r Base reuqest object. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @return {object} Object enriched with regulation info describing the request to the server. + */ +function applyRegulations(r, bidderRequest) { // Apply GDPR information to the request if GDPR is enabled. if (bidderRequest) { if (bidderRequest.gdprConsent) { @@ -716,6 +844,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { usPrivacy = bidderRequest.uspConsent; } + const pageUrl = deepAccess(bidderRequest, 'refererInfo.page'); if (pageUrl) { r.site.page = pageUrl; } @@ -730,7 +859,21 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { deepSetValue(r, 'regs.coppa', 1); } - const payload = {}; + return r +} + +/** + * createPayload creates the payload to be sent with the request. + * + * @param {array} validBidRequests A list of valid bid request config objects. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {object} r Reuqest object. + * @param {string} baseUrl Base exchagne URL. + * @param {array} requests List of request obejcts. + * @param {object} payload Request payload object. + * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). + */ +function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload, MAX_REQUEST_SIZE) { // Use the siteId in the first bid request as the main siteId. siteID = validBidRequests[0].params.siteId; payload.s = siteID; @@ -738,19 +881,16 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Parse additional runtime configs. const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix'; const otherIxConfig = config.getConfig(bidderCode); - const requests = []; - let requestSequenceNumber = 0; - const transactionIds = Object.keys(impressions); - const baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; + + baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; if (baseRequestSize > MAX_REQUEST_SIZE) { logError('IX Bid Adapter: Base request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.EXCEEDS_MAX_SIZE }); return requests; } - let currentRequestSize = baseRequestSize; + currentRequestSize = baseRequestSize; let fpdRequestSize = 0; - let isFpdAdded = false; if (otherIxConfig) { // Append firstPartyData to r.site.page if firstPartyData exists. @@ -766,196 +906,198 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { fpdRequestSize = encodeURIComponent(firstPartyString).length; - if (fpdRequestSize < MAX_REQUEST_SIZE) { + if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { + if (fpdRequestSize < MAX_REQUEST_SIZE) { + if ('page' in r.site) { + r.site.page += firstPartyString; + } else { + r.site.page = firstPartyString; + } + currentRequestSize += fpdRequestSize; + } else { + logError('IX Bid Adapter: IX config FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE }); + } + } else { if ('page' in r.site) { r.site.page += firstPartyString; } else { r.site.page = firstPartyString; } - currentRequestSize += fpdRequestSize; - } else { - logError('IX Bid Adapter: IX config FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE }); } } } +} - for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { - if (currentRequestSize >= MAX_REQUEST_SIZE || requests.length >= MAX_REQUEST_LIMIT) { - break; - } +/** + * addImpressions adds impressions to request object + * + * @param {array} impressions List of impressions to be added to the request. + * @param {array} transactionIds List of transaction Ids. + * @param {object} r Reuqest object. + * @param {int} adUnitIndex Index of the current add unit + * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). + * @return {object} Reqyest object with added impressions describing the request to the server. + */ +function addImpressions(impressions, transactionIds, r, adUnitIndex, MAX_REQUEST_SIZE) { + const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; + const { missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; - const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; - const { missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; - let wasAdUnitImpressionsTrimmed = false; - let remainingRequestSize = MAX_REQUEST_SIZE - currentRequestSize; - const sourceImpressions = { ixImps, missingBannerImpressions }; - const impressionObjects = Object.keys(sourceImpressions) - .map((key) => sourceImpressions[key]) - .filter(item => Array.isArray(item)) - .reduce((acc, curr) => acc.concat(...curr), []); + let remainingRequestSize = MAX_REQUEST_SIZE - currentRequestSize; + const sourceImpressions = { ixImps, missingBannerImpressions }; + const impressionObjects = Object.keys(sourceImpressions) + .map((key) => sourceImpressions[key]) + .filter(item => Array.isArray(item)) + .reduce((acc, curr) => acc.concat(...curr), []); - let currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; + currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; + if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { while (impressionObjects.length && currentImpressionSize > remainingRequestSize) { wasAdUnitImpressionsTrimmed = true; impressionObjects.pop(); currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; } + } - const gpid = impressions[transactionIds[adUnitIndex]].gpid; - const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; - const tid = impressions[transactionIds[adUnitIndex]].tid; - const sid = impressions[transactionIds[adUnitIndex]].sid - - if (impressionObjects.length && BANNER in impressionObjects[0]) { - const { id, banner: { topframe } } = impressionObjects[0]; - const _bannerImpression = { - id, - banner: { - topframe, - format: impressionObjects.map(({ banner: { w, h }, ext }) => ({ w, h, ext })) - }, - }; - - for (let i = 0; i < _bannerImpression.banner.format.length; i++) { - // We add sid in imp.ext.sid therefore, remove from banner.format[].ext - if (_bannerImpression.banner.format[i].ext != null && _bannerImpression.banner.format[i].ext.sid != null) { - delete _bannerImpression.banner.format[i].ext.sid; - } - - // add floor per size - if ('bidfloor' in impressionObjects[i]) { - _bannerImpression.banner.format[i].ext.bidfloor = impressionObjects[i].bidfloor - } - } - - const position = impressions[transactionIds[adUnitIndex]].pos; - if (isInteger(position)) { - _bannerImpression.banner.pos = position; - } + const gpid = impressions[transactionIds[adUnitIndex]].gpid; + const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; + const tid = impressions[transactionIds[adUnitIndex]].tid; + const sid = impressions[transactionIds[adUnitIndex]].sid - if (dfpAdUnitCode || gpid || tid || sid) { - _bannerImpression.ext = {}; - _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; - _bannerImpression.ext.gpid = gpid; - _bannerImpression.ext.tid = tid; - _bannerImpression.ext.sid = sid; - } + if (impressionObjects.length && BANNER in impressionObjects[0]) { + const { id, banner: { topframe } } = impressionObjects[0]; + const _bannerImpression = { + id, + banner: { + topframe, + format: impressionObjects.map(({ banner: { w, h }, ext }) => ({ w, h, ext })) + }, + }; - if ('bidfloor' in impressionObjects[0]) { - _bannerImpression.bidfloor = impressionObjects[0].bidfloor; + for (let i = 0; i < _bannerImpression.banner.format.length; i++) { + // We add sid in imp.ext.sid therefore, remove from banner.format[].ext + if (_bannerImpression.banner.format[i].ext != null && _bannerImpression.banner.format[i].ext.sid != null) { + delete _bannerImpression.banner.format[i].ext.sid; } - if ('bidfloorcur' in impressionObjects[0]) { - _bannerImpression.bidfloorcur = impressionObjects[0].bidfloorcur; + // add floor per size + if ('bidfloor' in impressionObjects[i]) { + _bannerImpression.banner.format[i].ext.bidfloor = impressionObjects[i].bidfloor; } - - r.imp.push(_bannerImpression); - } else { - // set imp.ext.gpid to resolved gpid for each imp - impressionObjects.forEach(imp => deepSetValue(imp, 'ext.gpid', gpid)); - r.imp.push(...impressionObjects); } - currentRequestSize += currentImpressionSize; - - const fpd = deepAccess(bidderRequest, 'ortb2') || {}; - if (!isEmpty(fpd) && !isFpdAdded) { - r.ext.ixdiag.fpd = true; + const position = impressions[transactionIds[adUnitIndex]].pos; + if (isInteger(position)) { + _bannerImpression.banner.pos = position; + } - const site = { ...(fpd.site || fpd.context) }; + if (dfpAdUnitCode || gpid || tid || sid) { + _bannerImpression.ext = {}; + _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; + _bannerImpression.ext.gpid = gpid; + _bannerImpression.ext.tid = tid; + _bannerImpression.ext.sid = sid; + } - Object.keys(site).forEach(key => { - if (FIRST_PARTY_DATA.SITE.indexOf(key) === -1) { - delete site[key]; - } - }); + if ('bidfloor' in impressionObjects[0]) { + _bannerImpression.bidfloor = impressionObjects[0].bidfloor; + } - const user = { ...fpd.user }; + if ('bidfloorcur' in impressionObjects[0]) { + _bannerImpression.bidfloorcur = impressionObjects[0].bidfloorcur; + } - Object.keys(user).forEach(key => { - if (FIRST_PARTY_DATA.USER.indexOf(key) === -1) { - delete user[key]; - } - }); + r.imp.push(_bannerImpression); + } else { + // set imp.ext.gpid to resolved gpid for each imp + impressionObjects.forEach(imp => deepSetValue(imp, 'ext.gpid', gpid)); + r.imp.push(...impressionObjects); + } - if (fpd.device) { - const sua = {...fpd.device.sua}; - if (!isEmpty(sua)) { - deepSetValue(r, 'device.sua', sua); - } - } + return r; +} - if (fpd.hasOwnProperty('regs') && !bidderRequest.gppConsent) { - if (fpd.regs.hasOwnProperty('gpp') && typeof fpd.regs.gpp == 'string') { - deepSetValue(r, 'regs.gpp', fpd.regs.gpp) - } +/** + * addFPD adds ortb2 first party data to request object. + * + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {object} r Reuqest object. + * @param {object} fpd ortb2 first party data. + * @param {object} site First party site data. + * @param {object} user First party user data. + * @return {object} Reqyest object with added FPD describing the request to the server. + */ +function addFPD(bidderRequest, r, fpd, site, user) { + r.ext.ixdiag.fpd = true; - if (fpd.regs.hasOwnProperty('gpp_sid') && Array.isArray(fpd.regs.gpp_sid)) { - deepSetValue(r, 'regs.gpp_sid', fpd.regs.gpp_sid) - } - } + Object.keys(site).forEach(key => { + if (FIRST_PARTY_DATA.SITE.indexOf(key) === -1) { + delete site[key]; + } + }); - const clonedRObject = deepClone(r); + Object.keys(user).forEach(key => { + if (FIRST_PARTY_DATA.USER.indexOf(key) === -1) { + delete user[key]; + } + }); - clonedRObject.site = mergeDeep({}, clonedRObject.site, site); - clonedRObject.user = mergeDeep({}, clonedRObject.user, user); + if (fpd.device) { + const sua = {...fpd.device.sua}; + if (!isEmpty(sua)) { + deepSetValue(r, 'device.sua', sua); + } + } - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + if (fpd.hasOwnProperty('regs') && !bidderRequest.gppConsent) { + if (fpd.regs.hasOwnProperty('gpp') && typeof fpd.regs.gpp == 'string') { + deepSetValue(r, 'regs.gpp', fpd.regs.gpp) + } - if (requestSize < MAX_REQUEST_SIZE) { - r.site = mergeDeep({}, r.site, site); - r.user = mergeDeep({}, r.user, user); - isFpdAdded = true; - const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; - currentRequestSize += fpdRequestSize; - } else { - logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE }); - } + if (fpd.regs.hasOwnProperty('gpp_sid') && Array.isArray(fpd.regs.gpp_sid)) { + deepSetValue(r, 'regs.gpp_sid', fpd.regs.gpp_sid) } + } - // add identifiers info to ixDiag - const pbaAdSlot = impressions[transactionIds[adUnitIndex]].pbadslot; - const tagId = impressions[transactionIds[adUnitIndex]].tagId; - const adUnitCode = impressions[transactionIds[adUnitIndex]].adUnitCode; - const divId = impressions[transactionIds[adUnitIndex]].divId; - if (pbaAdSlot || tagId || adUnitCode || divId) { - const clonedRObject = deepClone(r); - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + return r; +} + +/** + * addIdentifiersInfo adds indentifier info to ixDaig. + * + * @param {array} impressions List of impressions to be added to the request. + * @param {object} r Reuqest object. + * @param {array} transactionIds List of transaction Ids. + * @param {int} adUnitIndex Index of the current add unit + * @param {object} payload Request payload object. + * @param {string} baseUrl Base exchagne URL. + * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). + * @return {object} Reqyest object with added indentigfier info to ixDiag. + */ +function addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl, MAX_REQUEST_SIZE) { + const pbaAdSlot = impressions[transactionIds[adUnitIndex]].pbadslot; + const tagId = impressions[transactionIds[adUnitIndex]].tagId; + const adUnitCode = impressions[transactionIds[adUnitIndex]].adUnitCode; + const divId = impressions[transactionIds[adUnitIndex]].divId; + if (pbaAdSlot || tagId || adUnitCode || divId) { + const clonedRObject = deepClone(r); + const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { if (requestSize < MAX_REQUEST_SIZE) { r.ext.ixdiag.pbadslot = pbaAdSlot; r.ext.ixdiag.tagid = tagId; r.ext.ixdiag.adunitcode = adUnitCode; r.ext.ixdiag.divId = divId; } - } - - const isLastAdUnit = adUnitIndex === transactionIds.length - 1; - - if (wasAdUnitImpressionsTrimmed || isLastAdUnit) { - if (!isLastAdUnit || requestSequenceNumber) { - r.ext.ixdiag.sn = requestSequenceNumber; - } - - requestSequenceNumber++; - - requests.push({ - method: 'POST', - url: baseUrl + '?s=' + siteID, - data: deepClone(r), - option: { - contentType: 'text/plain', - }, - validBidRequests - }); - - currentRequestSize = baseRequestSize; - r.imp = []; - isFpdAdded = false; + } else { + r.ext.ixdiag.pbadslot = pbaAdSlot; + r.ext.ixdiag.tagid = tagId; + r.ext.ixdiag.adunitcode = adUnitCode; + r.ext.ixdiag.divId = divId; } } - return requests; + return r; } /** diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index f681e7786ae..5f104dce13f 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -3615,27 +3615,9 @@ describe('IndexexchangeAdapter', function () { expect(FEATURE_TOGGLES.featureToggles).to.deep.equal({}); }); - it('should set request size limit to 32KB when its feature enabled', () => { + it('6 ad units should generate only 1 request if buildRequestV2 FT is enabled', function () { sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - serverResponse.body.ext.features.pbjs_use_32kb_size_limit = { - activated: true - }; - FEATURE_TOGGLES.setFeatureToggles(serverResponse); - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.bidderRequestId = Array(10000).join('#'); - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], {}); - const lsData = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_FEATURE_TOGGLES_KEY)); - expect(lsData.features.pbjs_use_32kb_size_limit.activated).to.be.true; - }); - - it('6 ad units should generate only 2 requests if 32kb size limit FT is enabled', function () { - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - serverResponse.body.ext.features.pbjs_use_32kb_size_limit = { - activated: true - }; - serverResponse.body.ext.features.pbjs_enable_post = { + serverResponse.body.ext.features.pbjs_use_buildRequestV2 = { activated: true }; FEATURE_TOGGLES.setFeatureToggles(serverResponse); @@ -3665,47 +3647,30 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid1, bid2, bid3, bid4, bid5, bid6], DEFAULT_OPTION); expect(requests).to.be.an('array'); - // 32KB size limit causes only 2 requests to get generated. - expect(requests).to.have.lengthOf(2); + // buildRequestv2 enabled causes only 1 requests to get generated. + expect(requests).to.have.lengthOf(1); for (let request of requests) { expect(request.method).to.equal('POST'); } }); - it('4 ad units should generate only 1 requests if 32kb size limit FT is enabled', function () { + it('1 request with 2 ad units, buildRequestV2 enabled', function () { sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - serverResponse.body.ext.features.pbjs_use_32kb_size_limit = { - activated: true - }; - serverResponse.body.ext.features.pbjs_enable_post = { + serverResponse.body.ext.features.pbjs_use_buildRequestV2 = { activated: true }; FEATURE_TOGGLES.setFeatureToggles(serverResponse); - const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - bid1.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; - bid1.params.siteId = '121'; - bid1.adUnitCode = 'div-gpt-1' - bid1.transactionId = 'tr1'; - bid1.bidId = '2f6g5s5e'; - - const bid2 = utils.deepClone(bid1); - bid2.transactionId = 'tr2'; - - const bid3 = utils.deepClone(bid1); - bid3.transactionId = 'tr3'; - - const bid4 = utils.deepClone(bid1); - bid4.transactionId = 'tr4'; - - const requests = spec.buildRequests([bid1, bid2, bid3, bid4], DEFAULT_OPTION); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; + bid.params.siteId = '124'; + bid.adUnitCode = 'div-gpt-1' + bid.transactionId = '152e36d1-1241-4242-t35e-y1dv34d12315'; + bid.bidId = '2f6g5s5e'; + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); expect(requests).to.be.an('array'); - // 32KB size limit causes only 1 requests to get generated. expect(requests).to.have.lengthOf(1); - for (let request of requests) { - expect(request.method).to.equal('POST'); - } }); }); From 4689d3b34e074b17129700d316161bd59808410f Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Thu, 9 Feb 2023 04:36:54 -0500 Subject: [PATCH 084/375] bug fix for grid adapter not applying jw segment data when bidderRequest ortb2.user data doe not exist (#9521) --- modules/gridBidAdapter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index aa6c0ab668f..ce7fcc680dd 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -288,9 +288,11 @@ export const spec = { return; } + if (request.user) user = request.user; + const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); if (ortb2UserData && ortb2UserData.length) { - user = request.user || { data: [] }; + if (!user) user = { data: [] }; user = mergeDeep(user, { data: [...ortb2UserData] }); From b56e2ff8bbd3398625e8e207ff58baa359ef2696 Mon Sep 17 00:00:00 2001 From: Taro FURUKAWA <6879289+0tarof@users.noreply.github.com> Date: Thu, 9 Feb 2023 20:20:54 +0900 Subject: [PATCH 085/375] add sua support (#9523) --- modules/ajaBidAdapter.js | 7 ++++++- test/spec/modules/ajaBidAdapter_spec.js | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 121dc2ef96f..67b448eb484 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -1,4 +1,4 @@ -import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError, logWarn } from '../src/utils.js'; +import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError, logWarn, deepAccess } from '../src/utils.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js'; @@ -64,6 +64,11 @@ export const spec = { })) } + const sua = deepAccess(bidRequest, 'ortb2.device.sua'); + if (sua) { + queryString = tryAppendQueryString(queryString, 'sua', JSON.stringify(sua)); + } + bidRequests.push({ method: 'GET', url: URL, diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index a2095d52857..7cf5698f7d4 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -45,6 +45,26 @@ describe('AjaAdapter', function () { bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'Android', + version: ['8', '0', '0'] + }, + browsers: [ + {brand: 'Not_A Brand', version: ['99', '0', '0', '0']}, + {brand: 'Google Chrome', version: ['109', '0', '5414', '119']}, + {brand: 'Chromium', version: ['109', '0', '5414', '119']} + ], + mobile: 1, + model: 'SM-G955U', + bitness: '64', + architecture: '' + } + } + } } ]; @@ -58,7 +78,7 @@ describe('AjaAdapter', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('GET'); - expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&'); + expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&sua=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22Android%22%2C%22version%22%3A%5B%228%22%2C%220%22%2C%220%22%5D%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Not_A%20Brand%22%2C%22version%22%3A%5B%2299%22%2C%220%22%2C%220%22%2C%220%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%2C%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%5D%2C%22mobile%22%3A1%2C%22model%22%3A%22SM-G955U%22%2C%22bitness%22%3A%2264%22%2C%22architecture%22%3A%22%22%7D&'); }); }); From 0880777873248c5358507a118f9c97d84147630b Mon Sep 17 00:00:00 2001 From: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Date: Thu, 9 Feb 2023 18:55:52 +0530 Subject: [PATCH 086/375] Read and pass device.sua object to translator payload (#9526) Co-authored-by: Kapil Tuptewar --- modules/pubmaticBidAdapter.js | 4 ++++ test/spec/modules/pubmaticBidAdapter_spec.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 296a4314ac8..d8f5002ebd7 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1189,6 +1189,10 @@ export const spec = { if (commonFpd.bcat) { blockedIabCategories = blockedIabCategories.concat(commonFpd.bcat); } + // check if fpd ortb2 contains device property with sua object + if (commonFpd.device?.sua) { + payload.device.sua = commonFpd.device?.sua; + } if (commonFpd.ext?.prebid?.bidderparams?.[bidderRequest.bidderCode]?.acat) { const acatParams = commonFpd.ext.prebid.bidderparams[bidderRequest.bidderCode].acat; diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 6b240cb2d06..fa8f809e8a2 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2607,6 +2607,21 @@ describe('PubMatic adapter', function () { expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); }); + it('should pass device.sua if present in bidderRequest fpd ortb2 object', function () { + const suaObject = {'source': 2, 'platform': {'brand': 'macOS', 'version': ['12', '4', '0']}, 'browsers': [{'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']}], 'mobile': 0, 'model': '', 'bitness': '64', 'architecture': 'x86'}; + let request = spec.buildRequests(multipleMediaRequests, { + auctionId: 'new-auction-id', + ortb2: { + device: { + sua: suaObject + } + } + }); + let data = JSON.parse(request.data); + expect(data.device.sua).to.exist.and.to.be.an('object'); + expect(data.device.sua).to.deep.equal(suaObject); + }); + it('Request params should have valid native bid request for all valid params', function () { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' From 53acdd7eaad78516be0e133869ba487eb3cc9e4d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 Feb 2023 13:54:16 +0000 Subject: [PATCH 087/375] Prebid 7.36.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index dca60dbf37e..1e76c5b38a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.36.0-pre", + "version": "7.36.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 7163c732634..bfd92f7cfff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.36.0-pre", + "version": "7.36.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 7fd7b3016bf0bab1dacf59bd305e51dc805dc45a Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 Feb 2023 13:54:16 +0000 Subject: [PATCH 088/375] Increment version to 7.37.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e76c5b38a7..1c6a10e6ac6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.36.0", + "version": "7.37.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index bfd92f7cfff..95ba8e522d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.36.0", + "version": "7.37.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 90b3b400cf0125694de3f3634f109c2254e2d1b7 Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Thu, 9 Feb 2023 09:41:08 -0500 Subject: [PATCH 089/375] 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 --- modules/sonobiBidAdapter.js | 11 +-------- test/spec/modules/sonobiBidAdapter_spec.js | 28 +++------------------- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index f9cc1f3b353..87358705cb5 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, deepClone, getGptSlotInfoForAdUnitCode, isFn, isPlainObject } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, getGptSlotInfoForAdUnitCode, isFn, isPlainObject } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; @@ -132,15 +132,6 @@ export const spec = { if (validBidRequests[0].schain) { payload.schain = JSON.stringify(validBidRequests[0].schain); } - if (deepAccess(validBidRequests[0], 'userId') && Object.keys(validBidRequests[0].userId).length > 0) { - const userIds = deepClone(validBidRequests[0].userId); - - if (userIds.id5id) { - userIds.id5id = deepAccess(userIds, 'id5id.uid'); - } - - payload.userid = JSON.stringify(userIds); - } const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); if (Array.isArray(eids) && eids.length > 0) { diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index a9382f092e2..56ed4d5196e 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -371,7 +371,7 @@ describe('SonobiBidAdapter', function () { } } }; - const bidRequests = spec.buildRequests(bidRequest, {...bidderRequests, ortb2}); + const bidRequests = spec.buildRequests(bidRequest, { ...bidderRequests, ortb2 }); expect(bidRequests.data.fpd).to.equal(JSON.stringify(ortb2)); }); @@ -540,7 +540,7 @@ describe('SonobiBidAdapter', function () { ]); }); - it('should return a properly formatted request with userid as a JSON-encoded set of User ID results', function () { + it('should return a properly formatted request with the userid value omitted when the userId object is present on the bidRequest. ', function () { bidRequest[0].userId = { 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': { 'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': { 'linkType': 2 } } }; bidRequest[1].userId = { 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': { 'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': { 'linkType': 2 } } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests); @@ -548,29 +548,7 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.method).to.equal('GET'); expect(bidRequests.data.ref).not.to.be.empty; expect(bidRequests.data.s).not.to.be.empty; - expect(JSON.parse(bidRequests.data.userid)).to.eql({ 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ' }); - }); - - it('should return a properly formatted request with userid omitted if there are no userIds', function () { - bidRequest[0].userId = {}; - bidRequest[1].userId = {}; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); - expect(bidRequests.data.ref).not.to.be.empty; - expect(bidRequests.data.s).not.to.be.empty; - expect(bidRequests.data.userid).to.equal(undefined); - }); - - it('should return a properly formatted request with userid omitted', function () { - bidRequest[0].userId = undefined; - bidRequest[1].userId = undefined; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); - expect(bidRequests.data.ref).not.to.be.empty; - expect(bidRequests.data.s).not.to.be.empty; - expect(bidRequests.data.userid).to.equal(undefined); + expect(bidRequests.data.userid).to.be.undefined; }); it('should return a properly formatted request with keywrods included as a csv of strings', function () { From 732b7dcadff3c0a19b6d020413687ff318829200 Mon Sep 17 00:00:00 2001 From: Espen <2290914+espen-j@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:32:26 +0100 Subject: [PATCH 090/375] Update adapter docs (c-wire/prebid#3) (#9528) --- modules/cwireBidAdapter.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md index 026c2943c6b..9804250b906 100644 --- a/modules/cwireBidAdapter.md +++ b/modules/cwireBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: C-WIRE Bid Adapter Module Type: Bidder Adapter -Maintainer: devs@cwire.ch +Maintainer: devs@cwire.com ``` ## Description @@ -40,10 +40,16 @@ var adUnits = [ params: { pageId: 1422, // required - number placementId: 2211521, // required - number - cwcreative: '42', // optional - id of creative to force - refgroups: 'test-user', // optional - name of group or coma separated list of groups to force } }] } ]; ``` + +### URL parameters + +For debugging and testing purposes url parameters can be set. + +**Example:** + +`https://www.some-site.com/article.html?cwdebug=true&cwfeatures=feature1,feature2&cwcreative=1234` From 2e8d15978fb40e686dcbabe13694778defe0b8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Udi=20Talias=20=E2=9A=9B=EF=B8=8F?= Date: Fri, 10 Feb 2023 04:57:15 +0200 Subject: [PATCH 091/375] MinuteMediaPlus Bid Adapter: New Bid Adapter (#9430) * MinuteMediaPlus bid adapter * bidder code changed to --- modules/minutemediaplusBidAdapter.js | 291 ++++++++++ modules/minutemediaplusBidAdapter.md | 35 ++ .../modules/minutemediaplusBidAdapter_spec.js | 519 ++++++++++++++++++ 3 files changed, 845 insertions(+) create mode 100644 modules/minutemediaplusBidAdapter.js create mode 100644 modules/minutemediaplusBidAdapter.md create mode 100644 test/spec/modules/minutemediaplusBidAdapter_spec.js diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js new file mode 100644 index 00000000000..578db846289 --- /dev/null +++ b/modules/minutemediaplusBidAdapter.js @@ -0,0 +1,291 @@ +import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const GVLID = 918; +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'mmplus'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +export const SUPPORTED_ID_SYSTEMS = { + 'britepoolid': 1, + 'criteoId': 1, + 'id5id': 1, + 'idl_env': 1, + 'lipb': 1, + 'netId': 1, + 'parrableId': 1, + 'pubcid': 1, + 'tdid': 1, + 'pubProvidedId': 1 +}; +const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); + +function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.minutemedia-prebid.com`; +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { + const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; + let { bidFloor, ext } = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes + }; + + appendUserIdsToRequestPayload(data, userId); + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data + }; + + _each(ext, (value, key) => { + dto.data['ext.' + key] = value; + }); + + return dto; +} + +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + if (SUPPORTED_ID_SYSTEMS[idSystemProviderName]) { + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + } + }); +} + +function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + requests.push(request); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const { bidId } = request.data; + const { results } = serverResponse.body; + + let output = []; + + try { + results.forEach(result => { + const { creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER } = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + meta: { + advertiserDomains: advertiserDomains || [] + } + }; + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + return output; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { + let syncs = []; + const { iframeEnabled, pixelEnabled } = syncOptions; + const { gdprApplies, consentString = '' } = gdprConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` + if (iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `https://sync.minutemedia-prebid.com/api/sync/iframe/${params}` + }); + } + if (pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://sync.minutemedia-prebid.com/api/sync/image/${params}` + }); + } + return syncs; +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { h = (h << 5) - h + s.charCodeAt(i++) | 0; } + } + return prefix + h; +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({ value, created }); + storage.setDataInLocalStorage(key, data); + } catch (e) { } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/minutemediaplusBidAdapter.md b/modules/minutemediaplusBidAdapter.md new file mode 100644 index 00000000000..410c00e7017 --- /dev/null +++ b/modules/minutemediaplusBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name:** MinuteMediaPlus Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** hb@minutemedia.com + +# Description + +Module that connects to MinuteMediaPlus's demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'mmplus', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js new file mode 100644 index 00000000000..de38554f010 --- /dev/null +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -0,0 +1,519 @@ +import { expect } from 'chai'; +import { + spec as adapter, + SUPPORTED_ID_SYSTEMS, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from 'modules/minutemediaplusBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { version } from 'package.json'; +import { useFakeTimers } from 'sinon'; +import { BANNER, VIDEO } from '../../../src/mediaTypes'; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER] +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['minutemedia-prebid.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('MinuteMediaPlus Bid Adapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mmplus: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + prebidVersion: version, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + } + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + usPrivacy: 'consent_string', + sizes: ['300x250', '300x600'], + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + } + }); + }); + + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.minutemedia-prebid.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'type': 'image' + }]); + }) + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({ price: 1, ad: '' }); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['minutemedia-prebid.com'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return { lipbid: id }; + case 'parrableId': + return { eid: id }; + case 'id5id': + return { uid: id }; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({ 'c_id': '1' }); + const pid = extractPID({ 'p_id': '1' }); + const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({ 'cID': '1' }); + const pid = extractPID({ 'Pid': '2' }); + const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mmplus: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mmplus: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const { value, created } = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({ event: 'send' }); + const { event } = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); From d119d6526594de2eb64fea2df6ac54c67fd5e7f9 Mon Sep 17 00:00:00 2001 From: Prebid-Team Date: Fri, 10 Feb 2023 15:27:12 +0530 Subject: [PATCH 092/375] 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 --- modules/incrxBidAdapter.js | 2 ++ test/spec/modules/incrxBidAdapter_spec.js | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/modules/incrxBidAdapter.js b/modules/incrxBidAdapter.js index 914ef0b904e..d054309ee40 100644 --- a/modules/incrxBidAdapter.js +++ b/modules/incrxBidAdapter.js @@ -68,6 +68,8 @@ export const spec = { requestId: response.slotBidId, cpm: response.cpm, currency: response.currency || DEFAULT_CURRENCY, + adType: response.adType || '1', + settings: response.settings, width: response.adWidth, height: response.adHeight, ttl: CREATIVE_TTL, diff --git a/test/spec/modules/incrxBidAdapter_spec.js b/test/spec/modules/incrxBidAdapter_spec.js index 8fb95742766..3fb4ffe2cd3 100644 --- a/test/spec/modules/incrxBidAdapter_spec.js +++ b/test/spec/modules/incrxBidAdapter_spec.js @@ -71,6 +71,8 @@ describe('IncrementX', function () { cpm: '0.7', ad: '

Ad from IncrementX

', slotBidId: 'bid-id-123456', + adType: '1', + settings: '1,2', nurl: 'htt://nurl.com', statusText: 'Success' } @@ -80,6 +82,8 @@ describe('IncrementX', function () { requestId: 'bid-id-123456', cpm: '0.7', currency: 'USD', + adType: '1', + settings: '1,2', netRevenue: false, width: '300', height: '250', From 926e4a35c740eece993dbb9f64c3d0a2435914a9 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Sun, 12 Feb 2023 16:21:45 +0200 Subject: [PATCH 093/375] KueezRtbBidAdapter - pass gpp and bid data to server. (#9491) --- modules/kueezRtbBidAdapter.js | 74 ++++++++++++----- test/spec/modules/kueezRtbBidAdapter_spec.js | 85 +++++++++++++++----- 2 files changed, 117 insertions(+), 42 deletions(-) diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 4b0e5055328..821b3263597 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.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 = 1165; 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,7 +104,14 @@ 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); @@ -107,6 +128,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 +177,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 +191,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 +237,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 +262,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 +288,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 +297,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/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 30b2a46cefb..7c0d8e92001 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_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/kueezRtbBidAdapter.js'; import * as utils from 'src/utils.js'; -import { version } from 'package.json'; -import { useFakeTimers } from 'sinon'; -import { BANNER, VIDEO } from '../../../src/mediaTypes'; +import {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] @@ -46,6 +51,10 @@ const VIDEO_BID = { 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { 'subDomain': SUB_DOMAIN, @@ -78,10 +87,18 @@ 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] + } } }; @@ -134,7 +151,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 +230,9 @@ describe('KueezRtbBidAdapter', 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,11 +243,20 @@ describe('KueezRtbBidAdapter', 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', @@ -259,6 +288,9 @@ describe('KueezRtbBidAdapter', 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,7 +299,16 @@ describe('KueezRtbBidAdapter', 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'], url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', @@ -296,7 +337,7 @@ describe('KueezRtbBidAdapter', 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 +346,7 @@ describe('KueezRtbBidAdapter', 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.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -313,7 +354,7 @@ describe('KueezRtbBidAdapter', 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.kueezrtb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -329,12 +370,12 @@ describe('KueezRtbBidAdapter', 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 +435,11 @@ describe('KueezRtbBidAdapter', 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 +458,18 @@ describe('KueezRtbBidAdapter', 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 +529,7 @@ describe('KueezRtbBidAdapter', 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 +545,8 @@ describe('KueezRtbBidAdapter', 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'); }); From 5ba4e650e94701e81ac5d574c3607b6b80f6e261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Mon, 13 Feb 2023 15:30:03 +0100 Subject: [PATCH 094/375] Criteo Bid Adapter: Map full user & site objects (#9534) DPP-4310 --- modules/criteoBidAdapter.js | 5 ++--- test/spec/modules/criteoBidAdapter_spec.js | 25 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index fb42066a008..48fea78eeb4 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -523,9 +523,8 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { } }; }; - request.user = { - ext: bidderRequest.userExt - }; + request.user = bidderRequest.ortb2?.user || {}; + request.site = bidderRequest.ortb2?.site || {}; if (bidderRequest && bidderRequest.ceh) { request.user.ceh = bidderRequest.ceh; } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index c54453bd7fc..da1a0ca7f32 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1156,6 +1156,18 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request with first party data', function () { const siteData = { keywords: ['power tools'], + content: { + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }] + }, ext: { data: { pageType: 'article' @@ -1164,6 +1176,16 @@ describe('The Criteo bidding adapter', function () { }; const userData = { gender: 'M', + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }], ext: { data: { registered: true @@ -1203,7 +1225,8 @@ describe('The Criteo bidding adapter', function () { const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); expect(request.data.publisher.ext).to.deep.equal({ data: { pageType: 'article' } }); - expect(request.data.user.ext).to.deep.equal({ data: { registered: true } }); + expect(request.data.user).to.deep.equal(userData); + expect(request.data.site).to.deep.equal(siteData); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { From b378dbae4318c708835e66d6d9ca64d7ae7138ee Mon Sep 17 00:00:00 2001 From: pnh-pubx <73683023+pnh-pubx@users.noreply.github.com> Date: Tue, 14 Feb 2023 18:18:47 +0530 Subject: [PATCH 095/375] Update: Replaced adUnitCount with adUnits array (#9510) --- modules/pubxaiAnalyticsAdapter.js | 4 ++-- test/spec/modules/pubxaiAnalyticsAdapter_spec.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index f7e73dd5fdd..96665c786bf 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -6,7 +6,7 @@ import CONSTANTS from '../src/constants.json'; const emptyUrl = ''; const analyticsType = 'endpoint'; -const pubxaiAnalyticsVersion = 'v1.1.0'; +const pubxaiAnalyticsVersion = 'v1.2.0'; const defaultHost = 'api.pbxai.com'; const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; @@ -154,7 +154,7 @@ function send(data, status) { search: location.search }); if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - data.pageDetail.adUnitCount = data.auctionInit.adUnitCodes ? data.auctionInit.adUnitCodes.length : null; + data.pageDetail.adUnits = data.auctionInit.adUnitCodes; data.initOptions.auctionId = data.auctionInit.auctionId; delete data.auctionInit; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 1ad23b9a41e..1dce87e4b8e 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -567,7 +567,9 @@ describe('pubxai analytics adapter', function() { 'host': location.host, 'path': location.pathname, 'search': location.search, - 'adUnitCount': 1 + 'adUnits': [ + '/19968336/header-bid-tag-1' + ] }, 'floorDetail': { 'fetchStatus': 'success', From 3a9d4cafccc1da31fdd34cae4db33a1078ccca20 Mon Sep 17 00:00:00 2001 From: msmeza Date: Tue, 14 Feb 2023 15:13:13 +0100 Subject: [PATCH 096/375] 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 --- modules/livewrappedBidAdapter.js | 44 +-- .../modules/livewrappedBidAdapter_spec.js | 269 +++++++++++++++++- 2 files changed, 295 insertions(+), 18 deletions(-) diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 3839eb80b91..af754ae3ff0 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep} from '../src/utils.js'; +import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep, isFn, isPlainObject} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {find} from '../src/polyfill.js'; @@ -68,7 +68,9 @@ export const spec = { bidUrl = bidUrl ? bidUrl.params.bidUrl : URL; url = url ? url.params.url : (getAppDomain() || getTopWindowLocation(bidderRequest)); test = test ? test.params.test : undefined; - var adRequests = bidRequests.map(bidToAdRequest); + const currency = config.getConfig('currency.adServerCurrency') || 'USD'; + var adRequests = bidRequests.map(b => bidToAdRequest(b, currency)); + const adRequestsContainFloors = adRequests.some(r => r.flr !== undefined); if (eids) { ortb2 = mergeDeep(mergeDeep({}, ortb2 || {}), eids); @@ -96,7 +98,8 @@ export const spec = { rcv: getAdblockerRecovered(), adRequests: [...adRequests], rtbData: ortb2, - schain: schain + schain: schain, + flrCur: adRequestsContainFloors ? currency : undefined }; if (config.getConfig().debug) { @@ -223,13 +226,14 @@ function hasPubcid(bid) { return !!bid.crumbs && !!bid.crumbs.pubcid; } -function bidToAdRequest(bid) { +function bidToAdRequest(bid, currency) { var adRequest = { adUnitId: bid.params.adUnitId, callerAdUnitId: bid.params.adUnitName || bid.adUnitCode || bid.placementCode, bidId: bid.bidId, transactionId: bid.transactionId, formats: getSizes(bid).map(sizeToFormat), + flr: getBidFloor(bid, currency), options: bid.params.options }; @@ -264,6 +268,22 @@ function sizeToFormat(size) { } } +function getBidFloor(bid, currency) { + if (!isFn(bid.getFloor)) { + return undefined; + } + + const floor = bid.getFloor({ + currency: currency, + mediaType: '*', + size: '*' + }); + + return isPlainObject(floor) && !isNaN(floor.floor) && floor.currency == currency + ? floor.floor + : undefined; +} + function getAdblockerRecovered() { try { return getWindowTop().I12C && getWindowTop().I12C.Morph === 1; @@ -302,21 +322,13 @@ function getDeviceIfa() { } function getDeviceWidth() { - let device = config.getConfig('device'); - if (typeof device === 'object' && device.width) { - return device.width; - } - - return window.innerWidth; + const device = config.getConfig('device') || {}; + return device.w || window.innerWidth; } function getDeviceHeight() { - let device = config.getConfig('device'); - if (typeof device === 'object' && device.height) { - return device.height; - } - - return window.innerHeight; + const device = config.getConfig('device') || {}; + return device.h || window.innerHeight; } function getCoppa() { diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index a4fee5e3b26..6bbdf6e1705 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -13,6 +13,10 @@ describe('Livewrapped adapter tests', function () { window.livewrapped = undefined; + config.setConfig({ + device: { w: 100, h: 100 } + }); + bidderRequest = { bidderCode: 'livewrapped', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', @@ -43,6 +47,7 @@ describe('Livewrapped adapter tests', function () { afterEach(function () { sandbox.restore(); + config.resetConfig(); }); describe('isBidRequestValid', function() { @@ -351,7 +356,7 @@ describe('Livewrapped adapter tests', function () { expect(data).to.deep.equal(expectedQuery); }); - it('should make a well-formed single request object with ad blocker revovered parameter', function() { + it('should make a well-formed single request object with ad blocker recovered parameter', function() { sandbox.stub(utils, 'getWindowTop').returns({ I12C: { Morph: 1 } }); sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); @@ -489,7 +494,7 @@ describe('Livewrapped adapter tests', function () { return {bundle: 'bundle', domain: 'https://appdomain.com'}; } if (key === 'device') { - return {ifa: 'ifa', width: 300, height: 200}; + return {ifa: 'ifa', w: 300, h: 200}; } return origGetConfig.apply(config, arguments); }); @@ -839,6 +844,266 @@ describe('Livewrapped adapter tests', function () { expect(data).to.deep.equal(expectedQuery); }); + + it('width and height should default to values from window when not set in config', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + config.resetConfig(); + + let testbidRequest = clone(bidderRequest); + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: window.innerWidth, + height: window.innerHeight, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + }); + + describe('price floors module', function() { + it('price floors module disabled', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor does not return an object', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return undefined; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns a NaN floor', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: undefined }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns unexpected currency', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: 10, currency: 'EUR' }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns valid floor - ad server currency', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'currency.adServerCurrency') { + return 'EUR'; + } + return origGetConfig.apply(config, arguments); + }); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: 10, currency: 'EUR' }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + flrCur: 'EUR', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}], + flr: 10 + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns valid floor - default currency', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: 10, currency: 'USD' }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + flrCur: 'USD', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}], + flr: 10 + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); }); it('should make use of user ids if available', function() { From b85c4e4bef163596d8f76584aeba3879cf835d8b Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Mon, 9 Jan 2023 14:01:27 -0500 Subject: [PATCH 097/375] Define "VIDEO" compile time feature flag --- features.json | 3 +- modules/sizeMappingV2.js | 2 +- src/adapterManager.js | 4 +- src/adapters/bidderFactory.js | 2 +- src/auction.js | 54 ++--- src/prebid.js | 37 +-- src/utils.js | 2 +- test/spec/auctionmanager_spec.js | 218 +++++++++--------- test/spec/modules/sizeMappingV2_spec.js | 4 + test/spec/unit/core/adapterManager_spec.js | 7 +- test/spec/unit/pbjs_api_spec.js | 250 +++++++++++---------- 11 files changed, 308 insertions(+), 275 deletions(-) diff --git a/features.json b/features.json index c0f7e4d75a9..ccb2166a05f 100644 --- a/features.json +++ b/features.json @@ -1,3 +1,4 @@ [ - "NATIVE" + "NATIVE", + "VIDEO" ] diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 05475b3e143..d212d98f50b 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -182,7 +182,7 @@ export function checkAdUnitSetupHook(adUnits) { } } - if (mediaTypes.video) { + if (FEATURES.VIDEO && mediaTypes.video) { if (mediaTypes.video.playerSize) { // Ad unit is using 'mediaTypes.video.playerSize' instead of the new property 'sizeConfig'. Apply the old checks! validatedVideo = validatedBanner ? adUnitSetupChecks.validateVideoMediaType(validatedBanner) : adUnitSetupChecks.validateVideoMediaType(adUnit); diff --git a/src/adapterManager.js b/src/adapterManager.js index f4f9e59fb84..fae41a6888f 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -443,7 +443,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request function getSupportedMediaTypes(bidderCode) { let supportedMediaTypes = []; - if (includes(adapterManager.videoAdapters, bidderCode)) supportedMediaTypes.push('video'); + if (FEATURES.VIDEO && includes(adapterManager.videoAdapters, bidderCode)) supportedMediaTypes.push('video'); if (FEATURES.NATIVE && includes(nativeAdapters, bidderCode)) supportedMediaTypes.push('native'); return supportedMediaTypes; } @@ -455,7 +455,7 @@ adapterManager.registerBidAdapter = function (bidAdapter, bidderCode, {supported if (typeof bidAdapter.callBids === 'function') { _bidderRegistry[bidderCode] = bidAdapter; - if (includes(supportedMediaTypes, 'video')) { + if (FEATURES.VIDEO && includes(supportedMediaTypes, 'video')) { adapterManager.videoAdapters.push(bidderCode); } if (FEATURES.NATIVE && includes(supportedMediaTypes, 'native')) { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 55c84e57062..e3cc607a3e2 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -599,7 +599,7 @@ export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) { logError(errorMessage('Native bid missing some required properties.')); return false; } - if (bid.mediaType === 'video' && !isValidVideoBid(bid, {index})) { + if (FEATURES.VIDEO && bid.mediaType === 'video' && !isValidVideoBid(bid, {index})) { logError(errorMessage(`Video bid does not have required vastUrl or renderer property`)); return false; } diff --git a/src/auction.js b/src/auction.js index 41e6fe3565b..3d729c0b860 100644 --- a/src/auction.js +++ b/src/auction.js @@ -463,7 +463,7 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM handleBidResponse(adUnitCode, bid, (done) => { let bidResponse = getPreparedBidForAuction(bid); - if (bidResponse.mediaType === VIDEO) { + if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { tryAddVideoBid(auctionInstance, bidResponse, done); } else { if (FEATURES.NATIVE && bidResponse.native != null && typeof bidResponse.native === 'object') { @@ -574,28 +574,30 @@ export function addBidToAuction(auctionInstance, bidResponse) { // Video bids may fail if the cache is down, or there's trouble on the network. function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = auctionManager.index} = {}) { - let addBid = true; - - const videoMediaType = deepAccess( - index.getMediaTypes({ - requestId: bidResponse.originalRequestId || bidResponse.requestId, - transactionId: bidResponse.transactionId - }), 'video'); - const context = videoMediaType && deepAccess(videoMediaType, 'context'); - const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); - - if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) { - if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { - addBid = false; - callPrebidCache(auctionInstance, bidResponse, afterBidAdded, videoMediaType); - } else if (!bidResponse.vastUrl) { - logError('videoCacheKey specified but not required vastUrl for video bid'); - addBid = false; + if (FEATURES.VIDEO) { + let addBid = true; + + const videoMediaType = deepAccess( + index.getMediaTypes({ + requestId: bidResponse.originalRequestId || bidResponse.requestId, + transactionId: bidResponse.transactionId + }), 'video'); + const context = videoMediaType && deepAccess(videoMediaType, 'context'); + const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); + + if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) { + if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { + addBid = false; + callPrebidCache(auctionInstance, bidResponse, afterBidAdded, videoMediaType); + } else if (!bidResponse.vastUrl) { + logError('videoCacheKey specified but not required vastUrl for video bid'); + addBid = false; + } + } + if (addBid) { + addBidToAuction(auctionInstance, bidResponse); + afterBidAdded(); } - } - if (addBid) { - addBidToAuction(auctionInstance, bidResponse); - afterBidAdded(); } } @@ -610,7 +612,7 @@ const addLegacyFieldsIfNeeded = (bidResponse) => { } } -const storeInCache = (batch) => { +const _storeInCache = (batch) => { store(batch.map(entry => entry.bidResponse), function (error, cacheIds) { cacheIds.forEach((cacheId, i) => { const { auctionInstance, bidResponse, afterBidAdded } = batch[i]; @@ -637,6 +639,8 @@ const storeInCache = (batch) => { }); }; +const storeInCache = FEATURES.VIDEO ? _storeInCache : () => {}; + let batchSize, batchTimeout; config.getConfig('cache', (cacheConfig) => { batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 @@ -772,7 +776,7 @@ function setupBidTargeting(bidObject) { */ export function getMediaTypeGranularity(mediaType, mediaTypes, mediaTypePriceGranularity) { if (mediaType && mediaTypePriceGranularity) { - if (mediaType === VIDEO) { + if (FEATURES.VIDEO && mediaType === VIDEO) { const context = deepAccess(mediaTypes, `${VIDEO}.context`, 'instream'); if (mediaTypePriceGranularity[`${VIDEO}-${context}`]) { return mediaTypePriceGranularity[`${VIDEO}-${context}`]; @@ -881,7 +885,7 @@ export function getStandardBidderSettings(mediaType, bidderCode) { standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = defaultAdserverTargeting(); } - if (mediaType === 'video') { + if (FEATURES.VIDEO && mediaType === 'video') { const adserverTargeting = standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].slice(); standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = adserverTargeting; diff --git a/src/prebid.js b/src/prebid.js index 9ad72409990..db6941e5227 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -225,7 +225,6 @@ function validateAdUnit(adUnit) { export const adUnitSetupChecks = { validateAdUnit, validateBannerMediaType, - validateVideoMediaType, validateSizes }; @@ -233,6 +232,10 @@ if (FEATURES.NATIVE) { Object.assign(adUnitSetupChecks, {validateNativeMediaType}); } +if (FEATURES.VIDEO) { + Object.assign(adUnitSetupChecks, { validateVideoMediaType }); +} + export const checkAdUnitSetup = hook('sync', function (adUnits) { const validatedAdUnits = []; @@ -248,7 +251,7 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { if (mediaTypes.banner.hasOwnProperty('pos')) validatedBanner = validateAdUnitPos(validatedBanner, 'banner'); } - if (mediaTypes.video) { + if (FEATURES.VIDEO && mediaTypes.video) { validatedVideo = validatedBanner ? validateVideoMediaType(validatedBanner) : validateVideoMediaType(adUnit); if (mediaTypes.video.hasOwnProperty('pos')) validatedVideo = validateAdUnitPos(validatedVideo, 'video'); } @@ -995,21 +998,23 @@ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { * @alias module:pbjs.markWinningBidAsUsed */ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { - let bids = []; - - if (markBidRequest.adUnitCode && markBidRequest.adId) { - bids = auctionManager.getBidsReceived() - .filter(bid => bid.adId === markBidRequest.adId && bid.adUnitCode === markBidRequest.adUnitCode); - } else if (markBidRequest.adUnitCode) { - bids = targeting.getWinningBids(markBidRequest.adUnitCode); - } else if (markBidRequest.adId) { - bids = auctionManager.getBidsReceived().filter(bid => bid.adId === markBidRequest.adId); - } else { - logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); - } + if (FEATURES.VIDEO) { + let bids = []; + + if (markBidRequest.adUnitCode && markBidRequest.adId) { + bids = auctionManager.getBidsReceived() + .filter(bid => bid.adId === markBidRequest.adId && bid.adUnitCode === markBidRequest.adUnitCode); + } else if (markBidRequest.adUnitCode) { + bids = targeting.getWinningBids(markBidRequest.adUnitCode); + } else if (markBidRequest.adId) { + bids = auctionManager.getBidsReceived().filter(bid => bid.adId === markBidRequest.adId); + } else { + logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); + } - if (bids.length > 0) { - bids[0].status = CONSTANTS.BID_STATUS.RENDERED; + if (bids.length > 0) { + bids[0].status = CONSTANTS.BID_STATUS.RENDERED; + } } }; diff --git a/src/utils.js b/src/utils.js index 869e1007841..07925605b34 100644 --- a/src/utils.js +++ b/src/utils.js @@ -904,7 +904,7 @@ export function isValidMediaTypes(mediaTypes) { return false; } - if (mediaTypes.video && mediaTypes.video.context) { + if (FEATURES.VIDEO && mediaTypes.video && mediaTypes.video.context) { return includes(SUPPORTED_STREAM_TYPES, mediaTypes.video.context); } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index e1ecf801aa3..4d24ff13c2d 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -232,21 +232,23 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); - it('No bidder level configuration defined - default for video', function () { - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache' - } + if (FEATURES.VIDEO) { + it('No bidder level configuration defined - default for video', function () { + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }); + $$PREBID_GLOBAL$$.bidderSettings = {}; + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; + + let expected = getDefaultExpected(videoBid); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); + assert.deepEqual(response, expected); }); - $$PREBID_GLOBAL$$.bidderSettings = {}; - let videoBid = utils.deepClone(bid); - videoBid.mediaType = 'video'; - videoBid.videoCacheKey = 'abc123def'; - - let expected = getDefaultExpected(videoBid); - let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); - assert.deepEqual(response, expected); - }); + } it('Custom configuration for all bidders', function () { $$PREBID_GLOBAL$$.bidderSettings = @@ -311,17 +313,18 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); - it('Custom configuration for all bidders with video bid', function () { - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache' - } - }); - let videoBid = utils.deepClone(bid); - videoBid.mediaType = 'video'; - videoBid.videoCacheKey = 'abc123def'; + if (FEATURES.VIDEO) { + it('Custom configuration for all bidders with video bid', function () { + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }); + let videoBid = utils.deepClone(bid); + videoBid.mediaType = 'video'; + videoBid.videoCacheKey = 'abc123def'; - $$PREBID_GLOBAL$$.bidderSettings = + $$PREBID_GLOBAL$$.bidderSettings = { standard: { adserverTargeting: [ @@ -387,11 +390,12 @@ describe('auctionmanager.js', function () { } }; - let expected = getDefaultExpected(videoBid); + let expected = getDefaultExpected(videoBid); - let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); - assert.deepEqual(response, expected); - }); + let response = getKeyValueTargetingPairs(videoBid.bidderCode, videoBid); + assert.deepEqual(response, expected); + }); + } it('Custom configuration for one bidder', function () { $$PREBID_GLOBAL$$.bidderSettings = @@ -1385,63 +1389,65 @@ describe('auctionmanager.js', function () { } describe('getMediaTypeGranularity', function () { - it('video', function () { - let mediaTypes = { video: {id: '1'} }; - - // mediaType is video and video.context is undefined - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - - expect(getMediaTypeGranularity('video', {}, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - `` - expect(getMediaTypeGranularity('video', undefined, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - - // also when mediaTypes.video is undefined - mediaTypes = { banner: {} }; - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - - // also when mediaTypes is undefined - expect(getMediaTypeGranularity('video', {}, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - }); + if (FEATURES.VIDEO) { + it('video', function () { + let mediaTypes = { video: {id: '1'} }; + + // mediaType is video and video.context is undefined + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + + expect(getMediaTypeGranularity('video', {}, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + `` + expect(getMediaTypeGranularity('video', undefined, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + + // also when mediaTypes.video is undefined + mediaTypes = { banner: {} }; + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + + // also when mediaTypes is undefined + expect(getMediaTypeGranularity('video', {}, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + }); - it('video-outstream', function () { - let mediaTypes = { video: { context: 'outstream' } }; + it('video-outstream', function () { + let mediaTypes = { video: { context: 'outstream' } }; - expect(getMediaTypeGranularity('video', mediaTypes, { - 'banner': 'low', 'video': 'medium', 'video-outstream': 'high' - })).to.equal('high'); - }); + expect(getMediaTypeGranularity('video', mediaTypes, { + 'banner': 'low', 'video': 'medium', 'video-outstream': 'high' + })).to.equal('high'); + }); - it('video-instream', function () { - let mediaTypes = { video: { context: 'instream' } }; + it('video-instream', function () { + let mediaTypes = { video: { context: 'instream' } }; - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', video: 'medium', 'video-instream': 'high' - })).to.equal('high'); + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', video: 'medium', 'video-instream': 'high' + })).to.equal('high'); - // fall back to video if video-instream not found - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', video: 'medium' - })).to.equal('medium'); + // fall back to video if video-instream not found + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', video: 'medium' + })).to.equal('medium'); - expect(getMediaTypeGranularity('video', {mediaTypes: {banner: {}}}, { - banner: 'low', video: 'medium' - })).to.equal('medium'); - }); + expect(getMediaTypeGranularity('video', {mediaTypes: {banner: {}}}, { + banner: 'low', video: 'medium' + })).to.equal('medium'); + }); + } it('native', function () { expect(getMediaTypeGranularity('native', {native: {}}, { @@ -1543,34 +1549,36 @@ describe('auctionmanager.js', function () { }); }) - it('should call auction done after prebid cache is complete for mediaType video', function() { - bids[0].mediaType = 'video'; - let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; + if (FEATURES.VIDEO) { + it('should call auction done after prebid cache is complete for mediaType video', function() { + bids[0].mediaType = 'video'; + let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; - let opts = { - mediaType: { - video: { - context: 'instream', - playerSize: [640, 480], - }, - } - }; - bidRequests = [ - mockBidRequest(bids[0], opts), - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), - ]; + let opts = { + mediaType: { + video: { + context: 'instream', + playerSize: [640, 480], + }, + } + }; + bidRequests = [ + mockBidRequest(bids[0], opts), + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + ]; - let cbs = auctionCallbacks(doneSpy, auction); - cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]); - cbs.adapterDone.call(bidRequests[0]); - cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]); - cbs.adapterDone.call(bidRequests[1]); - assert.equal(doneSpy.callCount, 0); - const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; - const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; - server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); - assert.equal(doneSpy.callCount, 1); - }); + let cbs = auctionCallbacks(doneSpy, auction); + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]); + cbs.adapterDone.call(bidRequests[0]); + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[1]); + assert.equal(doneSpy.callCount, 0); + const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; + const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; + server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); + assert.equal(doneSpy.callCount, 1); + }); + } it('should convert cpm to number', () => { auction.addBidReceived = sinon.spy(); diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 27fd7a33c14..16c1527a3ad 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -445,6 +445,10 @@ describe('sizeMappingV2', function () { }); describe('video mediaTypes checks', function () { + if (!FEATURES.VIDEO) { + return; + } + beforeEach(function () { sinon.spy(adUnitSetupChecks, 'validateVideoMediaType'); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 3fea8988a95..80e2197fbb7 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1602,12 +1602,15 @@ describe('adapterManager tests', function () { }); it('should add alias to registry when original adapter is using bidderFactory', function() { - let thisSpec = Object.assign(spec, { supportedMediaTypes: ['video'] }); + const mediaType = FEATURES.VIDEO ? 'video' : 'banner' + let thisSpec = Object.assign(spec, { supportedMediaTypes: [mediaType] }); registerBidder(thisSpec); const alias = 'aliasBidder'; adapterManager.aliasBidAdapter(CODE, alias); expect(adapterManager.bidderRegistry).to.have.property(alias); - expect(adapterManager.videoAdapters).to.include(alias); + if (FEATURES.VIDEO) { + expect(adapterManager.videoAdapters).to.include(alias); + } }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 0867cb0e399..b069b13fc37 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -2109,7 +2109,9 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.requestBids({ adUnits: fullAdUnit }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal( + FEATURES.VIDEO ? [[640, 480]] : [[300, 250]] + ); expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.deep.equal([150, 150]); expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.deep.equal([75, 75]); @@ -2142,45 +2144,47 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]); expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - let mixedAdUnit = [{ - code: 'test3', - bids: [], - sizes: [[300, 250], [300, 600]], - mediaTypes: { - video: { - context: 'outstream', - playerSize: [[400, 350]] - }, - native: { - image: { - aspect_ratios: [200, 150], - required: true + if (FEATURES.VIDEO) { + let mixedAdUnit = [{ + code: 'test3', + bids: [], + sizes: [[300, 250], [300, 600]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[400, 350]] + }, + native: { + image: { + aspect_ratios: [200, 150], + required: true + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: mixedAdUnit - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]); - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: mixedAdUnit + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - let altVideoPlayerSize = [{ - code: 'test4', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [640, 480] + let altVideoPlayerSize = [{ + code: 'test4', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [640, 480] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: altVideoPlayerSize - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: altVideoPlayerSize + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + } }); it('should normalize adUnit.sizes and adUnit.mediaTypes.banner.sizes', function () { @@ -2285,41 +2289,43 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].mediaTypes.banner).to.be.undefined; assert.ok(logErrorSpy.calledWith('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.')); - let badVideo1 = [{ - code: 'testb2', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: ['600x400'] + if (FEATURES.VIDEO) { + let badVideo1 = [{ + code: 'testb2', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: ['600x400'] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badVideo1 - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badVideo1 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); - let badVideo2 = [{ - code: 'testb3', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [['300', '200']] + let badVideo2 = [{ + code: 'testb3', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [['300', '200']] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badVideo2 - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badVideo2 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + } if (FEATURES.NATIVE) { let badNativeImgSize = [{ @@ -3326,69 +3332,71 @@ describe('Unit: Prebid Module', function () { }); }); - describe('markWinningBidAsUsed', function () { - it('marks the bid object as used for the given adUnitCode/adId combination', function () { - // make sure the auction has "state" and does not reload the fixtures - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; - - // mark the bid and verify the state has changed to RENDERED - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + if (FEATURES.VIDEO) { + describe('markWinningBidAsUsed', function () { + it('marks the bid object as used for the given adUnitCode/adId combination', function () { + // make sure the auction has "state" and does not reload the fixtures + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; + + // mark the bid and verify the state has changed to RENDERED + const winningBid = targeting.getWinningBids(adUnitCode)[0]; + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: winningBid.adId }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); + + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + it('try and mark the bid object, but fail because we supplied the wrong adId', function () { + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; - it('try and mark the bid object, but fail because we supplied the wrong adId', function () { - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + const winningBid = targeting.getWinningBids(adUnitCode)[0]; + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: 'miss' }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: 'miss' }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); - - expect(markedBid.status).to.not.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + expect(markedBid.status).to.not.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); - it('marks the winning bid object as used for the given adUnitCode', function () { - // make sure the auction has "state" and does not reload the fixtures - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + it('marks the winning bid object as used for the given adUnitCode', function () { + // make sure the auction has "state" and does not reload the fixtures + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; - // mark the bid and verify the state has changed to RENDERED - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + // mark the bid and verify the state has changed to RENDERED + const winningBid = targeting.getWinningBids(adUnitCode)[0]; + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); - it('marks a bid object as used for the given adId', function () { - // make sure the auction has "state" and does not reload the fixtures - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + it('marks a bid object as used for the given adId', function () { + // make sure the auction has "state" and does not reload the fixtures + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; - // mark the bid and verify the state has changed to RENDERED - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + // mark the bid and verify the state has changed to RENDERED + const winningBid = targeting.getWinningBids(adUnitCode)[0]; + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adId: winningBid.adId }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); }); - }); + } describe('setTargetingForAst', function () { let targeting; From e95769360d6ac5a3cdfe06df1bbd259d62255d1e Mon Sep 17 00:00:00 2001 From: Anthony Date: Tue, 14 Feb 2023 16:17:47 +0100 Subject: [PATCH 098/375] Proxistore Bid Adapter: migrate to new subdomain (#9537) * Migrate to new subdomain * Upgrade domain in tests as well --- modules/proxistoreBidAdapter.js | 4 ++-- test/spec/modules/proxistoreBidAdapter_spec.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index 04d363be7fb..1f113f9c432 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -3,9 +3,9 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'proxistore'; const PROXISTORE_VENDOR_ID = 418; -const COOKIE_BASE_URL = 'https://abs.proxistore.com/v3/rtb/prebid/multi'; +const COOKIE_BASE_URL = 'https://api.proxistore.com/v3/rtb/prebid/multi'; const COOKIE_LESS_URL = - 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi'; + 'https://api.cookieless-proxistore.com/v3/rtb/prebid/multi'; function _createServerRequest(bidRequests, bidderRequest) { var sizeIds = []; diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 5b9f35c5bcf..bcddb9e8b04 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -55,9 +55,9 @@ describe('ProxistoreBidAdapter', function () { }); describe('buildRequests', function () { const url = { - cookieBase: 'https://abs.proxistore.com/v3/rtb/prebid/multi', + cookieBase: 'https://api.proxistore.com/v3/rtb/prebid/multi', cookieLess: - 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi', + 'https://api.cookieless-proxistore.com/v3/rtb/prebid/multi', }; let request = spec.buildRequests([bid], bidderRequest); From f7b78cf64df38e98709189db9c45f7c9452e46e6 Mon Sep 17 00:00:00 2001 From: BaronJHYu <254878848@qq.com> Date: Wed, 15 Feb 2023 21:13:23 +0800 Subject: [PATCH 099/375] 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 --- modules/discoveryBidAdapter.js | 34 ++++++++++++++++++----------- modules/mediagoBidAdapter.js | 39 +++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 0c536892716..8145c9d25e7 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -40,7 +40,7 @@ const NATIVERET = { title: { len: 75, }, - } + }, ], plcmttype: 1, privacy: 1, @@ -63,9 +63,9 @@ const getUserID = () => { let idm = storage.getCookie(COOKIE_KEY_MGUID); if (idd && !idm) { - idm = idd + idm = idd; } else if (idm && !idd) { - idd = idm + idd = idm; } else if (!idd && !idm) { const uuid = utils.generateUUID(); storage.setCookie(COOKIE_KEY_MGUID, uuid); @@ -228,7 +228,7 @@ function getItems(validBidRequests, bidderRequest) { let id = '' + (i + 1); if (mediaTypes.native) { - ret = { ...NATIVERET, ...{ id, bidFloor } } + ret = { ...NATIVERET, ...{ id, bidFloor } }; } // banner if (mediaTypes.banner) { @@ -276,6 +276,11 @@ function getItems(validBidRequests, bidderRequest) { */ function getParam(validBidRequests, bidderRequest) { const pubcid = utils.deepAccess(validBidRequests[0], 'crumbs.pubcid'); + const sharedid = + utils.deepAccess(validBidRequests[0], 'userId.sharedid.id') || + utils.deepAccess(validBidRequests[0], 'userId.pubcid'); + const eids = validBidRequests[0].userIdAsEids || validBidRequests[0].userId; + let isMobile = getDevice() ? 1 : 0; // input test status by Publisher. more frequently for test true req let isTest = validBidRequests[0].params.test || 0; @@ -284,7 +289,8 @@ function getParam(validBidRequests, bidderRequest) { const timeout = bidderRequest.timeout || 2000; - const domain = utils.deepAccess(bidderRequest, 'refererInfo.domain') || document.domain; + const domain = + utils.deepAccess(bidderRequest, 'refererInfo.domain') || document.domain; const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); const page = utils.deepAccess(bidderRequest, 'refererInfo.page'); const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); @@ -304,10 +310,14 @@ function getParam(validBidRequests, bidderRequest) { ua: navigator.userAgent, language: /en/.test(navigator.language) ? 'en' : navigator.language, }, + ext: { + eids, + }, user: { buyeruid: getUserID(), - id: pubcid, + id: sharedid || pubcid, }, + eids, tmax: timeout, site: { name: domain, @@ -317,7 +327,7 @@ function getParam(validBidRequests, bidderRequest) { mobile: isMobile, cat: [], // todo publisher: { - id: globals['publisher'] + id: globals['publisher'], // todo // name: xxx }, @@ -401,8 +411,8 @@ export const spec = { nurl: getKv(bid, 'nurl'), ttl: TIME_TO_LIVE, meta: { - advertiserDomains: getKv(bid, 'adomain') || [] - } + advertiserDomains: getKv(bid, 'adomain') || [], + }, }; if (mediaType === 'native') { const adm = getKv(bid, 'adm'); @@ -448,7 +458,7 @@ export const spec = { native.impressionTrackers.push(tracker.url); break; // case 2: - // native.javascriptTrackers = ``; + // native.javascriptTrackers = ``; // break; } }); @@ -484,8 +494,8 @@ export const spec = { */ onBidWon: function (bid) { if (bid['nurl']) { - utils.triggerPixel(bid['nurl']) + utils.triggerPixel(bid['nurl']); } - } + }, }; registerBidder(spec); diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 16460b195f5..9b1b02e00a2 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -148,12 +148,12 @@ function isMobileAndTablet() { */ // function getBidFloor(bid, mediaType, sizes) { // var floor; -// var size = sizes.length === 1 ? sizes[0] : "*"; -// if (typeof bid.getFloor === "function") { -// const floorInfo = bid.getFloor({ currency: "USD", mediaType, size }); +// var size = sizes.length === 1 ? sizes[0] : '*'; +// if (typeof bid.getFloor === 'function') { +// const floorInfo = bid.getFloor({ currency: 'USD', mediaType, size }); // if ( -// typeof floorInfo === "object" && -// floorInfo.currency === "USD" && +// typeof floorInfo === 'object' && +// floorInfo.currency === 'USD' && // !isNaN(parseFloat(floorInfo.floor)) // ) { // floor = parseFloat(floorInfo.floor); @@ -255,7 +255,6 @@ function getItems(validBidRequests, bidderRequest) { // utils.deepAccess(req, 'ortb2Imp.ext.gpid') || // utils.deepAccess(req, 'ortb2Imp.ext.data.pbadslot') || // utils.deepAccess(req, 'params.placementId', 0); - // console.log("wjh getItems:", req, bidFloor, gpid); // if (mediaTypes.native) {} // banner广告类型 @@ -270,7 +269,7 @@ function getItems(validBidRequests, bidderRequest) { pos: 1, }, ext: { - // gpid: gpid, // 加入后无法返回广告 + // gpid: gpid, // 加入后无法返回广告 }, }; itemMaps[id] = { @@ -296,6 +295,8 @@ function getParam(validBidRequests, bidderRequest) { const sharedid = utils.deepAccess(validBidRequests[0], 'userId.sharedid.id') || utils.deepAccess(validBidRequests[0], 'userId.pubcid'); + const eids = validBidRequests[0].userIdAsEids || validBidRequests[0].userId; + let isMobile = isMobileAndTablet() ? 1 : 0; // input test status by Publisher. more frequently for test true req let isTest = validBidRequests[0].params.test || 0; @@ -318,19 +319,23 @@ function getParam(validBidRequests, bidderRequest) { cur: ['USD'], device: { connectiontype: 0, - // ip: '64.188.178.115', + // ip: '98.61.5.0', js: 1, - // language: "en", - // os: "Microsoft Windows", - // ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19043", + // language: 'en', + // os: 'Microsoft Windows', + // ua: 'Mozilla/5.0 (Linux; Android 12; SM-G970U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36', os: navigator.platform || '', ua: navigator.userAgent, language: /en/.test(navigator.language) ? 'en' : navigator.language, }, - ext: {}, + ext: { + eids, + }, user: { - id: sharedid || pubcid || getUserID(), + buyeruid: getUserID(), + id: sharedid || pubcid, }, + eids, site: { name: domain, domain: domain, @@ -420,12 +425,12 @@ export const spec = { nurl: getProperty(bid, 'nurl'), // adserverTargeting: { // granularityMultiplier: 0.1, - // priceGranularity: "pbHg", - // pbMg: "0.01", + // priceGranularity: 'pbHg', + // pbMg: '0.01', // }, - // pbMg: "0.01", + // pbMg: '0.01', // granularityMultiplier: 0.1, - // priceGranularity: "pbHg", + // priceGranularity: 'pbHg', }; bidResponses.push(bidResponse); } From 16166bde8690569f74e07ecb1d780ea576c09820 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 15 Feb 2023 11:46:52 -0700 Subject: [PATCH 100/375] 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 --- .../prebidServerBidAdapter/ortbConverter.js | 6 + src/adapterManager.js | 44 ++++-- src/utils.js | 2 +- .../modules/prebidServerBidAdapter_spec.js | 56 +++++++ test/spec/unit/core/adapterManager_spec.js | 147 ++++++++++++++++++ 5 files changed, 240 insertions(+), 15 deletions(-) diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index e35a3825826..be034b3cfbe 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -32,6 +32,12 @@ const PBS_CONVERTER = ortbConverter({ imp(buildImp, proxyBidRequest, context) { Object.assign(context, proxyBidRequest.pbsData); const imp = buildImp(proxyBidRequest, context); + (proxyBidRequest.bids || []).forEach(bid => { + if (bid.ortb2Imp && Object.keys(bid.ortb2Imp).length > 0) { + // set bidder-level imp attributes; see https://github.com/prebid/prebid-server/issues/2335 + deepSetValue(imp, `ext.prebid.imp.${bid.bidder}`, bid.ortb2Imp); + } + }); if (Object.values(SUPPORTED_MEDIA_TYPES).some(mtype => imp[mtype])) { imp.secure = context.s2sBidRequest.s2sConfig.secure; return imp; diff --git a/src/adapterManager.js b/src/adapterManager.js index f4f9e59fb84..c9715fe0168 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -65,15 +65,21 @@ var _analyticsRegistry = {}; function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics}) { return adUnits.reduce((result, adUnit) => { - result.push(adUnit.bids.filter(bid => bid.bidder === bidderCode) - .reduce((bids, bid) => { - bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'nativeParams', - 'nativeOrtbRequest', - 'ortb2Imp', - 'mediaType', - 'renderer' - ])); + const bids = adUnit.bids.filter(bid => bid.bidder === bidderCode); + if (bidderCode == null && bids.length === 0 && adUnit.s2sBid != null) { + bids.push({bidder: null}); + } + result.push( + bids.reduce((bids, bid) => { + bid = Object.assign({}, bid, + {ortb2Imp: mergeDeep({}, adUnit.ortb2Imp, bid.ortb2Imp)}, + getDefinedParams(adUnit, [ + 'nativeParams', + 'nativeOrtbRequest', + 'mediaType', + 'renderer' + ]) + ); const mediaTypes = bid.mediaTypes == null ? adUnit.mediaTypes : bid.mediaTypes @@ -128,9 +134,18 @@ export const filterBidsForAdUnit = hook('sync', _filterBidsForAdUnit, 'filterBid function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { let adUnitsCopy = deepClone(adUnits); + let hasModuleBids = false; adUnitsCopy.forEach((adUnit) => { // filter out client side bids + const s2sBids = adUnit.bids.filter((b) => b.module === 'pbsBidAdapter' && b.params?.configName === s2sConfig.configName); + if (s2sBids.length === 1) { + adUnit.s2sBid = s2sBids[0]; + hasModuleBids = true; + adUnit.ortb2Imp = mergeDeep({}, adUnit.s2sBid.ortb2Imp, adUnit.ortb2Imp); + } else if (s2sBids.length > 1) { + logWarn('Multiple "module" bids for the same s2s configuration; all will be ignored', s2sBids); + } adUnit.bids = filterBidsForAdUnit(adUnit.bids, s2sConfig) .map((bid) => { bid.bid_id = getUniqueIdentifierStr(); @@ -140,9 +155,9 @@ function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { // don't send empty requests adUnitsCopy = adUnitsCopy.filter(adUnit => { - return adUnit.bids.length !== 0; + return adUnit.bids.length !== 0 || adUnit.s2sBid != null; }); - return adUnitsCopy; + return {adUnits: adUnitsCopy, hasModuleBids}; } function getAdUnitCopyForClientAdapters(adUnits) { @@ -244,11 +259,12 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a _s2sConfigs.forEach(s2sConfig => { if (s2sConfig && s2sConfig.enabled) { - let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); + let {adUnits: adUnitsS2SCopy, hasModuleBids} = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); // uniquePbsTid is so we know which server to send which bids to during the callBids function let uniquePbsTid = generateUUID(); - serverBidders.forEach(bidderCode => { + + (serverBidders.length === 0 && hasModuleBids ? [null] : serverBidders).forEach(bidderCode => { const bidderRequestId = getUniqueIdentifierStr(); const metrics = auctionMetrics.fork(); const bidderRequest = addOrtb2({ @@ -279,7 +295,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a bidRequests.forEach(request => { if (request.adUnitsS2SCopy === undefined) { - request.adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnitCopy => adUnitCopy.bids.length > 0); + request.adUnitsS2SCopy = adUnitsS2SCopy.filter(au => au.bids.length > 0 || au.s2sBid != null); } }); } diff --git a/src/utils.js b/src/utils.js index 869e1007841..8df45bab9d2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -710,7 +710,7 @@ export function getKeyByValue(obj, value) { export function getBidderCodes(adUnits = $$PREBID_GLOBAL$$.adUnits) { // this could memoize adUnits return adUnits.map(unit => unit.bids.map(bid => bid.bidder) - .reduce(flatten, [])).reduce(flatten, []).filter(uniques); + .reduce(flatten, [])).reduce(flatten, []).filter((bidder) => typeof bidder !== 'undefined').filter(uniques); } export function isGptPubadsDefined() { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index cffc75f6949..0ce060b9904 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2660,6 +2660,62 @@ describe('S2S Adapter', function () { }); }); + describe('Bidder-level ortb2Imp', () => { + beforeEach(() => { + config.setConfig({ + s2sConfig: { + ...CONFIG, + bidders: ['A', 'B'] + } + }) + }) + it('should be set on imp.ext.prebid.imp', () => { + const s2sReq = utils.deepClone(REQUEST); + s2sReq.ad_units[0].ortb2Imp = {l0: 'adUnit'}; + s2sReq.ad_units[0].bids = [ + { + bidder: 'A', + bid_id: 1, + ortb2Imp: { + l2: 'A' + } + }, + { + bidder: 'B', + bid_id: 2, + ortb2Imp: { + l2: 'B' + } + } + ]; + const bidderReqs = [ + { + ...BID_REQUESTS[0], + bidderCode: 'A', + bids: [{ + bidId: 1, + bidder: 'A' + }] + }, + { + ...BID_REQUESTS[0], + bidderCode: 'B', + bids: [{ + bidId: 2, + bidder: 'B' + }] + } + ] + adapter.callBids(s2sReq, bidderReqs, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.imp[0].l0).to.eql('adUnit'); + expect(req.imp[0].ext.prebid.imp).to.eql({ + A: {l2: 'A'}, + B: {l2: 'B'} + }); + }); + }); + describe('ext.prebid config', function () { it('should send \"imp.ext.prebid.storedrequest.id\" if \"ortb2Imp.ext.prebid.storedrequest.id\" is set', function () { const consentConfig = { s2sConfig: CONFIG }; diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 3fea8988a95..06366e8cc5c 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1757,6 +1757,153 @@ describe('adapterManager tests', function () { requests.appnexus.bids.forEach((bid) => expect(bid.ortb2).to.eql(requests.appnexus.ortb2)); }); + it('should merge in bid-level ortb2Imp with adUnit-level ortb2Imp', () => { + const adUnit = { + ...adUnits[1], + ortb2Imp: {oneone: {twoone: 'val'}, onetwo: 'val'} + }; + adUnit.bids[0].ortb2Imp = {oneone: {twotwo: 'val'}, onethree: 'val', onetwo: 'val2'}; + const reqs = Object.fromEntries( + adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {}) + .map((req) => [req.bidderCode, req]) + ); + sinon.assert.match(reqs[adUnit.bids[0].bidder].bids[0].ortb2Imp, { + oneone: { + twoone: 'val', + twotwo: 'val', + }, + onetwo: 'val2', + onethree: 'val' + }) + sinon.assert.match(reqs[adUnit.bids[1].bidder].bids[0].ortb2Imp, adUnit.ortb2Imp) + }) + + it('picks ortb2Imp from "module" when only one s2sConfig is set', () => { + config.setConfig({ + s2sConfig: [ + { + enabled: true, + adapter: 'mockS2S1', + } + ] + }); + const adUnit = { + code: 'mockau', + ortb2Imp: { + p1: 'adUnit' + }, + bids: [ + { + module: 'pbsBidAdapter', + ortb2Imp: { + p2: 'module' + } + } + ] + }; + const req = adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {})[0]; + [req.adUnitsS2SCopy[0].ortb2Imp, req.bids[0].ortb2Imp].forEach(imp => { + sinon.assert.match(imp, { + p1: 'adUnit', + p2: 'module' + }); + }); + }); + + describe('with named s2s configs', () => { + beforeEach(() => { + config.setConfig({ + s2sConfig: [ + { + enabled: true, + adapter: 'mockS2S1', + configName: 'one', + bidders: ['A'] + }, + { + enabled: true, + adapter: 'mockS2S2', + configName: 'two', + bidders: ['B'] + } + ] + }) + }); + + it('generates requests for "module" bids', () => { + const adUnit = { + code: 'mockau', + ortb2Imp: { + p1: 'adUnit' + }, + bids: [ + { + module: 'pbsBidAdapter', + params: {configName: 'one'}, + ortb2Imp: { + p2: 'one' + } + }, + { + module: 'pbsBidAdapter', + params: {configName: 'two'}, + ortb2Imp: { + p2: 'two' + } + } + ] + }; + const reqs = adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {}); + [reqs[0].adUnitsS2SCopy[0].ortb2Imp, reqs[0].bids[0].ortb2Imp].forEach(imp => { + sinon.assert.match(imp, { + p1: 'adUnit', + p2: 'one' + }) + }); + [reqs[1].adUnitsS2SCopy[0].ortb2Imp, reqs[1].bids[0].ortb2Imp].forEach(imp => { + sinon.assert.match(imp, { + p1: 'adUnit', + p2: 'two' + }) + }); + }); + + it('applies module-level ortb2Imp to "normal" s2s requests', () => { + const adUnit = { + code: 'mockau', + ortb2Imp: { + p1: 'adUnit' + }, + bids: [ + { + module: 'pbsBidAdapter', + params: {configName: 'one'}, + ortb2Imp: { + p2: 'one' + } + }, + { + bidder: 'A', + ortb2Imp: { + p3: 'bidderA' + } + } + ] + }; + const reqs = adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {}); + expect(reqs.length).to.equal(1); + sinon.assert.match(reqs[0].adUnitsS2SCopy[0].ortb2Imp, { + p1: 'adUnit', + p2: 'one' + }) + sinon.assert.match(reqs[0].bids[0].ortb2Imp, { + p1: 'adUnit', + p2: 'one', + p3: 'bidderA' + }) + }); + }); + describe('when calling the s2s adapter', () => { beforeEach(() => { config.setConfig({ From 7b7389c5abdd05626f71c3df606a93713d1b9f85 Mon Sep 17 00:00:00 2001 From: JacobKlein26 <42449375+JacobKlein26@users.noreply.github.com> Date: Wed, 15 Feb 2023 13:58:36 -0500 Subject: [PATCH 101/375] 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 (&) --- modules/nextMillenniumBidAdapter.js | 10 +++++++++- test/spec/modules/nextMillenniumBidAdapter_spec.js | 11 +++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 655f5fc3a35..6abb369522b 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -24,6 +24,7 @@ import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; const TEST_ENDPOINT = 'https://test.pbs.nextmillmedia.com/openrtb2/auction'; +const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?'; const REPORT_ENDPOINT = 'https://report2.hb.brainlyads.com/statistics/metric'; const TIME_TO_LIVE = 360; const VIDEO_PARAMS = [ @@ -182,7 +183,7 @@ export const spec = { height: bid.h, creativeId: bid.adid, currency: response.cur, - netRevenue: false, + netRevenue: true, ttl: TIME_TO_LIVE, meta: { advertiserDomains: bid.adomain || [] @@ -231,6 +232,13 @@ export const spec = { }) } + if (!pixels.length) { + let syncUrl = SYNC_ENDPOINT; + if (gdprConsent && gdprConsent.gdprApplies) syncUrl += 'gdpr=1&gdpr_consent=' + gdprConsent.consentString + '&'; + if (uspConsent) syncUrl += 'us_privacy=' + uspConsent + '&'; + if (syncOptions.iframeEnabled) pixels.push({type: 'iframe', url: syncUrl + 'type=iframe'}); + if (syncOptions.pixelEnabled) pixels.push({type: 'image', url: syncUrl + 'type=image'}); + } return pixels; }, diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 571bb5fc584..7f40e7d89f8 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -142,6 +142,17 @@ describe('nextMillenniumBidAdapterTests', function() { expect(userSync[0].url).to.equal('urlB'); }); + it('Test getUserSyncs with no response', function () { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions, [], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array') + expect(userSync[0].type).to.equal('iframe') + expect(userSync[0].url).to.equal('https://cookies.nextmillmedia.com/sync?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&type=iframe') + }) + it('Test getUserSyncs function if GDPR is undefined', function () { const syncOptions = { 'iframeEnabled': false, From 7b2fdd57c0f4c16db3f58fc90db7f870669235d9 Mon Sep 17 00:00:00 2001 From: Gammelin Guillaume Date: Wed, 15 Feb 2023 20:07:18 +0100 Subject: [PATCH 102/375] Adagio Bid Adapter: update video params validation (#9542) * Adagio Bid Adapter: update video params validation * Adagio: update adapter doc --- modules/adagioBidAdapter.js | 28 +++++++++++----------- modules/adagioBidAdapter.md | 4 +++- test/spec/modules/adagioBidAdapter_spec.js | 4 ++-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 12fd50c6636..adb9744428f 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -15,6 +15,7 @@ import { isFn, isInteger, isNumber, + isArrayOfNums, logError, logInfo, logWarn, @@ -48,33 +49,32 @@ const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0k const ADAGIO_PUBKEY_E = 65537; const CURRENCY = 'USD'; -// This provide a whitelist and a basic validation -// of OpenRTB 2.5 options used by the Adagio SSP. -// https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf +// This provide a whitelist and a basic validation of OpenRTB 2.6 options used by the Adagio SSP. +// https://iabtechlab.com/wp-content/uploads/2022/04/OpenRTB-2-6_FINAL.pdf export const ORTB_VIDEO_PARAMS = { 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), 'minduration': (value) => isInteger(value), 'maxduration': (value) => isInteger(value), - 'protocols': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].indexOf(v) !== -1), + 'protocols': (value) => isArrayOfNums(value), 'w': (value) => isInteger(value), 'h': (value) => isInteger(value), 'startdelay': (value) => isInteger(value), - 'placement': (value) => [1, 2, 3, 4, 5].indexOf(value) !== -1, - 'linearity': (value) => [1, 2].indexOf(value) !== -1, - 'skip': (value) => [0, 1].indexOf(value) !== -1, + 'placement': (value) => isInteger(value), + 'linearity': (value) => isInteger(value), + 'skip': (value) => isInteger(value), 'skipmin': (value) => isInteger(value), 'skipafter': (value) => isInteger(value), 'sequence': (value) => isInteger(value), - 'battr': (value) => Array.isArray(value) && value.every(v => Array.from({length: 17}, (_, i) => i + 1).indexOf(v) !== -1), + 'battr': (value) => isArrayOfNums(value), 'maxextended': (value) => isInteger(value), 'minbitrate': (value) => isInteger(value), 'maxbitrate': (value) => isInteger(value), - 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, - 'playbackmethod': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1), - 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, - 'delivery': (value) => [1, 2, 3].indexOf(value) !== -1, - 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].indexOf(value) !== -1, - 'api': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1) + 'boxingallowed': (value) => isInteger(value), + 'playbackmethod': (value) => isArrayOfNums(value), + 'playbackend': (value) => isInteger(value), + 'delivery': (value) => isInteger(value), + 'pos': (value) => isInteger(value), + 'api': (value) => isArrayOfNums(value) }; let currentWindow; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index b804a72fea9..f10c0a75e4d 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -81,8 +81,10 @@ var adUnits = [ cpm: 3.00 // default to 1.00 }, video: { + api: [2, 7], // Required - Your video player must at least support the value 2 and/or 7. + playbackMethod: [6], // Highly recommended skip: 0 - // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. + // OpenRTB video options defined here override ones defined in mediaTypes. }, native: { // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 8abdc621922..ba1db077488 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -381,8 +381,8 @@ describe('Adagio bid adapter', () => { context: 'outstream', playerSize: [[300, 250]], mimes: ['video/mp4'], - api: 5, // will be removed because invalid - playbackmethod: [7], // will be removed because invalid + api: 'val', // will be removed because invalid + playbackmethod: ['val'], // will be removed because invalid } }, }).withParams({ From 623b88edcccde6c414f101c80375612c8f39268b Mon Sep 17 00:00:00 2001 From: Patrick Loughrey Date: Wed, 15 Feb 2023 14:21:48 -0500 Subject: [PATCH 103/375] 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> --- modules/tripleliftBidAdapter.js | 4 ++++ test/spec/modules/tripleliftBidAdapter_spec.js | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 3ce44d067bf..8012a4a8051 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -436,6 +436,10 @@ function _buildResponseObject(bidderRequest, bid) { if (bid.tl_source && bid.tl_source == 'tlx') { bidResponse.meta.mediaType = 'native'; } + + if (creativeId) { + bidResponse.meta.networkId = creativeId.slice(0, creativeId.indexOf('_')); + } }; return bidResponse; } diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index a20719d89e5..221ffb8371f 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -1437,6 +1437,13 @@ describe('triplelift adapter', function () { expect(result[0].meta.advertiserDomains[1]).to.equal('internetalerts.org'); expect(result[1].meta).to.not.have.key('advertiserDomains'); }); + + it('should include networkId in the meta field if available', function () { + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result[1].meta.networkId).to.equal('10092'); + expect(result[2].meta.networkId).to.equal('5989'); + expect(result[3].meta.networkId).to.equal('5989'); + }); }); describe('getUserSyncs', function() { From 23fe08397668f252c774213a3be14617264a4e8d Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Wed, 15 Feb 2023 20:58:26 +0100 Subject: [PATCH 104/375] 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 --- modules/oxxionAnalyticsAdapter.js | 210 ++++++++++++ modules/oxxionAnalyticsAdapter.md | 33 ++ .../modules/oxxionAnalyticsAdapter_spec.js | 324 ++++++++++++++++++ 3 files changed, 567 insertions(+) create mode 100644 modules/oxxionAnalyticsAdapter.js create mode 100644 modules/oxxionAnalyticsAdapter.md create mode 100644 test/spec/modules/oxxionAnalyticsAdapter_spec.js diff --git a/modules/oxxionAnalyticsAdapter.js b/modules/oxxionAnalyticsAdapter.js new file mode 100644 index 00000000000..73160b5bebf --- /dev/null +++ b/modules/oxxionAnalyticsAdapter.js @@ -0,0 +1,210 @@ +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import { ajax } from '../src/ajax.js'; + +const analyticsType = 'endpoint'; +const url = 'URL_TO_SERVER_ENDPOINT'; + +const { + EVENTS: { + AUCTION_END, + BID_WON, + BID_RESPONSE, + BID_REQUESTED, + BID_TIMEOUT, + } +} = CONSTANTS; + +let saveEvents = {} +let allEvents = {} +let auctionEnd = {} +let initOptions = {} +let endpoint = 'https://default' +let requestsAttributes = ['adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'params', 'userId', 'labelAny', 'bids', 'adId']; + +function getAdapterNameForAlias(aliasName) { + return adapterManager.aliasRegistry[aliasName] || aliasName; +} + +function filterAttributes(arg, removead) { + let response = {}; + if (typeof arg == 'object') { + if (typeof arg['bidderCode'] == 'string') { + response['originalBidder'] = getAdapterNameForAlias(arg['bidderCode']); + } else if (typeof arg['bidder'] == 'string') { + response['originalBidder'] = getAdapterNameForAlias(arg['bidder']); + } + if (!removead && typeof arg['ad'] != 'undefined') { + response['ad'] = arg['ad']; + } + if (typeof arg['gdprConsent'] != 'undefined') { + response['gdprConsent'] = {}; + if (typeof arg['gdprConsent']['consentString'] != 'undefined') { response['gdprConsent']['consentString'] = arg['gdprConsent']['consentString']; } + } + if (typeof arg['meta'] == 'object' && typeof arg['meta']['advertiserDomains'] != 'undefined') { + response['meta'] = {'advertiserDomains': arg['meta']['advertiserDomains']}; + } + requestsAttributes.forEach((attr) => { + if (typeof arg[attr] != 'undefined') { response[attr] = arg[attr]; } + }); + if (typeof response['creativeId'] == 'number') { response['creativeId'] = response['creativeId'].toString(); } + } + return response; +} + +function cleanAuctionEnd(args) { + let response = {}; + let filteredObj; + let objects = ['bidderRequests', 'bidsReceived', 'noBids', 'adUnits']; + objects.forEach((attr) => { + if (Array.isArray(args[attr])) { + response[attr] = []; + args[attr].forEach((obj) => { + filteredObj = filterAttributes(obj, true); + if (typeof obj['bids'] == 'object') { + filteredObj['bids'] = []; + obj['bids'].forEach((bid) => { + filteredObj['bids'].push(filterAttributes(bid, true)); + }); + } + response[attr].push(filteredObj); + }); + } + }); + return response; +} + +function cleanCreatives(args) { + return filterAttributes(args, false); +} + +function enhanceMediaType(arg) { + saveEvents['bidRequested'].forEach((bidRequested) => { + if (bidRequested['auctionId'] == arg['auctionId'] && Array.isArray(bidRequested['bids'])) { + bidRequested['bids'].forEach((bid) => { + if (bid['transactionId'] == arg['transactionId'] && bid['bidId'] == arg['requestId']) { arg['mediaTypes'] = bid['mediaTypes']; } + }); + } + }); + return arg; +} + +function addBidResponse(args) { + let eventType = BID_RESPONSE; + let argsCleaned = cleanCreatives(JSON.parse(JSON.stringify(args))); ; + if (allEvents[eventType] == undefined) { allEvents[eventType] = [] } + allEvents[eventType].push(argsCleaned); +} + +function addBidRequested(args) { + let eventType = BID_REQUESTED; + let argsCleaned = filterAttributes(args, true); + if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } + saveEvents[eventType].push(argsCleaned); +} + +function addTimeout(args) { + let eventType = BID_TIMEOUT; + if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } + saveEvents[eventType].push(args); + let argsCleaned = []; + let argsDereferenced = JSON.parse(JSON.stringify(args)); + argsDereferenced.forEach((attr) => { + argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false)); + }); + if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } + auctionEnd[eventType].push(argsCleaned); +} + +function addAuctionEnd(args) { + let eventType = AUCTION_END; + if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } + saveEvents[eventType].push(args); + let argsCleaned = cleanAuctionEnd(JSON.parse(JSON.stringify(args))); + if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } + auctionEnd[eventType].push(argsCleaned); +} + +function handleBidWon(args) { + args = enhanceMediaType(filterAttributes(JSON.parse(JSON.stringify(args)), true)); + let increment = args['cpm']; + if (typeof saveEvents['auctionEnd'] == 'object') { + saveEvents['auctionEnd'].forEach((auction) => { + if (auction['auctionId'] == args['auctionId'] && typeof auction['bidsReceived'] == 'object') { + auction['bidsReceived'].forEach((bid) => { + if (bid['transactionId'] == args['transactionId'] && bid['adId'] != args['adId']) { + if (args['cpm'] < bid['cpm']) { + increment = 0; + } else if (increment > args['cpm'] - bid['cpm']) { + increment = args['cpm'] - bid['cpm']; + } + } + }); + } + }); + } + args['cpmIncrement'] = increment; + if (typeof saveEvents.bidRequested == 'object' && saveEvents.bidRequested.length > 0 && saveEvents.bidRequested[0].gdprConsent) { args.gdpr = saveEvents.bidRequested[0].gdprConsent; } + ajax(endpoint + '.oxxion.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true}); +} + +function handleAuctionEnd() { + ajax(endpoint + '.oxxion.io/analytics/auctions', function (data) { + let list = JSON.parse(data); + if (Array.isArray(list) && typeof allEvents['bidResponse'] != 'undefined') { + let alreadyCalled = []; + allEvents['bidResponse'].forEach((bidResponse) => { + let tmpId = bidResponse['originalBidder'] + '_' + bidResponse['creativeId']; + if (list.includes(tmpId) && !alreadyCalled.includes(tmpId)) { + alreadyCalled.push(tmpId); + ajax(endpoint + '.oxxion.io/analytics/creatives', null, JSON.stringify(bidResponse), {method: 'POST', withCredentials: true}); + } + }); + } + allEvents = {}; + }, JSON.stringify(auctionEnd), {method: 'POST', withCredentials: true}); + auctionEnd = {}; +} + +let oxxionAnalytics = Object.assign(adapter({url, analyticsType}), { + track({ + eventType, + args + }) { + switch (eventType) { + case AUCTION_END: + addAuctionEnd(args); + handleAuctionEnd(); + break; + case BID_WON: + handleBidWon(args); + break; + case BID_RESPONSE: + addBidResponse(args); + break; + case BID_REQUESTED: + addBidRequested(args); + break; + case BID_TIMEOUT: + addTimeout(args); + break; + } + }}); + +// save the base class function +oxxionAnalytics.originEnableAnalytics = oxxionAnalytics.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +oxxionAnalytics.enableAnalytics = function (config) { + oxxionAnalytics.originEnableAnalytics(config); // call the base class function + initOptions = config.options; + if (initOptions.domain) { endpoint = 'https://' + initOptions.domain; } +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: oxxionAnalytics, + code: 'oxxion' +}); + +export default oxxionAnalytics; diff --git a/modules/oxxionAnalyticsAdapter.md b/modules/oxxionAnalyticsAdapter.md new file mode 100644 index 00000000000..506f013eb37 --- /dev/null +++ b/modules/oxxionAnalyticsAdapter.md @@ -0,0 +1,33 @@ +# Overview +Module Name: oxxion Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: tech@oxxion.io + +# Oxxion Analytics Adapter + +Oxxion helps you to understand how your prebid stack performs. + +# Integration + +Add the oxxion analytics adapter module to your prebid configuration : +``` +pbjs.enableAnalytics( + ... + { + provider: 'oxxion', + options : { + domain: 'test.endpoint' + } + } + ... +) +``` + +# Parameters + +| Name | Type | Description | +|:-------------------------------|:---------|:------------------------------------------------------------------------------------------------------------| +| domain | String | This string identifies yourself in Oxxion's systems and is provided to you by your Oxxion representative. | + diff --git a/test/spec/modules/oxxionAnalyticsAdapter_spec.js b/test/spec/modules/oxxionAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..5f449eb882b --- /dev/null +++ b/test/spec/modules/oxxionAnalyticsAdapter_spec.js @@ -0,0 +1,324 @@ +import oxxionAnalytics from 'modules/oxxionAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('Oxxion Analytics', function () { + let timestamp = new Date() - 256; + let auctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + let timeout = 1500; + + let bidTimeout = [ + { + 'bidId': '5fe418f2d70364', + 'bidder': 'appnexusAst', + 'adUnitCode': 'tag_200124_banner', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b' + } + ]; + + const auctionEnd = { + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'timestamp': 1647424261187, + 'auctionEnd': 1647424261714, + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': 'tag_200124_banner', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 123456 + } + }, + { + 'bidder': 'appnexusAst', + 'params': { + 'placementId': 234567 + } + } + ], + 'sizes': [ + [ + 300, + 600 + ] + ], + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40' + } + ], + 'adUnitCodes': [ + 'tag_200124_banner' + ], + 'bidderRequests': [ + { + 'bidderCode': 'appnexus', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'bidderRequestId': '11dc6ff6378de7', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 123456 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '34a63e5d5378a3', + 'bidderRequestId': '11dc6ff6378de7', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1647424261187, + 'timeout': 1000, + 'gdprConsent': { + 'consentString': 'CONSENT', + 'gdprApplies': true, + 'apiVersion': 2, + 'vendorData': 'a lot of borring stuff', + }, + 'start': 1647424261189 + }, + ], + 'noBids': [ + { + 'bidder': 'appnexusAst', + 'params': { + 'placementId': 10471298 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '5fe418f2d70364', + 'bidderRequestId': '4229a45ab8ea87', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'bidsReceived': [ + { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 600, + 'statusMessage': 'Bid available', + 'adId': '7a4ced80f33d33', + 'requestId': '34a63e5d5378a3', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158534630', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261559, + 'requestTimestamp': 1647424261189, + 'bidder': 'appnexus', + 'adUnitCode': 'tag_200124_banner', + 'timeToRespond': 370, + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '300x600', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '7a4ced80f33d33', + 'hb_pb': '20.000000', + 'hb_size': '300x600', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + } + } + ], + 'winningBids': [ + + ], + 'timeout': 1000 + }; + + let bidWon = { + 'bidderCode': 'appnexus', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '65d16ef039a97a', + 'requestId': '2bd3e8ff8a113f', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158533702', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261558, + 'requestTimestamp': 1647424261189, + 'bidder': 'appnexus', + 'adUnitCode': 'tag_200123_banner', + 'timeToRespond': 369, + 'originalBidder': 'appnexus', + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '970x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '65d16ef039a97a', + 'hb_pb': '20.000000', + 'hb_size': '970x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 123456 + } + ] + }; + + after(function () { + oxxionAnalytics.disableAnalytics(); + }); + + describe('main test flow', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + sinon.spy(oxxionAnalytics, 'track'); + }); + afterEach(function () { + events.getEvents.restore(); + oxxionAnalytics.disableAnalytics(); + oxxionAnalytics.track.restore(); + }); + + it('test auctionEnd', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'oxxion', + adapter: oxxionAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'oxxion', + options: { + domain: 'test' + } + }); + + events.emit(constants.EVENTS.BID_REQUESTED, auctionEnd['bidderRequests'][0]); + events.emit(constants.EVENTS.BID_RESPONSE, auctionEnd['bidsReceived'][0]); + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.have.property('auctionEnd').exist; + expect(message.auctionEnd).to.have.lengthOf(1); + expect(message.auctionEnd[0]).to.have.property('bidsReceived').and.to.have.lengthOf(1); + expect(message.auctionEnd[0].bidsReceived[0]).not.to.have.property('ad'); + expect(message.auctionEnd[0].bidsReceived[0]).to.have.property('meta'); + expect(message.auctionEnd[0].bidsReceived[0].meta).to.have.property('advertiserDomains'); + expect(message.auctionEnd[0].bidsReceived[0]).to.have.property('adId'); + expect(message.auctionEnd[0]).to.have.property('bidderRequests').and.to.have.lengthOf(1); + expect(message.auctionEnd[0].bidderRequests[0]).to.have.property('gdprConsent'); + expect(message.auctionEnd[0].bidderRequests[0].gdprConsent).not.to.have.property('vendorData'); + sinon.assert.callCount(oxxionAnalytics.track, 4); + }); + + it('test bidWon', function() { + adapterManager.registerAnalyticsAdapter({ + code: 'oxxion', + adapter: oxxionAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'oxxion', + options: { + domain: 'test' + } + }); + events.emit(constants.EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).not.to.have.property('ad'); + expect(message).to.have.property('adId') + expect(message).to.have.property('cpmIncrement').and.to.equal(27.4276); + // sinon.assert.callCount(oxxionAnalytics.track, 1); + }); + }); +}); From e986edda7bb4649670fa5519d6a62e70824425a8 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Wed, 15 Feb 2023 13:47:43 -0700 Subject: [PATCH 105/375] Revert "Oxxion Analytics Adapter : initial adapter release (#9449)" (#9549) This reverts commit 23fe08397668f252c774213a3be14617264a4e8d. --- modules/oxxionAnalyticsAdapter.js | 210 ------------ modules/oxxionAnalyticsAdapter.md | 33 -- .../modules/oxxionAnalyticsAdapter_spec.js | 324 ------------------ 3 files changed, 567 deletions(-) delete mode 100644 modules/oxxionAnalyticsAdapter.js delete mode 100644 modules/oxxionAnalyticsAdapter.md delete mode 100644 test/spec/modules/oxxionAnalyticsAdapter_spec.js diff --git a/modules/oxxionAnalyticsAdapter.js b/modules/oxxionAnalyticsAdapter.js deleted file mode 100644 index 73160b5bebf..00000000000 --- a/modules/oxxionAnalyticsAdapter.js +++ /dev/null @@ -1,210 +0,0 @@ -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; -import { ajax } from '../src/ajax.js'; - -const analyticsType = 'endpoint'; -const url = 'URL_TO_SERVER_ENDPOINT'; - -const { - EVENTS: { - AUCTION_END, - BID_WON, - BID_RESPONSE, - BID_REQUESTED, - BID_TIMEOUT, - } -} = CONSTANTS; - -let saveEvents = {} -let allEvents = {} -let auctionEnd = {} -let initOptions = {} -let endpoint = 'https://default' -let requestsAttributes = ['adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'params', 'userId', 'labelAny', 'bids', 'adId']; - -function getAdapterNameForAlias(aliasName) { - return adapterManager.aliasRegistry[aliasName] || aliasName; -} - -function filterAttributes(arg, removead) { - let response = {}; - if (typeof arg == 'object') { - if (typeof arg['bidderCode'] == 'string') { - response['originalBidder'] = getAdapterNameForAlias(arg['bidderCode']); - } else if (typeof arg['bidder'] == 'string') { - response['originalBidder'] = getAdapterNameForAlias(arg['bidder']); - } - if (!removead && typeof arg['ad'] != 'undefined') { - response['ad'] = arg['ad']; - } - if (typeof arg['gdprConsent'] != 'undefined') { - response['gdprConsent'] = {}; - if (typeof arg['gdprConsent']['consentString'] != 'undefined') { response['gdprConsent']['consentString'] = arg['gdprConsent']['consentString']; } - } - if (typeof arg['meta'] == 'object' && typeof arg['meta']['advertiserDomains'] != 'undefined') { - response['meta'] = {'advertiserDomains': arg['meta']['advertiserDomains']}; - } - requestsAttributes.forEach((attr) => { - if (typeof arg[attr] != 'undefined') { response[attr] = arg[attr]; } - }); - if (typeof response['creativeId'] == 'number') { response['creativeId'] = response['creativeId'].toString(); } - } - return response; -} - -function cleanAuctionEnd(args) { - let response = {}; - let filteredObj; - let objects = ['bidderRequests', 'bidsReceived', 'noBids', 'adUnits']; - objects.forEach((attr) => { - if (Array.isArray(args[attr])) { - response[attr] = []; - args[attr].forEach((obj) => { - filteredObj = filterAttributes(obj, true); - if (typeof obj['bids'] == 'object') { - filteredObj['bids'] = []; - obj['bids'].forEach((bid) => { - filteredObj['bids'].push(filterAttributes(bid, true)); - }); - } - response[attr].push(filteredObj); - }); - } - }); - return response; -} - -function cleanCreatives(args) { - return filterAttributes(args, false); -} - -function enhanceMediaType(arg) { - saveEvents['bidRequested'].forEach((bidRequested) => { - if (bidRequested['auctionId'] == arg['auctionId'] && Array.isArray(bidRequested['bids'])) { - bidRequested['bids'].forEach((bid) => { - if (bid['transactionId'] == arg['transactionId'] && bid['bidId'] == arg['requestId']) { arg['mediaTypes'] = bid['mediaTypes']; } - }); - } - }); - return arg; -} - -function addBidResponse(args) { - let eventType = BID_RESPONSE; - let argsCleaned = cleanCreatives(JSON.parse(JSON.stringify(args))); ; - if (allEvents[eventType] == undefined) { allEvents[eventType] = [] } - allEvents[eventType].push(argsCleaned); -} - -function addBidRequested(args) { - let eventType = BID_REQUESTED; - let argsCleaned = filterAttributes(args, true); - if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } - saveEvents[eventType].push(argsCleaned); -} - -function addTimeout(args) { - let eventType = BID_TIMEOUT; - if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } - saveEvents[eventType].push(args); - let argsCleaned = []; - let argsDereferenced = JSON.parse(JSON.stringify(args)); - argsDereferenced.forEach((attr) => { - argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false)); - }); - if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } - auctionEnd[eventType].push(argsCleaned); -} - -function addAuctionEnd(args) { - let eventType = AUCTION_END; - if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } - saveEvents[eventType].push(args); - let argsCleaned = cleanAuctionEnd(JSON.parse(JSON.stringify(args))); - if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } - auctionEnd[eventType].push(argsCleaned); -} - -function handleBidWon(args) { - args = enhanceMediaType(filterAttributes(JSON.parse(JSON.stringify(args)), true)); - let increment = args['cpm']; - if (typeof saveEvents['auctionEnd'] == 'object') { - saveEvents['auctionEnd'].forEach((auction) => { - if (auction['auctionId'] == args['auctionId'] && typeof auction['bidsReceived'] == 'object') { - auction['bidsReceived'].forEach((bid) => { - if (bid['transactionId'] == args['transactionId'] && bid['adId'] != args['adId']) { - if (args['cpm'] < bid['cpm']) { - increment = 0; - } else if (increment > args['cpm'] - bid['cpm']) { - increment = args['cpm'] - bid['cpm']; - } - } - }); - } - }); - } - args['cpmIncrement'] = increment; - if (typeof saveEvents.bidRequested == 'object' && saveEvents.bidRequested.length > 0 && saveEvents.bidRequested[0].gdprConsent) { args.gdpr = saveEvents.bidRequested[0].gdprConsent; } - ajax(endpoint + '.oxxion.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true}); -} - -function handleAuctionEnd() { - ajax(endpoint + '.oxxion.io/analytics/auctions', function (data) { - let list = JSON.parse(data); - if (Array.isArray(list) && typeof allEvents['bidResponse'] != 'undefined') { - let alreadyCalled = []; - allEvents['bidResponse'].forEach((bidResponse) => { - let tmpId = bidResponse['originalBidder'] + '_' + bidResponse['creativeId']; - if (list.includes(tmpId) && !alreadyCalled.includes(tmpId)) { - alreadyCalled.push(tmpId); - ajax(endpoint + '.oxxion.io/analytics/creatives', null, JSON.stringify(bidResponse), {method: 'POST', withCredentials: true}); - } - }); - } - allEvents = {}; - }, JSON.stringify(auctionEnd), {method: 'POST', withCredentials: true}); - auctionEnd = {}; -} - -let oxxionAnalytics = Object.assign(adapter({url, analyticsType}), { - track({ - eventType, - args - }) { - switch (eventType) { - case AUCTION_END: - addAuctionEnd(args); - handleAuctionEnd(); - break; - case BID_WON: - handleBidWon(args); - break; - case BID_RESPONSE: - addBidResponse(args); - break; - case BID_REQUESTED: - addBidRequested(args); - break; - case BID_TIMEOUT: - addTimeout(args); - break; - } - }}); - -// save the base class function -oxxionAnalytics.originEnableAnalytics = oxxionAnalytics.enableAnalytics; - -// override enableAnalytics so we can get access to the config passed in from the page -oxxionAnalytics.enableAnalytics = function (config) { - oxxionAnalytics.originEnableAnalytics(config); // call the base class function - initOptions = config.options; - if (initOptions.domain) { endpoint = 'https://' + initOptions.domain; } -}; - -adapterManager.registerAnalyticsAdapter({ - adapter: oxxionAnalytics, - code: 'oxxion' -}); - -export default oxxionAnalytics; diff --git a/modules/oxxionAnalyticsAdapter.md b/modules/oxxionAnalyticsAdapter.md deleted file mode 100644 index 506f013eb37..00000000000 --- a/modules/oxxionAnalyticsAdapter.md +++ /dev/null @@ -1,33 +0,0 @@ -# Overview -Module Name: oxxion Analytics Adapter - -Module Type: Analytics Adapter - -Maintainer: tech@oxxion.io - -# Oxxion Analytics Adapter - -Oxxion helps you to understand how your prebid stack performs. - -# Integration - -Add the oxxion analytics adapter module to your prebid configuration : -``` -pbjs.enableAnalytics( - ... - { - provider: 'oxxion', - options : { - domain: 'test.endpoint' - } - } - ... -) -``` - -# Parameters - -| Name | Type | Description | -|:-------------------------------|:---------|:------------------------------------------------------------------------------------------------------------| -| domain | String | This string identifies yourself in Oxxion's systems and is provided to you by your Oxxion representative. | - diff --git a/test/spec/modules/oxxionAnalyticsAdapter_spec.js b/test/spec/modules/oxxionAnalyticsAdapter_spec.js deleted file mode 100644 index 5f449eb882b..00000000000 --- a/test/spec/modules/oxxionAnalyticsAdapter_spec.js +++ /dev/null @@ -1,324 +0,0 @@ -import oxxionAnalytics from 'modules/oxxionAnalyticsAdapter.js'; -import { expect } from 'chai'; -import { server } from 'test/mocks/xhr.js'; -let adapterManager = require('src/adapterManager').default; -let events = require('src/events'); -let constants = require('src/constants.json'); - -describe('Oxxion Analytics', function () { - let timestamp = new Date() - 256; - let auctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; - let timeout = 1500; - - let bidTimeout = [ - { - 'bidId': '5fe418f2d70364', - 'bidder': 'appnexusAst', - 'adUnitCode': 'tag_200124_banner', - 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b' - } - ]; - - const auctionEnd = { - 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', - 'timestamp': 1647424261187, - 'auctionEnd': 1647424261714, - 'auctionStatus': 'completed', - 'adUnits': [ - { - 'code': 'tag_200124_banner', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 600 - ] - ] - } - }, - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 123456 - } - }, - { - 'bidder': 'appnexusAst', - 'params': { - 'placementId': 234567 - } - } - ], - 'sizes': [ - [ - 300, - 600 - ] - ], - 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40' - } - ], - 'adUnitCodes': [ - 'tag_200124_banner' - ], - 'bidderRequests': [ - { - 'bidderCode': 'appnexus', - 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', - 'bidderRequestId': '11dc6ff6378de7', - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': 123456 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'tag_200124_banner', - 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', - 'sizes': [ - [ - 300, - 600 - ] - ], - 'bidId': '34a63e5d5378a3', - 'bidderRequestId': '11dc6ff6378de7', - 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ], - 'auctionStart': 1647424261187, - 'timeout': 1000, - 'gdprConsent': { - 'consentString': 'CONSENT', - 'gdprApplies': true, - 'apiVersion': 2, - 'vendorData': 'a lot of borring stuff', - }, - 'start': 1647424261189 - }, - ], - 'noBids': [ - { - 'bidder': 'appnexusAst', - 'params': { - 'placementId': 10471298 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'tag_200124_banner', - 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', - 'sizes': [ - [ - 300, - 600 - ] - ], - 'bidId': '5fe418f2d70364', - 'bidderRequestId': '4229a45ab8ea87', - 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ], - 'bidsReceived': [ - { - 'bidderCode': 'appnexus', - 'width': 300, - 'height': 600, - 'statusMessage': 'Bid available', - 'adId': '7a4ced80f33d33', - 'requestId': '34a63e5d5378a3', - 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', - 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 27.4276, - 'creativeId': '158534630', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 2000, - 'ad': 'some html', - 'meta': { - 'advertiserDomains': [ - 'example.com' - ] - }, - 'originalCpm': 25.02521, - 'originalCurrency': 'EUR', - 'responseTimestamp': 1647424261559, - 'requestTimestamp': 1647424261189, - 'bidder': 'appnexus', - 'adUnitCode': 'tag_200124_banner', - 'timeToRespond': 370, - 'pbLg': '5.00', - 'pbMg': '20.00', - 'pbHg': '20.00', - 'pbAg': '20.00', - 'pbDg': '20.00', - 'pbCg': '20.000000', - 'size': '300x600', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '7a4ced80f33d33', - 'hb_pb': '20.000000', - 'hb_size': '300x600', - 'hb_source': 'client', - 'hb_format': 'banner', - 'hb_adomain': 'example.com' - } - } - ], - 'winningBids': [ - - ], - 'timeout': 1000 - }; - - let bidWon = { - 'bidderCode': 'appnexus', - 'width': 970, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '65d16ef039a97a', - 'requestId': '2bd3e8ff8a113f', - 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', - 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 27.4276, - 'creativeId': '158533702', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 2000, - 'ad': 'some html', - 'meta': { - 'advertiserDomains': [ - 'example.com' - ] - }, - 'originalCpm': 25.02521, - 'originalCurrency': 'EUR', - 'responseTimestamp': 1647424261558, - 'requestTimestamp': 1647424261189, - 'bidder': 'appnexus', - 'adUnitCode': 'tag_200123_banner', - 'timeToRespond': 369, - 'originalBidder': 'appnexus', - 'pbLg': '5.00', - 'pbMg': '20.00', - 'pbHg': '20.00', - 'pbAg': '20.00', - 'pbDg': '20.00', - 'pbCg': '20.000000', - 'size': '970x250', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '65d16ef039a97a', - 'hb_pb': '20.000000', - 'hb_size': '970x250', - 'hb_source': 'client', - 'hb_format': 'banner', - 'hb_adomain': 'example.com' - }, - 'status': 'rendered', - 'params': [ - { - 'placementId': 123456 - } - ] - }; - - after(function () { - oxxionAnalytics.disableAnalytics(); - }); - - describe('main test flow', function () { - beforeEach(function () { - sinon.stub(events, 'getEvents').returns([]); - sinon.spy(oxxionAnalytics, 'track'); - }); - afterEach(function () { - events.getEvents.restore(); - oxxionAnalytics.disableAnalytics(); - oxxionAnalytics.track.restore(); - }); - - it('test auctionEnd', function () { - adapterManager.registerAnalyticsAdapter({ - code: 'oxxion', - adapter: oxxionAnalytics - }); - - adapterManager.enableAnalytics({ - provider: 'oxxion', - options: { - domain: 'test' - } - }); - - events.emit(constants.EVENTS.BID_REQUESTED, auctionEnd['bidderRequests'][0]); - events.emit(constants.EVENTS.BID_RESPONSE, auctionEnd['bidsReceived'][0]); - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); - events.emit(constants.EVENTS.AUCTION_END, auctionEnd); - expect(server.requests.length).to.equal(1); - let message = JSON.parse(server.requests[0].requestBody); - expect(message).to.have.property('auctionEnd').exist; - expect(message.auctionEnd).to.have.lengthOf(1); - expect(message.auctionEnd[0]).to.have.property('bidsReceived').and.to.have.lengthOf(1); - expect(message.auctionEnd[0].bidsReceived[0]).not.to.have.property('ad'); - expect(message.auctionEnd[0].bidsReceived[0]).to.have.property('meta'); - expect(message.auctionEnd[0].bidsReceived[0].meta).to.have.property('advertiserDomains'); - expect(message.auctionEnd[0].bidsReceived[0]).to.have.property('adId'); - expect(message.auctionEnd[0]).to.have.property('bidderRequests').and.to.have.lengthOf(1); - expect(message.auctionEnd[0].bidderRequests[0]).to.have.property('gdprConsent'); - expect(message.auctionEnd[0].bidderRequests[0].gdprConsent).not.to.have.property('vendorData'); - sinon.assert.callCount(oxxionAnalytics.track, 4); - }); - - it('test bidWon', function() { - adapterManager.registerAnalyticsAdapter({ - code: 'oxxion', - adapter: oxxionAnalytics - }); - - adapterManager.enableAnalytics({ - provider: 'oxxion', - options: { - domain: 'test' - } - }); - events.emit(constants.EVENTS.BID_WON, bidWon); - expect(server.requests.length).to.equal(1); - let message = JSON.parse(server.requests[0].requestBody); - expect(message).not.to.have.property('ad'); - expect(message).to.have.property('adId') - expect(message).to.have.property('cpmIncrement').and.to.equal(27.4276); - // sinon.assert.callCount(oxxionAnalytics.track, 1); - }); - }); -}); From 06dd3e4fd933cebdc35120f286739b6f454325d1 Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Wed, 15 Feb 2023 12:57:57 -0800 Subject: [PATCH 106/375] Adding tmax to bid request. (#9548) Defaulting to 400 if none is supplied. --- modules/yieldmoBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 5a0f302ab34..b4b916a1048 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -382,6 +382,7 @@ function openRtbRequest(bidRequests, bidderRequest) { const schain = bidRequests[0].schain; let openRtbRequest = { id: bidRequests[0].bidderRequestId, + tmax: bidderRequest.timeout || 400, at: 1, imp: bidRequests.map(bidRequest => openRtbImpression(bidRequest)), site: openRtbSite(bidRequests[0], bidderRequest), From f066fea32b854b8963b29f028d8226e4a6c4d519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Thu, 16 Feb 2023 16:03:50 +0100 Subject: [PATCH 107/375] Criteo Bid Adapter: Bumping PublisherTag version & Adapter version (#9554) --- modules/criteoBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 48fea78eeb4..f7ba82d3e06 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -11,7 +11,7 @@ import { getRefererInfo } from '../src/refererDetection.js'; import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const GVLID = 91; -export const ADAPTER_VERSION = 34; +export const ADAPTER_VERSION = 35; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; @@ -27,7 +27,7 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 134; +export const FAST_BID_VERSION_CURRENT = 135; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; From 9503f2a1d8b373f3ae694ac42e24cbee59b2aae6 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 17 Feb 2023 13:07:08 +0000 Subject: [PATCH 108/375] Prebid 7.37.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c6a10e6ac6..674b6ca4576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.37.0-pre", + "version": "7.37.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 95ba8e522d2..b47104c10cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.37.0-pre", + "version": "7.37.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a8a77cf3a64b4f56c027b460c5c5d28c6b5dab24 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 17 Feb 2023 13:07:09 +0000 Subject: [PATCH 109/375] Increment version to 7.38.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 674b6ca4576..052b6139912 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.37.0", + "version": "7.38.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index b47104c10cd..75faa503821 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.37.0", + "version": "7.38.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 2aea3ff6e13eb23d800045f179988c8079b0ca3e Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Thu, 16 Feb 2023 14:54:55 -0500 Subject: [PATCH 110/375] tag out adpod-related logic --- src/adapters/bidderFactory.js | 85 ++++++++++++----------- test/spec/unit/core/bidderFactory_spec.js | 4 ++ 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index e3cc607a3e2..1b38e1e652b 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -472,51 +472,56 @@ export const addComponentAuction = hook('sync', (_bidRequest, fledgeAuctionConfi }, 'addComponentAuction') export function preloadBidderMappingFile(fn, adUnits) { - if (!config.getConfig('adpod.brandCategoryExclusion')) { - return fn.call(this, adUnits); - } - let adPodBidders = adUnits - .filter((adUnit) => deepAccess(adUnit, 'mediaTypes.video.context') === ADPOD) - .map((adUnit) => adUnit.bids.map((bid) => bid.bidder)) - .reduce(flatten, []) - .filter(uniques); - - adPodBidders.forEach(bidder => { - let bidderSpec = adapterManager.getBidAdapter(bidder); - if (bidderSpec.getSpec().getMappingFileInfo) { - let info = bidderSpec.getSpec().getMappingFileInfo(); - let refreshInDays = (info.refreshInDays) ? info.refreshInDays : DEFAULT_REFRESHIN_DAYS; - let key = (info.localStorageKey) ? info.localStorageKey : bidderSpec.getSpec().code; - let mappingData = storage.getDataFromLocalStorage(key); - try { - mappingData = mappingData ? JSON.parse(mappingData) : undefined; - if (!mappingData || timestamp() > mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { - ajax(info.url, - { - success: (response) => { - try { - response = JSON.parse(response); - let mapping = { - lastUpdated: timestamp(), - mapping: response.mapping + if (FEATURES.VIDEO) { + if (!config.getConfig('adpod.brandCategoryExclusion')) { + return fn.call(this, adUnits); + } + + let adPodBidders = adUnits + .filter((adUnit) => deepAccess(adUnit, 'mediaTypes.video.context') === ADPOD) + .map((adUnit) => adUnit.bids.map((bid) => bid.bidder)) + .reduce(flatten, []) + .filter(uniques); + + adPodBidders.forEach(bidder => { + let bidderSpec = adapterManager.getBidAdapter(bidder); + if (bidderSpec.getSpec().getMappingFileInfo) { + let info = bidderSpec.getSpec().getMappingFileInfo(); + let refreshInDays = (info.refreshInDays) ? info.refreshInDays : DEFAULT_REFRESHIN_DAYS; + let key = (info.localStorageKey) ? info.localStorageKey : bidderSpec.getSpec().code; + let mappingData = storage.getDataFromLocalStorage(key); + try { + mappingData = mappingData ? JSON.parse(mappingData) : undefined; + if (!mappingData || timestamp() > mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { + ajax(info.url, + { + success: (response) => { + try { + response = JSON.parse(response); + let mapping = { + lastUpdated: timestamp(), + mapping: response.mapping + } + storage.setDataInLocalStorage(key, JSON.stringify(mapping)); + } catch (error) { + logError(`Failed to parse ${bidder} bidder translation mapping file`); } - storage.setDataInLocalStorage(key, JSON.stringify(mapping)); - } catch (error) { - logError(`Failed to parse ${bidder} bidder translation mapping file`); + }, + error: () => { + logError(`Failed to load ${bidder} bidder translation file`) } }, - error: () => { - logError(`Failed to load ${bidder} bidder translation file`) - } - }, - ); + ); + } + } catch (error) { + logError(`Failed to parse ${bidder} bidder translation mapping file`); } - } catch (error) { - logError(`Failed to parse ${bidder} bidder translation mapping file`); } - } - }); - fn.call(this, adUnits); + }); + fn.call(this, adUnits); + } else { + return fn.call(this, adUnits) + } } getHook('checkAdUnitSetup').before(preloadBidderMappingFile); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 1eecfac47a7..35c7cb0b971 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1266,6 +1266,10 @@ describe('validate bid response: ', function () { }); describe('preload mapping url hook', function() { + if (!FEATURES.VIDEO) { + return + } + let fakeTranslationServer; let getLocalStorageStub; let adapterManagerStub; From 7cc810caa52c9519fe4c55a105bb17dff688ecc0 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Thu, 16 Feb 2023 15:18:29 -0500 Subject: [PATCH 111/375] replace callPrebidCache with getHook result --- modules/adpod.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/adpod.js b/modules/adpod.js index f1ab4bd2ef1..4ab8e4e5ab9 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -28,14 +28,13 @@ import { import { addBidToAuction, AUCTION_IN_PROGRESS, - callPrebidCache, doCallbacksIfTimedout, getPriceByGranularity, getPriceGranularity } from '../src/auction.js'; import {checkAdUnitSetup} from '../src/prebid.js'; import {checkVideoBidSetup} from '../src/video.js'; -import {module, setupBeforeHookFnOnce} from '../src/hook.js'; +import {getHook, module, setupBeforeHookFnOnce} from '../src/hook.js'; import {store} from '../src/videoCache.js'; import {config} from '../src/config.js'; import {ADPOD} from '../src/mediaTypes.js'; @@ -424,7 +423,7 @@ config.getConfig('adpod', config => adpodSetConfig(config.adpod)); * This function initializes the adpod module's hooks. This is called by the corresponding adserver video module. */ function initAdpodHooks() { - setupBeforeHookFnOnce(callPrebidCache, callPrebidCacheHook); + setupBeforeHookFnOnce(getHook('callPrebidCache'), callPrebidCacheHook); setupBeforeHookFnOnce(checkAdUnitSetup, checkAdUnitSetupHook); setupBeforeHookFnOnce(checkVideoBidSetup, checkVideoBidSetupHook); } From 695eeebddb3dfe21cc48a203c84aad92c77609d5 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Thu, 16 Feb 2023 15:31:41 -0500 Subject: [PATCH 112/375] tag out video-related code in renderAd --- src/prebid.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index db6941e5227..da4cd76e39e 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -533,12 +533,14 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { const {height, width, ad, mediaType, adUrl, renderer} = bid; // video module - const adUnitCode = bid.adUnitCode; - const adUnit = $$PREBID_GLOBAL$$.adUnits.filter(adUnit => adUnit.code === adUnitCode); - const videoModule = $$PREBID_GLOBAL$$.videoModule; - if (adUnit.video && videoModule) { - videoModule.renderBid(adUnit.video.divId, bid); - return; + if (FEATURES.VIDEO) { + const adUnitCode = bid.adUnitCode; + const adUnit = $$PREBID_GLOBAL$$.adUnits.filter(adUnit => adUnit.code === adUnitCode); + const videoModule = $$PREBID_GLOBAL$$.videoModule; + if (adUnit.video && videoModule) { + videoModule.renderBid(adUnit.video.divId, bid); + return; + } } if (!doc) { From 78cf5409c82c754c70adce79469d8ed1ccfd0e67 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Thu, 16 Feb 2023 16:11:37 -0500 Subject: [PATCH 113/375] tag out video-related code in native.js --- src/native.js | 2 +- test/spec/native_spec.js | 28 +++++++++++++++------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/native.js b/src/native.js index 25f8c38cb30..ea21c5b74d4 100644 --- a/src/native.js +++ b/src/native.js @@ -174,7 +174,7 @@ function isOpenRTBAssetValid(asset) { logError(`for data asset 'type' property must be a number`); return false; } - } else if (asset.video) { + } else if (FEATURES.VIDEO && asset.video) { if (!Array.isArray(asset.video.mimes) || !Array.isArray(asset.video.protocols) || !isNumber(asset.video.minduration) || !isNumber(asset.video.maxduration)) { logError('video asset is not properly configured'); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 2b7c2b88449..d4586c29329 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -643,19 +643,21 @@ describe('validate native openRTB', function () { // openRTB request is valid expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); - openRTBNativeRequest.assets.push({ - id: 2, - required: 1, - video: { - mimes: [], - protocols: [], - minduration: 50, - }, - }); - // video asset should have all required properties - expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); - openRTBNativeRequest.assets[1].video.maxduration = 60; - expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); + if (FEATURES.VIDEO) { + openRTBNativeRequest.assets.push({ + id: 2, + required: 1, + video: { + mimes: [], + protocols: [], + minduration: 50, + }, + }); + // video asset should have all required properties + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets[1].video.maxduration = 60; + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); + } }); it('should validate openRTB native bid', function () { From f3b2ab03bd39b7ec72504b9a458b12bf7e1011f3 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Fri, 17 Feb 2023 09:33:42 -0500 Subject: [PATCH 114/375] tag out ORTB video conversion utils --- libraries/ortbConverter/processors/default.js | 19 +- .../modules/improvedigitalBidAdapter_spec.js | 248 ++++++++-------- test/spec/modules/openxOrtbBidAdapter_spec.js | 102 ++++--- .../modules/prebidServerBidAdapter_spec.js | 281 +++++++++--------- 4 files changed, 339 insertions(+), 311 deletions(-) diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index 1d6bfb8424e..f800cb458ee 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -57,10 +57,6 @@ export const DEFAULT_PROCESSORS = { // populates imp.banner fn: fillBannerImp }, - video: { - // populates imp.video - fn: fillVideoImp - }, pbadslot: { // removes imp.ext.data.pbaslot if it's not a string // TODO: is this needed? @@ -82,10 +78,6 @@ export const DEFAULT_PROCESSORS = { // sets banner response attributes if bidResponse.mediaType === BANNER fn: bannerResponseProcessor(), }, - video: { - // sets video response attributes if bidResponse.mediaType === VIDEO - fn: fillVideoResponse - }, props: { // sets base bidResponse properties common to all types of bids fn(bidResponse, bid, context) { @@ -126,6 +118,17 @@ if (FEATURES.NATIVE) { } } +if (FEATURES.VIDEO) { + DEFAULT_PROCESSORS[IMP].video = { + // populates imp.video + fn: fillVideoImp + } + DEFAULT_PROCESSORS[BID_RESPONSE].video = { + // sets video response attributes if bidResponse.mediaType === VIDEO + fn: fillVideoResponse + } +} + function fpdFromTopLevelConfig(prop) { return { priority: 90, // after FPD from 'ortb2', before the rest diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 400b9145e0b..58b39c7204d 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -278,12 +278,14 @@ describe('Improve Digital Adapter Tests', function () { placementId: 1053688, } }, - video: { - placement: OUTSTREAM_TYPE, - w: 640, - h: 480, - mimes: ['video/mp4'], - }, + ...(FEATURES.VIDEO && { + video: { + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + } + }), banner: { format: [ {w: 300, h: 250}, @@ -468,102 +470,104 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].video).to.not.exist; }); - it('should add correct placement value for instream and outstream video', function () { - let bidRequest = deepClone(simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video).to.not.exist; + if (FEATURES.VIDEO) { + it('should add correct placement value for instream and outstream video', function () { + let bidRequest = deepClone(simpleBidRequest); + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video).to.not.exist; - bidRequest = deepClone(simpleBidRequest); - bidRequest.mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480] - } - }; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(1); - bidRequest.mediaTypes.video.context = 'outstream'; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(3); - }); + bidRequest = deepClone(simpleBidRequest); + bidRequest.mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480] + } + }; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(1); + bidRequest.mediaTypes.video.context = 'outstream'; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(3); + }); - it('should set video params for instream', function() { - const bidRequest = deepClone(instreamBidRequest); - delete bidRequest.mediaTypes.video.playerSize; - const videoParams = { - mimes: ['video/mp4'], - skip: 1, - skipmin: 5, - skipafter: 30, - minduration: 15, - maxduration: 60, - startdelay: 5, - minbitrate: 500, - maxbitrate: 2000, - w: 1024, - h: 640, - placement: INSTREAM_TYPE, - }; - bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const payload = JSON.parse(request.data); - expect(payload.imp[0].video).to.deep.equal(videoParams); - }); + it('should set video params for instream', function() { + const bidRequest = deepClone(instreamBidRequest); + delete bidRequest.mediaTypes.video.playerSize; + const videoParams = { + mimes: ['video/mp4'], + skip: 1, + skipmin: 5, + skipafter: 30, + minduration: 15, + maxduration: 60, + startdelay: 5, + minbitrate: 500, + maxbitrate: 2000, + w: 1024, + h: 640, + placement: INSTREAM_TYPE, + }; + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal(videoParams); + }); - it('should set video playerSize over video params', () => { - const bidRequest = deepClone(instreamBidRequest); - bidRequest.params.video = { - w: 1024, h: 640 - } - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const payload = JSON.parse(request.data); - expect(payload.imp[0].video.h).equal(480); - expect(payload.imp[0].video.w).equal(640); - }); + it('should set video playerSize over video params', () => { + const bidRequest = deepClone(instreamBidRequest); + bidRequest.params.video = { + w: 1024, h: 640 + } + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video.h).equal(480); + expect(payload.imp[0].video.w).equal(640); + }); - it('should ignore invalid/unexpected video params', function() { - const bidRequest = deepClone(instreamBidRequest); - // 1 - const videoTest = { - skip: 1, - skipmin: 5, - skipafter: 30 - } - const videoTestInvParam = Object.assign({}, videoTest); - videoTestInvParam.blah = 1; - bidRequest.params.video = videoTestInvParam; - let request = spec.buildRequests([bidRequest], {})[0]; - let payload = JSON.parse(request.data); - expect(payload.imp[0].video.blah).not.to.exist; - }); + it('should ignore invalid/unexpected video params', function() { + const bidRequest = deepClone(instreamBidRequest); + // 1 + const videoTest = { + skip: 1, + skipmin: 5, + skipafter: 30 + } + const videoTestInvParam = Object.assign({}, videoTest); + videoTestInvParam.blah = 1; + bidRequest.params.video = videoTestInvParam; + let request = spec.buildRequests([bidRequest], {})[0]; + let payload = JSON.parse(request.data); + expect(payload.imp[0].video.blah).not.to.exist; + }); - it('should set video params for outstream', function() { - const bidRequest = deepClone(outstreamBidRequest); - bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest], {})[0]; - const payload = JSON.parse(request.data); - expect(payload.imp[0].video).to.deep.equal({...{ - mimes: ['video/mp4'], - placement: OUTSTREAM_TYPE, - w: bidRequest.mediaTypes.video.playerSize[0], - h: bidRequest.mediaTypes.video.playerSize[1], - }, - ...videoParams}); - }); - // - it('should set video params for multi-format', function() { - const bidRequest = deepClone(multiFormatBidRequest); - bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest], {})[0]; - const payload = JSON.parse(request.data); - const testVideoParams = Object.assign({ - placement: OUTSTREAM_TYPE, - w: 640, - h: 480, - mimes: ['video/mp4'], - }, videoParams); - expect(payload.imp[0].video).to.deep.equal(testVideoParams); - }); + it('should set video params for outstream', function() { + const bidRequest = deepClone(outstreamBidRequest); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], {})[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal({...{ + mimes: ['video/mp4'], + placement: OUTSTREAM_TYPE, + w: bidRequest.mediaTypes.video.playerSize[0], + h: bidRequest.mediaTypes.video.playerSize[1], + }, + ...videoParams}); + }); + // + it('should set video params for multi-format', function() { + const bidRequest = deepClone(multiFormatBidRequest); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], {})[0]; + const payload = JSON.parse(request.data); + const testVideoParams = Object.assign({ + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + }, videoParams); + expect(payload.imp[0].video).to.deep.equal(testVideoParams); + }); + } it('should add schain', function () { const schain = '{"ver":"1.0","complete":1,"nodes":[{"asi":"headerlift.com","sid":"xyz","hp":1}]}'; @@ -609,7 +613,7 @@ describe('Improve Digital Adapter Tests', function () { const request = JSON.parse(requests[0].data); expect(request.imp.length).to.equal(2); expect(request.imp[0].banner).to.exist; - expect(request.imp[1].video).to.exist; + if (FEATURES.VIDEO) { expect(request.imp[1].video).to.exist; } }); it('should create one request per endpoint in a single request mode', function () { @@ -623,7 +627,7 @@ describe('Improve Digital Adapter Tests', function () { const adServerRequest = JSON.parse(requests[1].data); expect(adServerRequest.imp.length).to.equal(2); expect(adServerRequest.imp[0].banner).to.exist; - expect(adServerRequest.imp[1].video).to.exist; + if (FEATURES.VIDEO) { expect(adServerRequest.imp[1].video).to.exist; } }); it('should set Prebid sizes in bid request', function () { @@ -1238,32 +1242,34 @@ describe('Improve Digital Adapter Tests', function () { } // Video - it('should return a well-formed instream video bid', function () { - const bids = spec.interpretResponse(serverResponseVideo, makeRequest(instreamBidderRequest)); - expectMatch(bids, expectedBidInstreamVideo); - }); + if (FEATURES.VIDEO) { + it('should return a well-formed instream video bid', function () { + const bids = spec.interpretResponse(serverResponseVideo, makeRequest(instreamBidderRequest)); + expectMatch(bids, expectedBidInstreamVideo); + }); - it('should return a well-formed outstream video bid', function () { - const bids = spec.interpretResponse(serverResponseVideo, makeRequest(outstreamBidderRequest)); - expect(bids[0].renderer).to.exist; - expectMatch(bids, expectedBidOutstreamVideo); - }); + it('should return a well-formed outstream video bid', function () { + const bids = spec.interpretResponse(serverResponseVideo, makeRequest(outstreamBidderRequest)); + expect(bids[0].renderer).to.exist; + expectMatch(bids, expectedBidOutstreamVideo); + }); - it('should return a well-formed outstream video bid for multi-format ad unit', function () { - const request = makeRequest(multiFormatBidderRequest); - const videoResponse = deepClone(serverResponseVideo); - let bids = spec.interpretResponse(videoResponse, request); - expect(bids[0].renderer).to.exist; - expectMatch(bids, expectedBidOutstreamVideo); + it('should return a well-formed outstream video bid for multi-format ad unit', function () { + const request = makeRequest(multiFormatBidderRequest); + const videoResponse = deepClone(serverResponseVideo); + let bids = spec.interpretResponse(videoResponse, request); + expect(bids[0].renderer).to.exist; + expectMatch(bids, expectedBidOutstreamVideo); - videoResponse.body.seatbid[0].bid[0].adm = ' { - item.bid[0].ext.prebid.targeting = targetingTestData - }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.targeting = targetingTestData + }); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('adserverTargeting'); - expect(response.adserverTargeting).to.deep.equal({ - 'hb_cache_path': '/cache', - 'hb_cache_host': 'prebid-cache.testurl.com' + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('adserverTargeting'); + expect(response.adserverTargeting).to.deep.equal({ + 'hb_cache_path': '/cache', + 'hb_cache_host': 'prebid-cache.testurl.com' + }); }); - }); + } it('should set the bidResponse currency to whats in the PBS response', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -2890,60 +2894,62 @@ describe('S2S Adapter', function () { expect(response).to.have.property('ttl', 30); }); - it('handles OpenRTB video responses', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' - } - }); - config.setConfig({ s2sConfig }); - - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + it('handles OpenRTB video responses', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } + }); + config.setConfig({ s2sConfig }); - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('vastXml', RESPONSE_OPENRTB_VIDEO.seatbid[0].bid[0].adm); - expect(response).to.have.property('mediaType', 'video'); - expect(response).to.have.property('bidderCode', 'appnexus'); - expect(response).to.have.property('requestId', '123'); - expect(response).to.have.property('cpm', 10); - }); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); - it('handles response cache from ext.prebid.cache.vastXml', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' - } + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('vastXml', RESPONSE_OPENRTB_VIDEO.seatbid[0].bid[0].adm); + expect(response).to.have.property('mediaType', 'video'); + expect(response).to.have.property('bidderCode', 'appnexus'); + expect(response).to.have.property('requestId', '123'); + expect(response).to.have.property('cpm', 10); }); - config.setConfig({ s2sConfig }); - const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - cacheResponse.seatbid.forEach(item => { - item.bid[0].ext.prebid.cache = { - vastXml: { - cacheId: 'abcd1234', - url: 'https://prebid-cache.net/cache?uuid=abcd1234' + + it('handles response cache from ext.prebid.cache.vastXml', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' } - } - }); + }); + config.setConfig({ s2sConfig }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.cache = { + vastXml: { + cacheId: 'abcd1234', + url: 'https://prebid-cache.net/cache?uuid=abcd1234' + } + } + }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('videoCacheKey', 'abcd1234'); - expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=abcd1234'); - }); + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('videoCacheKey', 'abcd1234'); + expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=abcd1234'); + }); + } it('add adserverTargeting object to bids when ext.prebid.targeting is defined', function () { const s2sConfig = Object.assign({}, CONFIG, { @@ -2962,20 +2968,22 @@ describe('S2S Adapter', function () { item.bid[0].ext.prebid.targeting = targetingTestData }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('adserverTargeting'); - expect(response.adserverTargeting).to.deep.equal({ - 'hb_cache_path': '/cache', - 'hb_cache_host': 'prebid-cache.testurl.com' - }); + expect(response).to.have.property('adserverTargeting'); + expect(response.adserverTargeting).to.deep.equal({ + 'hb_cache_path': '/cache', + 'hb_cache_host': 'prebid-cache.testurl.com' + }); + } }); it('handles response cache from ext.prebid.targeting', function () { @@ -2994,18 +3002,20 @@ describe('S2S Adapter', function () { } }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('videoCacheKey', 'a5ad3993'); - expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=a5ad3993'); + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('videoCacheKey', 'a5ad3993'); + expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=a5ad3993'); + } }); it('handles response cache from ext.prebid.targeting with wurl', function () { @@ -3026,15 +3036,18 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('pbsBidId', '654321'); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('pbsBidId', '654321'); + } }); it('add request property pbsBidId with ext.prebid.bidid value', function () { @@ -3046,16 +3059,18 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('pbsBidId', '654321'); + expect(response).to.have.property('pbsBidId', '654321'); + } }); if (FEATURES.NATIVE) { From 1afb17fe59e35a929784ac5c0e44213b8d3ba043 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Fri, 17 Feb 2023 10:14:19 -0500 Subject: [PATCH 115/375] remove unnecessary feature tag --- src/auction.js | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/auction.js b/src/auction.js index 3d729c0b860..e1580dadea9 100644 --- a/src/auction.js +++ b/src/auction.js @@ -574,31 +574,29 @@ export function addBidToAuction(auctionInstance, bidResponse) { // Video bids may fail if the cache is down, or there's trouble on the network. function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = auctionManager.index} = {}) { - if (FEATURES.VIDEO) { - let addBid = true; - - const videoMediaType = deepAccess( - index.getMediaTypes({ - requestId: bidResponse.originalRequestId || bidResponse.requestId, - transactionId: bidResponse.transactionId - }), 'video'); - const context = videoMediaType && deepAccess(videoMediaType, 'context'); - const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); - - if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) { - if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { - addBid = false; - callPrebidCache(auctionInstance, bidResponse, afterBidAdded, videoMediaType); - } else if (!bidResponse.vastUrl) { - logError('videoCacheKey specified but not required vastUrl for video bid'); - addBid = false; - } - } - if (addBid) { - addBidToAuction(auctionInstance, bidResponse); - afterBidAdded(); + let addBid = true; + + const videoMediaType = deepAccess( + index.getMediaTypes({ + requestId: bidResponse.originalRequestId || bidResponse.requestId, + transactionId: bidResponse.transactionId + }), 'video'); + const context = videoMediaType && deepAccess(videoMediaType, 'context'); + const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); + + if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) { + if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { + addBid = false; + callPrebidCache(auctionInstance, bidResponse, afterBidAdded, videoMediaType); + } else if (!bidResponse.vastUrl) { + logError('videoCacheKey specified but not required vastUrl for video bid'); + addBid = false; } } + if (addBid) { + addBidToAuction(auctionInstance, bidResponse); + afterBidAdded(); + } } // Native bid response might be in ortb2 format - adds legacy field for backward compatibility From 073058797ecef3394743d89d9898ff6c00654a0b Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Fri, 17 Feb 2023 10:31:31 -0500 Subject: [PATCH 116/375] tag out markWinningBidAsUsed entirely --- src/prebid.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index da4cd76e39e..44e2d940055 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -991,16 +991,16 @@ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { return targeting.getWinningBids(adUnitCode); }; -/** - * Mark the winning bid as used, should only be used in conjunction with video - * @typedef {Object} MarkBidRequest - * @property {string} adUnitCode The ad unit code - * @property {string} adId The id representing the ad we want to mark - * - * @alias module:pbjs.markWinningBidAsUsed - */ -$$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { - if (FEATURES.VIDEO) { +if (FEATURES.VIDEO) { + /** + * Mark the winning bid as used, should only be used in conjunction with video + * @typedef {Object} MarkBidRequest + * @property {string} adUnitCode The ad unit code + * @property {string} adId The id representing the ad we want to mark + * + * @alias module:pbjs.markWinningBidAsUsed + */ + $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { let bids = []; if (markBidRequest.adUnitCode && markBidRequest.adId) { @@ -1018,7 +1018,7 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { bids[0].status = CONSTANTS.BID_STATUS.RENDERED; } } -}; +} /** * Get Prebid config options From 4994064c8a21c762a5348d7331f8f923facd4315 Mon Sep 17 00:00:00 2001 From: Andrea Cannuni <57228257+ACannuniRP@users.noreply.github.com> Date: Fri, 17 Feb 2023 19:34:04 +0000 Subject: [PATCH 117/375] Add new size 480x480 (ID: 261) in Rubicon Adapter (#9557) Co-authored-by: Andrea Cannuni --- modules/rubiconBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index bd53e9d5104..d4875345043 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -115,6 +115,7 @@ var sizeMap = { 257: '400x600', 258: '500x200', 259: '998x200', + 261: '480x480', 264: '970x1000', 265: '1920x1080', 274: '1800x200', From 4329420d713424782319e70066f6f2fcc8861053 Mon Sep 17 00:00:00 2001 From: Michael Bishop <41241382+bishpls@users.noreply.github.com> Date: Sun, 19 Feb 2023 17:44:14 -0500 Subject: [PATCH 118/375] Fix empty-string sessionId when using unexpired sessions (#9559) --- modules/pubwiseAnalyticsAdapter.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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'); From aab50b1ed78204792111549839f6777ed9c4b8d1 Mon Sep 17 00:00:00 2001 From: xwang202 <57196235+xwang202@users.noreply.github.com> Date: Mon, 20 Feb 2023 13:13:41 +0800 Subject: [PATCH 119/375] 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 --- modules/freewheel-sspBidAdapter.js | 30 +++++++++++- .../modules/freewheel-sspBidAdapter_spec.js | 48 ++++++++++++++----- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index b4d8f69d1b4..1a962fd4060 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -1,6 +1,7 @@ -import { logWarn, isArray } from '../src/utils.js'; +import { logWarn, isArray, isFn, deepAccess } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'freewheel-ssp'; @@ -213,6 +214,27 @@ function getAPIName(componentId) { return componentId.replace('-', ''); } +function getBidFloor(bid, config) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: getFloorCurrency(config), + mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video', + size: '*', + }); + return bidFloor.floor; + } catch (e) { + return -1; + } +} + +function getFloorCurrency(config) { + return config.getConfig('floors.data.currency') != null ? config.getConfig('floors.data.currency') : 'USD'; +} + function formatAdHTML(bid, size) { var integrationType = bid.params.format; @@ -317,13 +339,17 @@ export const spec = { var zone = currentBidRequest.params.zoneId; var timeInMillis = new Date().getTime(); var keyCode = hashcode(zone + '' + timeInMillis); + var bidfloor = getBidFloor(currentBidRequest, config); + var requestParams = { reqType: 'AdsSetup', - protocolVersion: '2.0', + protocolVersion: '4.2', zoneId: zone, componentId: 'prebid', componentSubId: getComponentId(currentBidRequest.params.format), timestamp: timeInMillis, + _fw_bidfloor: (bidfloor > 0) ? bidfloor : 0, + _fw_bidfloorcur: (bidfloor > 0) ? getFloorCurrency(config) : '', pKey: keyCode }; diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index d1e0b055239..4d0c87ace3a 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -84,7 +84,8 @@ describe('freewheelSSP BidAdapter Test', () => { { 'bidder': 'freewheel-ssp', 'params': { - 'zoneId': '277225' + 'zoneId': '277225', + 'bidfloor': 2.00, }, 'adUnitCode': 'adunit-code', 'mediaTypes': { @@ -114,11 +115,36 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; + it('should get bidfloor value from params if no getFloor method', () => { + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload._fw_bidfloor).to.equal(2.00); + expect(payload._fw_bidfloorcur).to.deep.equal('USD'); + }); + + it('should get bidfloor value from getFloor method if available', () => { + const bidRequest = bidRequests[0]; + bidRequest.getFloor = () => ({ currency: 'USD', floor: 1.16 }); + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload._fw_bidfloor).to.equal(1.16); + expect(payload._fw_bidfloorcur).to.deep.equal('USD'); + }); + + it('should return empty bidFloorCurrency when bidfloor <= 0', () => { + const bidRequest = bidRequests[0]; + bidRequest.getFloor = () => ({ currency: 'USD', floor: -1 }); + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload._fw_bidfloor).to.equal(0); + expect(payload._fw_bidfloorcur).to.deep.equal(''); + }); + it('should add parameters to the tag', () => { const request = spec.buildRequests(bidRequests); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -144,7 +170,7 @@ describe('freewheelSSP BidAdapter Test', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -164,7 +190,7 @@ describe('freewheelSSP BidAdapter Test', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -211,7 +237,7 @@ describe('freewheelSSP BidAdapter Test', () => { const request = spec.buildRequests(bidRequests); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -231,7 +257,7 @@ describe('freewheelSSP BidAdapter Test', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -251,7 +277,7 @@ describe('freewheelSSP BidAdapter Test', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -337,7 +363,7 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; - let response = '' + + let response = '' + '' + ' ' + ' Adswizz' + @@ -422,7 +448,7 @@ describe('freewheelSSP BidAdapter Test', () => { it('handles nobid responses', () => { var request = spec.buildRequests(formattedBidRequests); - let response = ''; + let response = ''; let result = spec.interpretResponse(response, request[0]); expect(result.length).to.equal(0); @@ -503,7 +529,7 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; - let response = '' + + let response = '' + '' + ' ' + ' Adswizz' + @@ -599,7 +625,7 @@ describe('freewheelSSP BidAdapter Test', () => { it('handles nobid responses', () => { var request = spec.buildRequests(formattedBidRequests); - let response = ''; + let response = ''; let result = spec.interpretResponse(response, request[0]); expect(result.length).to.equal(0); From 058d68da4ad281eab35ad8cb136a9580345bdf43 Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:24:44 +0530 Subject: [PATCH 120/375] 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 --- modules/pubmaticBidAdapter.js | 20 ++++++++------------ test/spec/modules/pubmaticBidAdapter_spec.js | 15 ++++----------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index d8f5002ebd7..6f18d83d92a 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 = { diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index fa8f809e8a2..b6f719ae147 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2654,13 +2654,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 +2856,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 +2876,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 +2899,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() { From 2bcba39729fd18031680640c8380539ed705875d Mon Sep 17 00:00:00 2001 From: optidigital-prebid <124287395+optidigital-prebid@users.noreply.github.com> Date: Tue, 21 Feb 2023 06:00:12 +0100 Subject: [PATCH 121/375] Optidigital Bid Adapter: initial adapter release (#9501) * add new adapter * update adapter * update unit tests * update adapter --------- Co-authored-by: Dawid W --- modules/optidigitalBidAdapter.js | 221 +++++++ modules/optidigitalBidAdapter.md | 46 ++ .../modules/optidigitalBidAdapter_spec.js | 604 ++++++++++++++++++ 3 files changed, 871 insertions(+) create mode 100755 modules/optidigitalBidAdapter.js create mode 100755 modules/optidigitalBidAdapter.md create mode 100755 test/spec/modules/optidigitalBidAdapter_spec.js 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/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); + }); + }); +}); From 23b4baa5470b045c30f99587d7ba1eae6e08d4b0 Mon Sep 17 00:00:00 2001 From: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Date: Tue, 21 Feb 2023 12:20:39 +0100 Subject: [PATCH 122/375] Sspbc Bid Adapter : add native asset mapping and floor updates (#9389) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- modules/sspBCBidAdapter.js | 421 +++++++++++++--------- test/spec/modules/sspBCBidAdapter_spec.js | 11 +- 2 files changed, 254 insertions(+), 178 deletions(-) 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 = '', + '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' + }; +} From 8fbdaa472d9509a05a376f443458d025d8a2d4d2 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Fri, 24 Feb 2023 15:29:17 -0500 Subject: [PATCH 141/375] revert --- src/native.js | 2 +- test/spec/native_spec.js | 28 +++++++++++++--------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/native.js b/src/native.js index ea21c5b74d4..25f8c38cb30 100644 --- a/src/native.js +++ b/src/native.js @@ -174,7 +174,7 @@ function isOpenRTBAssetValid(asset) { logError(`for data asset 'type' property must be a number`); return false; } - } else if (FEATURES.VIDEO && asset.video) { + } else if (asset.video) { if (!Array.isArray(asset.video.mimes) || !Array.isArray(asset.video.protocols) || !isNumber(asset.video.minduration) || !isNumber(asset.video.maxduration)) { logError('video asset is not properly configured'); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index d4586c29329..2b7c2b88449 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -643,21 +643,19 @@ describe('validate native openRTB', function () { // openRTB request is valid expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); - if (FEATURES.VIDEO) { - openRTBNativeRequest.assets.push({ - id: 2, - required: 1, - video: { - mimes: [], - protocols: [], - minduration: 50, - }, - }); - // video asset should have all required properties - expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); - openRTBNativeRequest.assets[1].video.maxduration = 60; - expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); - } + openRTBNativeRequest.assets.push({ + id: 2, + required: 1, + video: { + mimes: [], + protocols: [], + minduration: 50, + }, + }); + // video asset should have all required properties + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets[1].video.maxduration = 60; + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); }); it('should validate openRTB native bid', function () { From 9d252d4e1659307e34bc244a0b0fe258b62136f5 Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Fri, 24 Feb 2023 23:03:05 +0200 Subject: [PATCH 142/375] Missena: add format params and onBidWon pixel (#9517) --- modules/missenaBidAdapter.js | 23 ++++++++++++++++++++- test/spec/modules/missenaBidAdapter_spec.js | 10 +++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 2ec9d39fc5d..02fd454c780 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,15 @@ 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; + } return { method: 'POST', url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), @@ -109,6 +121,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 }, + }) + ); logInfo('Missena - Bid won', bid); }, }; 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'); From fb422447762bee34036e4cbf28020deb2591b72a Mon Sep 17 00:00:00 2001 From: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:40:05 +0400 Subject: [PATCH 143/375] 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 --- modules/alkimiBidAdapter.js | 59 ++++++++++++---------- test/spec/modules/alkimiBidAdapter_spec.js | 2 +- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index a96b245d786..69b3fdae3d8 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -1,8 +1,8 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { deepClone, deepAccess } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {deepClone, deepAccess} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import {VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'alkimi'; export const ENDPOINT = 'https://exchange.alkimi-onboarding.com/bid?prebid=true'; @@ -20,8 +20,7 @@ export const spec = { let bidIds = []; let eids; validBidRequests.forEach(bidRequest => { - let formatType = getFormatType(bidRequest) - let alkimiSizes = prepareAlkimiSizes(bidRequest.sizes) + let formatTypes = getFormatType(bidRequest) if (bidRequest.userIdAsEids) { eids = eids || bidRequest.userIdAsEids @@ -30,10 +29,10 @@ export const spec = { bids.push({ token: bidRequest.params.token, pos: bidRequest.params.pos, - bidFloor: getBidFloor(bidRequest, formatType), - width: alkimiSizes[0].width, - height: alkimiSizes[0].height, - impMediaType: formatType, + bidFloor: getBidFloor(bidRequest, formatTypes), + sizes: prepareSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')), + playerSizes: prepareSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')), + impMediaTypes: formatTypes, adUnitCode: bidRequest.adUnitCode }) bidIds.push(bidRequest.bidId) @@ -43,7 +42,7 @@ export const spec = { let payload = { requestId: bidderRequest.auctionId, - signRequest: { bids, randomUUID: alkimiConfig && alkimiConfig.randomUUID }, + signRequest: {bids, randomUUID: alkimiConfig && alkimiConfig.randomUUID}, bidIds, referer: bidderRequest.refererInfo.page, signature: alkimiConfig && alkimiConfig.signature, @@ -87,7 +86,7 @@ export const spec = { return []; } - const { prebidResponse } = serverBody; + const {prebidResponse} = serverBody; if (!prebidResponse || typeof prebidResponse !== 'object') { return []; } @@ -115,7 +114,7 @@ export const spec = { let winUrl; if (bid.winUrl || bid.vastUrl) { winUrl = bid.winUrl ? bid.winUrl : bid.vastUrl; - winUrl = winUrl.replace(/\$\{AUCTION_PRICE\}/, bid.cpm); + winUrl = winUrl.replace(/\$\{AUCTION_PRICE}/, bid.cpm); } else if (bid.ad) { let trackImg = bid.ad.match(/(?!^)/); bid.ad = bid.ad.replace(trackImg[0], ''); @@ -130,29 +129,35 @@ export const spec = { } } -function prepareAlkimiSizes(sizes) { - return sizes && sizes.map(size => ({ width: size[0], height: size[1] })); +function prepareSizes(sizes) { + return sizes ? sizes.map(size => ({width: size[0], height: size[1]})) : [] } function prepareBidFloorSize(sizes) { - return sizes && sizes.length === 1 ? sizes[0] : '*'; + return sizes && sizes.length === 1 ? sizes : ['*']; } -function getBidFloor(bidRequest, formatType) { +function getBidFloor(bidRequest, formatTypes) { + let minFloor if (typeof bidRequest.getFloor === 'function') { - const bidFloorSize = prepareBidFloorSize(bidRequest.sizes) - const floor = bidRequest.getFloor({ currency: 'USD', mediaType: formatType.toLowerCase(), size: bidFloorSize }); - if (floor && !isNaN(floor.floor) && (floor.currency === 'USD')) { - return floor.floor; - } + const bidFloorSizes = prepareBidFloorSize(bidRequest.sizes) + formatTypes.forEach(formatType => { + bidFloorSizes.forEach(bidFloorSize => { + const floor = bidRequest.getFloor({currency: 'USD', mediaType: formatType.toLowerCase(), size: bidFloorSize}); + if (floor && !isNaN(floor.floor) && (floor.currency === 'USD')) { + minFloor = !minFloor || floor.floor < minFloor ? floor.floor : minFloor + } + }) + }) } - return bidRequest.params.bidFloor; + return minFloor || bidRequest.params.bidFloor; } const getFormatType = bidRequest => { - if (deepAccess(bidRequest, 'mediaTypes.banner')) return 'Banner' - if (deepAccess(bidRequest, 'mediaTypes.video')) return 'Video' - if (deepAccess(bidRequest, 'mediaTypes.audio')) return 'Audio' + let formats = [] + if (deepAccess(bidRequest, 'mediaTypes.banner')) formats.push('Banner') + if (deepAccess(bidRequest, 'mediaTypes.video')) formats.push('Video') + return formats } registerBidder(spec); diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js index 92e5d28f42a..a396e5b8139 100644 --- a/test/spec/modules/alkimiBidAdapter_spec.js +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -140,7 +140,7 @@ describe('alkimiBidAdapter', function () { expect(bidderRequest.data.requestId).to.equal('123') expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') expect(bidderRequest.data.schain).to.deep.contains({ ver: '1.0', complete: 1, nodes: [{ asi: 'alkimi-onboarding.com', sid: '00001', hp: 1 }] }) - expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, width: 300, height: 250, impMediaType: 'Banner', adUnitCode: 'bannerAdUnitCode' }) + expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, sizes: [{width: 300, height: 250}], playerSizes: [], impMediaTypes: ['Banner'], adUnitCode: 'bannerAdUnitCode' }) expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined) expect(bidderRequest.data.bidIds).to.deep.contains('456') expect(bidderRequest.data.signature).to.equal(undefined) From 72ad8f05f5b307cf05268bc75d784ff9224493b3 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 27 Feb 2023 09:21:26 -0800 Subject: [PATCH 144/375] Revert "Rubicon Bid Adapter: add native support (#9574)" (#9599) This reverts commit 99ffff27e11b2c984dad2dc8256e930d548f5535. --- modules/rubiconBidAdapter.js | 555 +++++++++++--------- test/spec/modules/rubiconBidAdapter_spec.js | 366 ++----------- 2 files changed, 360 insertions(+), 561 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 293b0d51b33..d4875345043 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,12 +1,5 @@ -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, @@ -18,8 +11,14 @@ import { logMessage, logWarn, mergeDeep, - parseSizesInput, _each + parseSizesInput } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {find} from '../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'; @@ -139,101 +138,17 @@ 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, NATIVE], + supportedMediaTypes: [BANNER, VIDEO], /** * @param {object} bid * @return boolean */ isBidRequestValid: function (bid) { - let valid = true; if (typeof bid.params !== 'object') { return false; } @@ -245,16 +160,15 @@ export const spec = { return false } } - let bidFormats = bidType(bid, true); + let bidFormat = bidType(bid, true); // bidType is undefined? Return false - if (!bidFormats.length) { + if (!bidFormat) { return false; - } else if (bidFormats.includes(VIDEO)) { // bidType is video, make sure it has required params - valid = hasValidVideoParams(bid); + } else if (bidFormat === 'video') { // bidType is video, make sure it has required params + return hasValidVideoParams(bid); } - const hasBannerOrNativeMediaType = [BANNER, NATIVE].filter(mediaType => bidFormats.includes(mediaType)).length > 0; - if (!hasBannerOrNativeMediaType) return valid; - return valid && hasBannerOrNativeMediaType; + // bidType is banner? return true + return true; }, /** * @param {BidRequest[]} bidRequests @@ -264,57 +178,166 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { // separate video bids because the requests are structured differently let requests = []; - 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))) - ) - }); + 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' + } + } - if (filteredRequests && filteredRequests.length) { - const data = converter.toORTB({bidRequests: filteredRequests, bidderRequest}); + let modules = (getGlobal()).installedModules; + if (modules && (!modules.length || modules.indexOf('rubiconAnalyticsAdapter') !== -1)) { + deepSetValue(data, 'ext.prebid.analytics', {'rubicon': {'client-analytics': true}}); + } - filteredHttpRequest.push({ + 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); + + // 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; + + return { method: 'POST', url: `https://${rubiConf.videoHost || 'prebid-server'}.rubiconproject.com/openrtb2/auction`, data, - 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)) - ) - ); + bidRequest + } }); + if (config.getConfig('rubicon.singleRequest') !== true) { // bids are not grouped if single request mode is not enabled - requests = filteredHttpRequest.concat(bannerBidRequests.map(bidRequest => { + requests = videoRequests.concat(bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner').map(bidRequest => { const bidParams = spec.createSlotParams(bidRequest, bidderRequest); return { method: 'GET', @@ -329,7 +352,8 @@ 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 groupedBidRequests = bannerBidRequests.reduce((groupedBids, bid) => { + const nonVideoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner'); + const groupedBidRequests = nonVideoRequests.reduce((groupedBids, bid) => { (groupedBids[bid.params['siteId']] = groupedBids[bid.params['siteId']] || []).push(bid); return groupedBids; }, {}); @@ -338,7 +362,7 @@ export const spec = { const SRA_BID_LIMIT = 10; // multiple requests are used if bids groups have more than 10 bids - requests = filteredHttpRequest.concat(Object.keys(groupedBidRequests).reduce((aggregate, bidGroupKey) => { + requests = videoRequests.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 => { @@ -591,36 +615,106 @@ export const spec = { /** * @param {*} responseObj - * @param {BidRequest|Object.} request - if request was SRA the bidRequest argument will be a keyed BidRequest array object, + * @param {BidRequest|Object.} bidRequest - 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, request) { + interpretResponse: function (responseObj, {bidRequest}) { responseObj = responseObj.body; - const {data} = request; // check overall response if (!responseObj || typeof responseObj !== 'object') { return []; } - // Response from PBS Java openRTB + // video 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 = converter.fromORTB({request: data, response: responseObj}).bids; + 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); + }); + }); + 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).includes(VIDEO) && typeof ads === 'object') { + if (typeof bidRequest === 'object' && !Array.isArray(bidRequest) && bidType(bidRequest) === 'video' && typeof ads === 'object') { ads = ads[bidRequest.adUnitCode]; } @@ -686,6 +780,7 @@ 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); @@ -824,7 +919,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 = [ @@ -854,6 +949,65 @@ 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}}}, @@ -964,15 +1118,11 @@ 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; } @@ -983,14 +1133,13 @@ export function classifiedAsVideo(bidRequest) { } /** - * Determine bidRequest mediaTypes. All mediaTypes must be correct. If one fails, all the others will fail too. + * Determine bidRequest mediaType * @param bid the bid to test - * @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. + * @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). */ 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 @@ -998,43 +1147,37 @@ function bidType(bid, log = false) { if (log) { logError('Rubicon: mediaTypes.video.context must be outstream or instream'); } - return bidTypes; + return; } // 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 bidTypes; + return; } if (log) { logMessage('Rubicon: making video request for adUnit', bid.adUnitCode); } - bidTypes.push(VIDEO); - } - if (typeof deepAccess(bid, `mediaTypes.${NATIVE}`) !== 'undefined') { - bidTypes.push(NATIVE); - } - - if (typeof deepAccess(bid, `mediaTypes.${BANNER}`) !== 'undefined') { + return 'video'; + } else { // 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 bidTypes; + return; } // everything looks good for banner so lets do it if (log) { logMessage('Rubicon: making banner request for adUnit', bid.adUnitCode); } - bidTypes.push(BANNER); + return 'banner'; } - return bidTypes; } export const resetRubiConf = () => rubiConf = {}; @@ -1164,64 +1307,4 @@ 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/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index d33a9350359..e81ef1c805f 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -5,21 +5,13 @@ import { masSizeOrdering, resetUserSync, classifiedAsVideo, - resetRubiConf, - converter + resetRubiConf } 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'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid const PBS_INTEGRATION = 'pbjs'; @@ -110,9 +102,6 @@ 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]], @@ -334,9 +323,6 @@ 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]], @@ -1567,21 +1553,23 @@ 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.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'); + 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'); // Also want it to be in post.site.content.language - 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); + 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); // should contain version - expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: $$PREBID_GLOBAL$$.version}); + expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.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'); @@ -1684,8 +1672,8 @@ describe('the rubicon adapter', function () { expect( bidderRequest.bids[0].getFloor.calledWith({ currency: 'USD', - mediaType: '*', - size: '*' + mediaType: 'video', + size: [640, 480] }) ).to.be.true; @@ -1713,7 +1701,7 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].bidfloor).to.equal(1.23); }); - it('should continue with auction if getFloor throws error', function () { + it('should continue with auction and log error if getFloor throws one', function () { createVideoBidderRequest(); // default getFloor response is empty object so should not break and not send hard_floor bidderRequest.bids[0].getFloor = () => { @@ -1725,18 +1713,20 @@ 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); @@ -1746,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.prebid.bidder).to.have.property('superRubicon').that.is.an('object'); - expect(request.data.imp[0].ext.prebid.bidder).to.not.haveOwnProperty('rubicon'); + expect(request.data.imp[0].ext).to.have.property('superRubicon').that.is.an('object'); + expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); }); it('should add floors flag correctly to PBS Request', function () { @@ -2010,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.prebid.bidder.rubicon.video.size_id).to.equal(203); + expect(request.data.imp[0].ext.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 () { @@ -2224,18 +2214,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.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'); - + 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'); // Also want it to be in post.site.content.language expect(post.site.content.language).to.equal('en'); - expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); + 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.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); // Config user.id @@ -2373,110 +2366,6 @@ 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 () { @@ -3230,7 +3119,7 @@ describe('the rubicon adapter', function () { seatbid: [{ bid: [{ id: '0', - impid: '/19968336/header-bid-tag-0', + impid: 'instream_video1', adomain: ['test.com'], price: 2, crid: '4259970', @@ -3255,9 +3144,9 @@ describe('the rubicon adapter', function () { }], }; - const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - - let bids = spec.interpretResponse({body: response}, {data: request}); + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); expect(bids).to.be.lengthOf(1); @@ -3278,18 +3167,6 @@ 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 () { @@ -3319,7 +3196,7 @@ describe('the rubicon adapter', function () { seatbid: [{ bid: [{ id: '0', - impid: '/19968336/header-bid-tag-0', + impid: 'outstream_video1', adomain: ['test.com'], price: 2, crid: '4259970', @@ -3344,9 +3221,9 @@ describe('the rubicon adapter', function () { }], }; - const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - - let bids = spec.interpretResponse({body: response}, { data: request }); + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); expect(bids).to.be.lengthOf(1); @@ -3379,7 +3256,7 @@ describe('the rubicon adapter', function () { seatbid: [{ bid: [{ id: '0', - impid: '/19968336/header-bid-tag-0', + impid: 'outstream_video1', adomain: ['test.com'], price: 2, crid: '4259970', @@ -3405,11 +3282,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}, {data: request}); + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); const bid = bids[0]; bid.adUnitCode = 'outstream_video1_placement'; const adUnit = document.createElement('div'); @@ -3740,164 +3617,3 @@ 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' - }; -} From bb997b2cf93f0baade26e7c4fd3bb8ebbcc8a78c Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Mon, 27 Feb 2023 11:07:40 -0700 Subject: [PATCH 145/375] 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 --- modules/prebidServerBidAdapter/index.js | 18 ++++++++++++++++-- src/auction.js | 12 +++++++++++- src/constants.json | 1 + test/spec/auctionmanager_spec.js | 14 ++++++++++++++ .../modules/prebidServerBidAdapter_spec.js | 15 +++++++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 924748ce197..c5f082f5355 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -469,10 +469,13 @@ export function PrebidServer() { } processPBSRequest(s2sBidRequest, bidRequests, ajax, { - onResponse: function (isValid, requestedBidders) { + onResponse: function (isValid, requestedBidders, response) { if (isValid) { bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); } + if (shouldEmitNonbids(s2sBidRequest.s2sConfig, response)) { + emitNonBids(response.ext.seatnonbid, bidRequests[0].auctionId); + } done(); doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, @@ -551,7 +554,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques logError('error parsing response: ', result ? result.status : 'not valid JSON'); onResponse(false, requestedBidders); } else { - onResponse(true, requestedBidders); + onResponse(true, requestedBidders, result); } }, error: function () { @@ -567,6 +570,17 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques } }, 'processPBSRequest'); +function shouldEmitNonbids(s2sConfig, response) { + return s2sConfig?.extPrebid?.returnallbidstatus && response?.ext?.seatnonbid; +} + +function emitNonBids(seatnonbid, auctionId) { + events.emit(CONSTANTS.EVENTS.SEAT_NON_BID, { + seatnonbid, + auctionId + }); +} + /** * Global setter that sets eids permissions for bidders * This setter is to be used by userId module when included diff --git a/src/auction.js b/src/auction.js index 41e6fe3565b..9052d6fd7a8 100644 --- a/src/auction.js +++ b/src/auction.js @@ -151,11 +151,13 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a let _auctionEnd; let _timer; let _auctionStatus; + let _nonBids = []; function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests); } function addBidReceived(bidsReceived) { _bidsReceived = _bidsReceived.concat(bidsReceived); } function addBidRejected(bidsRejected) { _bidsRejected = _bidsRejected.concat(bidsRejected); } function addNoBid(noBid) { _noBids = _noBids.concat(noBid); } + function addNonBids(seatnonbids) { _nonBids = _nonBids.concat(seatnonbids); } function getProperties() { return { @@ -172,7 +174,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a bidsRejected: _bidsRejected, winningBids: _winningBids, timeout: _timeout, - metrics: metrics + metrics: metrics, + seatNonBids: _nonBids }; } @@ -369,6 +372,12 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a adapterManager.callSetTargetingBidder(bid.adapterCode || bid.bidder, bid); } + events.on(CONSTANTS.EVENTS.SEAT_NON_BID, (event) => { + if (event.auctionId === _auctionId) { + addNonBids(event.seatnonbid) + } + }); + return { addBidReceived, addBidRejected, @@ -387,6 +396,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a getBidRequests: () => _bidderRequests, getBidsReceived: () => _bidsReceived, getNoBids: () => _noBids, + getNonBids: () => _nonBids, getFPD: () => ortb2Fragments, getMetrics: () => metrics, }; diff --git a/src/constants.json b/src/constants.json index e33d65f3fb1..ef652af1ae5 100644 --- a/src/constants.json +++ b/src/constants.json @@ -31,6 +31,7 @@ "BID_RESPONSE": "bidResponse", "BID_REJECTED": "bidRejected", "NO_BID": "noBid", + "SEAT_NON_BID": "seatNonBid", "BID_WON": "bidWon", "BIDDER_DONE": "bidderDone", "BIDDER_ERROR": "bidderError", diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index e1ecf801aa3..5ddf3ebf75e 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -759,6 +759,20 @@ describe('auctionmanager.js', function () { sinon.assert.calledWith(stubMakeBidRequests, ...anyArgs.slice(0, 5).concat([sinon.match.same(ortb2Fragments)])); sinon.assert.calledWith(stubCallAdapters, ...anyArgs.slice(0, 7).concat([sinon.match.same(ortb2Fragments)])); }); + + it('correctly adds nonbids when they are emitted', () => { + const ortb2Fragments = { + global: {}, + bidder: {} + } + const auction = auctionManager.createAuction({adUnits, ortb2Fragments}); + expect(auction.getNonBids()[0]).to.equal(undefined); + events.emit(CONSTANTS.EVENTS.SEAT_NON_BID, { + auctionId: auction.getAuctionId(), + seatnonbid: ['test'] + }); + expect(auction.getNonBids()[0]).to.equal('test'); + }); }); describe('addBidResponse #1', function () { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 0ce060b9904..820a57b4e83 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -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 From 603fb255563312e58639736fd444a9808a7ac87a Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Tue, 28 Feb 2023 15:09:23 +0100 Subject: [PATCH 146/375] 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 --- modules/tappxBidAdapter.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index c7a54431ee9..1a189f5271d 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -404,7 +404,7 @@ function buildOneRequest(validBidRequests, bidderRequest) { device.w = screen.width; device.dnt = getDNT() ? 1 : 0; device.language = getLanguage(); - device.make = navigator.vendor ? navigator.vendor : ''; + device.make = getVendor(); let geo = {}; geo.country = deepAccess(validBidRequests, 'params.geo.country'); @@ -490,7 +490,12 @@ function getLanguage() { function getOs() { let ua = navigator.userAgent; - if (ua == null) { return 'unknown'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'ios'; } else if (ua.match(/Android/)) { return 'android'; } else if (ua.match(/Window/)) { return 'windows'; } else { return 'unknown'; } + if (ua.indexOf('Windows') != -1) { return 'Windows'; } else if (ua.indexOf('Mac OS X') != -1) { return 'macOS'; } else if (ua.match(/Android/)) { return 'Android'; } else if (ua.match(/(iPhone|iPod|iPad)/)) { return 'iOS'; } else if (ua.indexOf('Linux') != -1) { return 'Linux'; } else { return 'Unknown'; } +} + +function getVendor() { + let ua = navigator.userAgent; + if (ua.indexOf('Chrome') != -1) { return 'Google'; } else if (ua.indexOf('Firefox') != -1) { return 'Mozilla'; } else if (ua.indexOf('Safari') != -1) { return 'Apple'; } else if (ua.indexOf('Edge') != -1) { return 'Microsoft'; } else if (ua.indexOf('MSIE') != -1 || ua.indexOf('Trident') != -1) { return 'Microsoft'; } else { return ''; } } export function _getHostInfo(validBidRequests) { From 18db442d078c08332a192f9e26ed963d59458b69 Mon Sep 17 00:00:00 2001 From: rajsidhunovatiq <79534312+rajsidhunovatiq@users.noreply.github.com> Date: Tue, 28 Feb 2023 14:24:22 +0000 Subject: [PATCH 147/375] 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> --- modules/novatiqIdSystem.js | 12 +++++++++++- modules/userId/eids.js | 7 +++++-- test/spec/modules/novatiqIdSystem_spec.js | 23 +++++++++++++++++++---- 3 files changed, 35 insertions(+), 7 deletions(-) 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/userId/eids.js b/modules/userId/eids.js index afa4a212dcf..a96dbaee4af 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -250,10 +250,13 @@ export const USER_IDS_CONFIG = { // Novatiq Snowflake 'novatiq': { getValue: function(data) { - return data.snowflake + if (data.snowflake.id === undefined) { + return data.snowflake; + } + + return data.snowflake.id; }, source: 'novatiq.com', - atype: 1 }, 'uid2': { 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); }); }); }) From 74a1ffcde514e4d83a275674654c0b5013b86a64 Mon Sep 17 00:00:00 2001 From: kzhang-id5 <126269766+kzhang-id5@users.noreply.github.com> Date: Tue, 28 Feb 2023 16:27:59 +0100 Subject: [PATCH 148/375] 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 --- modules/id5IdSystem.js | 25 ++++++++++++++ test/spec/modules/id5IdSystem_spec.js | 49 +++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index a0745df37c8..488df984913 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -113,6 +113,11 @@ export const id5IdSubmodule = { return undefined; } + if (!hasWriteConsentToLocalStorage(consentData)) { + logInfo(LOG_PREFIX + 'Skipping ID5 local storage write because no consent given.') + return undefined; + } + const resp = function (cbFunction) { new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData()).execute() .then(response => { @@ -140,6 +145,11 @@ export const id5IdSubmodule = { extendId(config, consentData, cacheIdObj) { hasRequiredConfig(config); + if (!hasWriteConsentToLocalStorage(consentData)) { + logInfo(LOG_PREFIX + 'No consent given for ID5 local storage writing, skipping nb increment.') + return cacheIdObj; + } + const partnerId = (config && config.params && config.params.partner) || 0; incrementNb(partnerId); @@ -376,4 +386,19 @@ export function storeInLocalStorage(key, value, expDays) { storage.setDataInLocalStorage(`${key}`, value); } +/** + * Check to see if we can write to local storage based on purpose consent 1, and that we have vendor consent (ID5=131) + * @param {ConsentData} consentData + * @returns {boolean} + */ +function hasWriteConsentToLocalStorage(consentData) { + const hasGdpr = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; + const localstorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`) + const id5VendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID.toString()}`) + if (hasGdpr && (!localstorageConsent || !id5VendorConsent)) { + return false; + } + return true; +} + submodule('userId', id5IdSubmodule); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 8c0f8ad9cf3..392c11f3529 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -52,6 +52,18 @@ describe('ID5 ID System', function () { 'signature': ID5_RESPONSE_SIGNATURE, 'link_type': ID5_RESPONSE_LINK_TYPE }; + const ALLOWED_ID5_VENDOR_DATA = { + purpose: { + consents: { + 1: true + } + }, + vendor: { + consents: { + 131: true + } + } + } function getId5FetchConfig(storageName = ID5_STORAGE_NAME, storageType = 'html5') { return { @@ -205,6 +217,37 @@ describe('ID5 ID System', function () { }); }); + describe('Check for valid consent', function() { + const dataConsentVals = [ + [{purpose: {consents: {1: false}}}, {vendor: {consents: {131: true}}}, ' no purpose consent'], + [{purpose: {consents: {1: true}}}, {vendor: {consents: {131: false}}}, ' no vendor consent'], + [{purpose: {consents: {1: false}}}, {vendor: {consents: {131: false}}}, ' no purpose and vendor consent'], + [{purpose: {consents: undefined}}, {vendor: {consents: {131: true}}}, ' undefined purpose consent'], + [{purpose: {consents: {1: false}}}, {vendor: {consents: undefined}}], ' undefined vendor consent', + [undefined, {vendor: {consents: {131: true}}}, ' undefined purpose'], + [{purpose: {consents: {1: true}}}, {vendor: undefined}, ' undefined vendor'], + [{purpose: {consents: {1: true}}}, {vendor: {consents: {31: true}}}, ' incorrect vendor consent'] + ]; + + dataConsentVals.forEach(function([purposeConsent, vendorConsent, caseName]) { + it('should fail with invalid consent because of ' + caseName, function() { + let dataConsent = { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + purposeConsent, vendorConsent + } + } + expect(id5IdSubmodule.getId(config)).is.eq(undefined); + expect(id5IdSubmodule.getId(config, dataConsent)).is.eq(undefined); + + let cacheIdObject = 'cacheIdObject'; + expect(id5IdSubmodule.extendId(config)).is.eq(undefined); + expect(id5IdSubmodule.extendId(config, dataConsent, cacheIdObject)).is.eq(cacheIdObject); + }); + }); + }); + describe('Xhr Requests from getId()', function () { const responseHeader = {'Content-Type': 'application/json'}; @@ -248,7 +291,8 @@ describe('ID5 ID System', function () { let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) let consentData = { gdprApplies: true, - consentString: 'consentString' + consentString: 'consentString', + vendorData: ALLOWED_ID5_VENDOR_DATA } let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); @@ -297,7 +341,8 @@ describe('ID5 ID System', function () { let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) let consentData = { gdprApplies: true, - consentString: 'consentString' + consentString: 'consentString', + vendorData: ALLOWED_ID5_VENDOR_DATA } let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); From 8f9712c0450c620affb2322de960ffa5c1a1951c Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 28 Feb 2023 08:49:20 -0800 Subject: [PATCH 149/375] GDPR (consentManagement): fix `actionTimeout` behavior (#9600) * GDPR (consentManagement): fix `actionTimeout` behavior * Add test case for actionTimeout = 0 --- modules/consentManagement.js | 85 +++++++++------------ test/spec/modules/consentManagement_spec.js | 77 ++++++++++--------- 2 files changed, 78 insertions(+), 84 deletions(-) diff --git a/modules/consentManagement.js b/modules/consentManagement.js index bdf6649bcba..44e8d39b832 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -18,16 +18,12 @@ const CMP_VERSION = 2; export let userCMP; export let consentTimeout; -export let actionTimeout; export let gdprScope; export let staticConsentData; +let actionTimeout; let consentData; let addedConsentHook = false; -let provisionalConsent; -let onTimeout; -let timer = null; -let actionTimer = null; // add new CMPs here, with their dedicated lookup function const cmpCallMap = { @@ -43,12 +39,6 @@ function lookupStaticConsentData({onSuccess, onError}) { processCmpData(staticConsentData, {onSuccess, onError}) } -export function setActionTimeout(timeout = setTimeout) { - clearTimeout(timer); - timer = null; - actionTimer = timeout(onTimeout, actionTimeout); -} - /** * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function @@ -56,7 +46,7 @@ export function setActionTimeout(timeout = setTimeout) { * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) */ -function lookupIabConsent({onSuccess, onError}) { +function lookupIabConsent({onSuccess, onError, onEvent}) { function findCMP() { let f = window; let cmpFrame; @@ -90,11 +80,9 @@ function lookupIabConsent({onSuccess, onError}) { function cmpResponseCallback(tcfData, success) { logInfo('Received a response from CMP', tcfData); if (success) { + onEvent(tcfData); if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { processCmpData(tcfData, {onSuccess, onError}); - } else { - provisionalConsent = tcfData; - if (!isNaN(actionTimeout) && actionTimer === null && timer != null) setActionTimeout(); } } else { onError('CMP unable to register callback function. Please check CMP setup.'); @@ -173,20 +161,27 @@ function lookupIabConsent({onSuccess, onError}) { * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra * error arguments that will be undefined if there's no error. */ -export function loadConsentData(cb, callMap = cmpCallMap, timeout = setTimeout) { +function loadConsentData(cb) { let isDone = false; + let timer = null; + let onTimeout, provisionalConsent; + let cmpLoaded = false; - function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { + function resetTimeout(timeout) { if (timer != null) { clearTimeout(timer); - timer = null; } - - if (actionTimer != null) { - clearTimeout(actionTimer); - actionTimer = null; + if (!isDone && timeout != null) { + if (timeout === 0) { + onTimeout() + } else { + timer = setTimeout(onTimeout, timeout); + } } + } + function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { + resetTimeout(null); isDone = true; gdprDataHandler.setConsentData(consentData); if (typeof cb === 'function') { @@ -203,32 +198,30 @@ export function loadConsentData(cb, callMap = cmpCallMap, timeout = setTimeout) onSuccess: (data) => done(data, false), onError: function (msg, ...extraArgs) { done(null, true, msg, ...extraArgs); + }, + onEvent: function (consentData) { + provisionalConsent = consentData; + if (cmpLoaded) return; + cmpLoaded = true; + if (actionTimeout != null) { + resetTimeout(actionTimeout); + } } } - callMap[userCMP](callbacks); - - if (!isDone) { - onTimeout = () => { - const continueToAuction = (data) => { - done(data, false, 'CMP did not load, continuing auction...'); - } - processCmpData(provisionalConsent, { - onSuccess: continueToAuction, - onError: () => continueToAuction(storeConsentData(undefined)) - }) + onTimeout = () => { + const continueToAuction = (data) => { + done(data, false, `${cmpLoaded ? 'Timeout waiting for user action on CMP' : 'CMP did not load'}, continuing auction...`); } + processCmpData(provisionalConsent, { + onSuccess: continueToAuction, + onError: () => continueToAuction(storeConsentData(undefined)), + }) + } - if (consentTimeout === 0) { - onTimeout(); - } else { - if (timer != null) { - clearTimeout(timer); - timer = null; - } - - timer = timeout(onTimeout, consentTimeout); - } + cmpCallMap[userCMP](callbacks); + if (!(actionTimeout != null && cmpLoaded)) { + resetTimeout(consentTimeout); } } @@ -352,10 +345,6 @@ export function setConsentConfig(config) { logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); } - if (isNumber(config.actionTimeout)) { - actionTimeout = config.actionTimeout; - } - if (isNumber(config.timeout)) { consentTimeout = config.timeout; } else { @@ -363,6 +352,8 @@ export function setConsentConfig(config) { logInfo(`consentManagement config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); } + actionTimeout = isNumber(config.actionTimeout) ? config.actionTimeout : null; + // if true, then gdprApplies should be set to true gdprScope = config.defaultGdprScope === true; diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 506b88ad839..48b48c987e5 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -685,6 +685,46 @@ describe('consentManagement', function () { }); }); + it('should timeout after actionTimeout from the first CMP event', (done) => { + mockTcfEvent({ + eventStatus: 'cmpuishown', + tcString: 'mock-consent-string', + vendorData: {} + }); + setConsentConfig({ + timeout: 1000, + actionTimeout: 100, + cmpApi: 'iab', + defaultGdprScope: true + }); + let hookRan = false; + requestBidsHook(() => { + hookRan = true; + }, {}); + setTimeout(() => { + expect(hookRan).to.be.true; + done(); + }, 200) + }); + + it('should still pick up consent data when actionTimeout is 0', (done) => { + mockTcfEvent({ + eventStatus: 'tcloaded', + tcString: 'mock-consent-string', + vendorData: {} + }); + setConsentConfig({ + timeout: 1000, + actionTimeout: 0, + cmpApi: 'iab', + defaultGdprScope: true + }); + requestBidsHook(() => { + expect(gdprDataHandler.getConsentData().consentString).to.eql('mock-consent-string'); + done(); + }, {}) + }) + Object.entries({ 'null': null, 'empty': '', @@ -737,41 +777,4 @@ describe('consentManagement', function () { }); }); }); - - describe('actionTimeout', function () { - afterEach(function () { - config.resetConfig(); - resetConsentData(); - }); - - it('should set actionTimeout if present', () => { - setConsentConfig({ - gdpr: { timeout: 5000, actionTimeout: 5500 } - }); - - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(5000); - expect(actionTimeout).to.be.equal(5500); - }); - - it('should utilize actionTimeout duration on initial user visit when user action is pending', () => { - const cb = () => {}; - const cmpCallMap = { - 'iab': () => {}, - 'static': () => {} - }; - const timeout = sinon.spy(); - - setConsentConfig({ - gdpr: { timeout: 5000, actionTimeout: 5500 } - }); - loadConsentData(cb, cmpCallMap, timeout); - - sinon.assert.calledWith(timeout, sinon.match.any, 5000); - - setActionTimeout(); - - timeout.lastCall.lastArg === 5500; - }); - }); }); From 7f54ed6363c4ac0354489cfcb873fd4ed1bdaec8 Mon Sep 17 00:00:00 2001 From: VisibleMeasuresHB <123548546+VisibleMeasuresHB@users.noreply.github.com> Date: Tue, 28 Feb 2023 19:59:31 +0200 Subject: [PATCH 150/375] VisibleMeasures Bid Adapter : initial adapter release (#9488) * add VisibleMeasures adapter * fix test --- modules/visiblemeasuresBidAdapter.js | 212 ++++++++++ modules/visiblemeasuresBidAdapter.md | 79 ++++ .../modules/visiblemeasuresBidAdapter_spec.js | 400 ++++++++++++++++++ 3 files changed, 691 insertions(+) create mode 100644 modules/visiblemeasuresBidAdapter.js create mode 100644 modules/visiblemeasuresBidAdapter.md create mode 100644 test/spec/modules/visiblemeasuresBidAdapter_spec.js diff --git a/modules/visiblemeasuresBidAdapter.js b/modules/visiblemeasuresBidAdapter.js new file mode 100644 index 00000000000..89dcf36917f --- /dev/null +++ b/modules/visiblemeasuresBidAdapter.js @@ -0,0 +1,212 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'visiblemeasures'; +const AD_URL = 'https://us-e.visiblemeasures.com/pbjs'; +const SYNC_URL = 'https://cs.visiblemeasures.com'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + // TODO: does the fallback make sense here? + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/visiblemeasuresBidAdapter.md b/modules/visiblemeasuresBidAdapter.md new file mode 100644 index 00000000000..aff91f47a2a --- /dev/null +++ b/modules/visiblemeasuresBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: VisibleMeasures Bidder Adapter +Module Type: VisibleMeasures Bidder Adapter +Maintainer: Support@visiblemeasures.com +``` + +# Description + +Connects to VisibleMeasures exchange for bids. +VisibleMeasures bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'visiblemeasures', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'visiblemeasures', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'visiblemeasures', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` 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`) + }); + }); +}); From 109d6da66e9de9f7a701b7e3c427b7ebab727c82 Mon Sep 17 00:00:00 2001 From: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Date: Tue, 28 Feb 2023 23:46:21 +0530 Subject: [PATCH 151/375] 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 --- modules/pubmaticAnalyticsAdapter.js | 35 +++++++- modules/pubmaticBidAdapter.js | 55 +++++++++--- .../modules/pubmaticAnalyticsAdapter_spec.js | 57 +++++++++++- test/spec/modules/pubmaticBidAdapter_spec.js | 90 ++++++++++++++++++- 4 files changed, 220 insertions(+), 17 deletions(-) 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 2c02d83ff33..5ca8a2c04b3 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -969,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, @@ -1289,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) { 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 c2e9c1a4a25..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' } }] }, { @@ -3428,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); @@ -3716,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() { From 8275dc416d8c11fcc70c5543126c45a5ec207278 Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Tue, 28 Feb 2023 11:55:15 -0800 Subject: [PATCH 152/375] 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 --- src/auction.js | 3 ++- test/spec/auctionmanager_spec.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/auction.js b/src/auction.js index 9052d6fd7a8..6dd0432712f 100644 --- a/src/auction.js +++ b/src/auction.js @@ -914,6 +914,7 @@ export function getStandardBidderSettings(mediaType, bidderCode) { } } } + return standardSettings; } @@ -965,7 +966,7 @@ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { if ( ((typeof bidderSettings.suppressEmptyKeys !== 'undefined' && bidderSettings.suppressEmptyKeys === true) || - key === CONSTANTS.TARGETING_KEYS.DEAL) && // hb_deal is suppressed automatically if not set + key === CONSTANTS.TARGETING_KEYS.DEAL || key === CONSTANTS.TARGETING_KEYS.ACAT) && // hb_deal & hb_acat are suppressed automatically if not set ( isEmptyStr(value) || value === null || diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 5ddf3ebf75e..6a72da8edce 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -232,6 +232,15 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); + it('should suppress acat if undefined', function () { + const noAcatBid = deepClone(DEFAULT_BID); + noAcatBid.meta.primaryCatId = '' + let expected = getDefaultExpected(noAcatBid); + delete expected.hb_acat; + let response = getKeyValueTargetingPairs(noAcatBid.bidderCode, noAcatBid); + assert.deepEqual(response, expected); + }); + it('No bidder level configuration defined - default for video', function () { config.setConfig({ cache: { From 6bd8a1de53c770375e678876e745e70d304a15d4 Mon Sep 17 00:00:00 2001 From: fndigrazia Date: Wed, 1 Mar 2023 10:21:02 -0300 Subject: [PATCH 153/375] Eplanning Bid Adapter : cut url when url size greater than 255 (#9606) * cut url whit url length is greater than 255 * Fix lint --- modules/eplanningBidAdapter.js | 16 +++++++-- test/spec/modules/eplanningBidAdapter_spec.js | 35 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index e230858487f..2216ab329b0 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -24,6 +24,7 @@ const VAST_INSTREAM = 1; const VAST_OUTSTREAM = 2; const VAST_VERSION_DEFAULT = 3; const DEFAULT_SIZE_VAST = '640x480'; +const MAX_LEN_URL = 255; export const spec = { code: BIDDER_CODE, @@ -60,7 +61,7 @@ export const spec = { params = { rnd: rnd, e: spaces.str, - ur: pageUrl || FILE, + ur: cutUrl(pageUrl || FILE), pbv: '$prebid.version$', ncb: '1', vs: spaces.vs @@ -70,7 +71,7 @@ export const spec = { } if (referrerUrl) { - params.fr = referrerUrl; + params.fr = cutUrl(referrerUrl); } if (bidderRequest && bidderRequest.gdprConsent) { @@ -491,6 +492,17 @@ function visibilityHandler(obj) { } } +function cutUrl (url) { + if (url.length > MAX_LEN_URL) { + url = url.split('?')[0]; + if (url.length > MAX_LEN_URL) { + url = url.slice(0, MAX_LEN_URL); + } + } + + return url; +} + function registerAuction(storageID) { let value; try { diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index cb8393a29b8..1a6cfd7afe4 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -729,11 +729,46 @@ describe('E-Planning Adapter', function () { expect(ur).to.equal(bidderRequest.refererInfo.page); }); + it('should return ur parameter without params query string when current window url length is greater than 255', function () { + let bidderRequestParams = bidderRequest; + + bidderRequestParams.refererInfo.page = refererUrl + '?param=' + 'x'.repeat(255); + const ur = spec.buildRequests(bidRequests, bidderRequest).data.ur; + expect(ur).to.equal(refererUrl); + }); + + it('should return ur parameter with a length of 255 when url length is greater than 255', function () { + let bidderRequestParams = bidderRequest; + let url_255_characters = 'https://localhost/abc' + '/subse'.repeat(39); + let refererUrl = url_255_characters + '/ext'.repeat(5) + '?param=' + 'x'.repeat(15); + + bidderRequestParams.refererInfo.page = refererUrl; + const ur = spec.buildRequests(bidRequests, bidderRequest).data.ur; + expect(ur).to.equal(url_255_characters); + }); + it('should return fr parameter when there is a referrer', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const dataRequest = request.data; expect(dataRequest.fr).to.equal(refererUrl); }); + it('should return fr parameter without params query string when ref length is greater than 255', function () { + let bidderRequestParams = bidderRequest; + + bidderRequestParams.refererInfo.ref = refererUrl + '?param=' + 'x'.repeat(255); + const fr = spec.buildRequests(bidRequests, bidderRequest).data.fr; + expect(fr).to.equal(refererUrl); + }); + + it('should return fr parameter with a length of 255 when url length is greater than 255', function () { + let bidderRequestParams = bidderRequest; + let url_255_characters = 'https://localhost/abc' + '/subse'.repeat(39); + let refererUrl = url_255_characters + '/ext'.repeat(5) + '?param=' + 'x'.repeat(15); + + bidderRequestParams.refererInfo.ref = refererUrl; + const fr = spec.buildRequests(bidRequests, bidderRequest).data.fr; + expect(fr).to.equal(url_255_characters); + }); it('should return crs parameter with document charset', function () { let expected; From 5a52d9d20be26b42a7dec6556e9bbee7531bf35a Mon Sep 17 00:00:00 2001 From: Patrick Loughrey Date: Thu, 2 Mar 2023 08:30:26 -0500 Subject: [PATCH 154/375] TL-35335: Cast playbackmethod as array (#9610) --- modules/tripleliftBidAdapter.js | 3 +++ test/spec/modules/tripleliftBidAdapter_spec.js | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 8012a4a8051..1f654d34c6a 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -195,6 +195,9 @@ function _getORTBVideo(bidRequest) { video.placement = 3 } } + if (video.playbackmethod && Number.isInteger(video.playbackmethod)) { + video.playbackmethod = Array.from(String(video.playbackmethod), Number); + } // clean up oRTB object delete video.playerSize; 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 () { From e59fa41dfd9ce5e74e82f4539176f04e013f1af2 Mon Sep 17 00:00:00 2001 From: aenrel <123280204+aenrel@users.noreply.github.com> Date: Thu, 2 Mar 2023 06:30:26 -0800 Subject: [PATCH 155/375] 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 <> --- .../gpt/relevadRtdProvider_example.html | 138 ++++++ modules/relevadRtdProvider.js | 365 ++++++++++++++++ modules/relevadRtdProvider.md | 108 +++++ test/spec/modules/relevadRtdProvider_spec.js | 412 ++++++++++++++++++ 4 files changed, 1023 insertions(+) create mode 100644 integrationExamples/gpt/relevadRtdProvider_example.html create mode 100644 modules/relevadRtdProvider.js create mode 100644 modules/relevadRtdProvider.md create mode 100644 test/spec/modules/relevadRtdProvider_spec.js 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/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..c2604619edc --- /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 [anna@relevad.com](mailto:anna@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/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': '', + } + ); + }); +}); From b5c00b7f1056273fa70eb8331925f6d818cfeb4b Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Thu, 2 Mar 2023 09:53:34 -0500 Subject: [PATCH 156/375] AMX Bid Adapter: add gpp support (#9556) * AMX bid adapter: add gpp support, cookie sync improvements * fix unit tests --- modules/amxBidAdapter.js | 101 +++++++++++++++++++++++- test/spec/modules/amxBidAdapter_spec.js | 99 ++++++++++++++++++++++- 2 files changed, 196 insertions(+), 4 deletions(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index c7bc99b6aa6..2a3716589b8 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -9,6 +9,7 @@ import { triggerPixel, isFn, logError, + isArray, } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -192,6 +193,51 @@ function resolveSize(bid, request, bidId) { return [bidRequest.aw, bidRequest.ah]; } +function isSyncEnabled(syncConfigP, syncType) { + const syncConfig = syncConfigP[syncType]; + if (syncConfig == null) { + return false; + } + + if (syncConfig.bidders === '*' || (isArray(syncConfig.bidders) && syncConfig.bidders.indexOf('amx') !== -1)) { + return syncConfig.filter == null || syncConfig.filter === 'include'; + } + + return false; +} + +const SYNC_IMAGE = 1; +const SYNC_IFRAME = 2; + +function getSyncSettings() { + const syncConfig = config.getConfig('userSync'); + if (syncConfig == null) { + return { + d: 0, + l: 0, + t: 0, + e: true + }; + } + + const settings = { d: syncConfig.syncDelay, l: syncConfig.syncsPerBidder, t: 0, e: syncConfig.syncEnabled } + const all = isSyncEnabled(syncConfig.filterSettings, 'all') + + if (all) { + settings.t = SYNC_IMAGE & SYNC_IFRAME; + return settings; + } + + if (isSyncEnabled(syncConfig.filterSettings, 'iframe')) { + settings.t |= SYNC_IFRAME; + } + if (isSyncEnabled(syncConfig.filterSettings, 'image')) { + settings.t |= SYNC_IMAGE; + } + + return settings; +} + function values(source) { if (Object.values != null) { return Object.values(source); @@ -202,6 +248,30 @@ function values(source) { }); } +function getGpp(bidderRequest) { + if (bidderRequest?.gppConsent != null) { + return bidderRequest.gppConsent; + } + + return bidderRequest?.ortb2?.regs?.gpp ?? {gppString: '', applicableSections: ''}; +} + +function buildReferrerInfo(bidderRequest) { + if (bidderRequest.refererInfo == null) { + return {r: '', t: false, c: '', l: 0, s: []} + } + + const re = bidderRequest.refererInfo; + + return { + r: re.topmostLocation, + t: re.reachedTop, + l: re.numIframes, + s: re.stack, + c: re.canonicalUrl, + } +} + const isTrue = (boolValue) => boolValue === true || boolValue === 1 || boolValue === 'true'; @@ -249,6 +319,7 @@ export const spec = { w: screen.width, gs: deepAccess(bidderRequest, 'gdprConsent.gdprApplies', ''), gc: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), + gpp: getGpp(bidderRequest), u: refInfo(bidderRequest, 'page', loc.href), do: refInfo(bidderRequest, 'site', loc.hostname), re: refInfo(bidderRequest, 'ref'), @@ -261,6 +332,8 @@ export const spec = { fpd2: bidderRequest.ortb2, tmax: config.getConfig('bidderTimeout'), amp: refInfo(bidderRequest, 'isAmp', null), + ri: buildReferrerInfo(bidderRequest), + sync: getSyncSettings(), eids: values( bidRequests.reduce((all, bid) => { // we only want unique ones in here @@ -287,17 +360,38 @@ export const spec = { }; }, - getUserSyncs(syncOptions, serverResponses) { + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const qp = { + gdpr_consent: enc(gdprConsent?.consentString || ''), + gdpr: enc(gdprConsent?.gdprApplies ? 1 : 0), + us_privacy: enc(uspConsent || ''), + gpp: enc(gppConsent?.gppString || ''), + gpp_sid: enc(gppConsent?.applicableSections || '') + }; + + const iframeSync = { + url: `https://prebid.a-mo.net/isyn?${formatQS(qp)}`, + type: 'iframe' + }; + if (serverResponses == null || serverResponses.length === 0) { + if (syncOptions.iframeEnabled) { + return [iframeSync] + } + return []; } + const output = []; + let hasFrame = false; + _each(serverResponses, function ({ body: response }) { if (response != null && response.p != null && response.p.hreq) { _each(response.p.hreq, function (syncPixel) { const pixelType = syncPixel.indexOf('__st=iframe') !== -1 ? 'iframe' : 'image'; if (syncOptions.iframeEnabled || pixelType === 'image') { + hasFrame = hasFrame || (pixelType === 'iframe') || (syncPixel.indexOf('cchain') !== -1) output.push({ url: syncPixel, type: pixelType, @@ -306,6 +400,11 @@ export const spec = { }); } }); + + if (!hasFrame && output.length < 2) { + output.push(iframeSync) + } + return output; }, diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index d71e9ab5cba..da31b221096 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/amxBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; const sampleRequestId = '82c91e127a9b93e'; @@ -34,9 +35,17 @@ const sampleBidderRequest = { consentString: utils.getUniqueIdentifierStr(), vendorData: {}, }, + gppConsent: { + gppString: 'example', + applicableSections: 'example' + }, auctionId: utils.getUniqueIdentifierStr(), uspConsent: '1YYY', refererInfo: { + reachedTop: true, + numIframes: 10, + stack: ['https://www.prebid.org'], + canonicalUrl: 'https://prebid.org', location: 'https://www.prebid.org', site: 'prebid.org', topmostLocation: 'https://www.prebid.org', @@ -195,9 +204,12 @@ describe('AmxBidAdapter', () => { }); }); describe('getUserSync', () => { - it('will only sync from valid server responses', () => { + it('Will perform an iframe sync even if there is no server response..', () => { const syncs = spec.getUserSyncs({ iframeEnabled: true }); - expect(syncs).to.eql([]); + expect(syncs).to.eql([{ + type: 'iframe', + url: 'https://prebid.a-mo.net/isyn?gdpr_consent=&gdpr=0&us_privacy=&gpp=&gpp_sid=' + }]); }); it('will return valid syncs from a server response', () => { @@ -260,6 +272,15 @@ describe('AmxBidAdapter', () => { expect(data.tm).to.equal(true); }); + it('will attach additional referrer info data', () => { + const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); + expect(data.ri.r).to.equal(sampleBidderRequest.refererInfo.topmostLocation); + expect(data.ri.t).to.equal(sampleBidderRequest.refererInfo.reachedTop); + expect(data.ri.l).to.equal(sampleBidderRequest.refererInfo.numIframes); + expect(data.ri.s).to.equal(sampleBidderRequest.refererInfo.stack); + expect(data.ri.c).to.equal(sampleBidderRequest.refererInfo.canonicalUrl); + }); + it('if prebid is in an iframe, will use the frame url as domain, if the topmost is not avialable', () => { const { data } = spec.buildRequests([sampleBidRequestBase], { ...sampleBidderRequest, @@ -286,7 +307,7 @@ describe('AmxBidAdapter', () => { expect(data.re).to.equal('http://search-traffic-source.com'); }); - it('handles referer data and GDPR, USP Consent, COPPA', () => { + it('handles GDPR, USP Consent, COPPA, and GPP', () => { const { data } = spec.buildRequests( [sampleBidRequestBase], sampleBidderRequest @@ -295,6 +316,7 @@ describe('AmxBidAdapter', () => { expect(data.gs).to.equal(sampleBidderRequest.gdprConsent.gdprApplies); expect(data.gc).to.equal(sampleBidderRequest.gdprConsent.consentString); expect(data.usp).to.equal(sampleBidderRequest.uspConsent); + expect(data.gpp).to.equal(sampleBidderRequest.gppConsent); expect(data.cpp).to.equal(0); }); @@ -316,6 +338,70 @@ describe('AmxBidAdapter', () => { expect(data.bwc).to.equal(bidderWinsCount); expect(data.trc).to.equal(0); }); + + it('will attach sync configuration', () => { + const request = () => spec.buildRequests( + [sampleBidRequestBase], + sampleBidderRequest + ); + + const setConfig = (filterSettings) => + config.setConfig({ + userSync: { + syncsPerBidder: 2, + syncDelay: 2300, + syncEnabled: true, + filterSettings, + } + }); + + const test = (filterSettings) => { + setConfig(filterSettings); + return request().data.sync; + } + + const base = { d: 2300, l: 2, e: true }; + + const tests = [[{ + image: { + bidders: '*', + filter: 'include' + }, + iframe: { + bidders: '*', + filter: 'include' + } + }, { ...base, t: 3 }], [{ + image: { + bidders: ['amx'], + }, + iframe: { + bidders: '*', + filter: 'include' + } + }, { ...base, t: 3 }], [{ + image: { + bidders: ['other'], + }, + iframe: { + bidders: '*' + } + }, { ...base, t: 2 }], [{ + image: { + bidders: ['amx'] + }, + iframe: { + bidders: ['amx'], + filter: 'exclude' + } + }, { ...base, t: 1 }]] + + for (let i = 0, l = tests.length; i < l; i++) { + const [result, expected] = tests[i]; + expect(test(result), `input: ${JSON.stringify(result)}`).to.deep.equal(expected); + } + }); + it('will forward first-party data', () => { const { data } = spec.buildRequests( [sampleBidRequestBase], @@ -509,7 +595,14 @@ describe('AmxBidAdapter', () => { before(() => { _Image = window.Image; window.Image = class FakeImage { + _src = ''; + + get src() { + return this._src; + } + set src(value) { + this._src = value; firedPixels.push(value); } }; From 974e401eb8ba73e1316c7ddd793c2541e2d7372d Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Thu, 2 Mar 2023 16:32:42 +0100 Subject: [PATCH 157/375] 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 --- modules/rubiconBidAdapter.js | 555 +++++++++----------- test/spec/modules/rubiconBidAdapter_spec.js | 374 +++++++++++-- 2 files changed, 565 insertions(+), 364 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index d4875345043..293b0d51b33 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'; @@ -138,17 +139,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; } @@ -160,15 +245,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 @@ -178,166 +264,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', @@ -352,8 +329,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; }, {}); @@ -362,7 +338,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 => { @@ -615,106 +591,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]; } @@ -780,7 +686,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); @@ -919,7 +824,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 = [ @@ -949,65 +854,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}}}, @@ -1118,11 +964,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; } @@ -1133,13 +983,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 @@ -1147,37 +998,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 = {}; @@ -1307,4 +1164,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/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index e81ef1c805f..034536d4973 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]], @@ -323,6 +335,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]], @@ -1542,7 +1557,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 +1568,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 +1685,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 +1714,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 +1726,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 +1747,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 +2011,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 +2075,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 +2213,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 +2224,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 +2373,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 +3230,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 +3255,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 +3278,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 +3319,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 +3344,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 +3379,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 +3405,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 +3740,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' + }; +} From 500b348d8c8af535c5ba101c116f95a1cc013d97 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Thu, 2 Mar 2023 12:33:34 -0300 Subject: [PATCH 158/375] [Video Module] Bugfix: Allow publishers to override video params (#9611) * allows publisher to override video params * passes proper config to ima --- modules/videoModule/index.js | 8 +++-- modules/videojsVideoProvider.js | 6 ++-- test/spec/modules/videoModule/pbVideo_spec.js | 31 +++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js index fb3c621918d..53f8a217f37 100644 --- a/modules/videoModule/index.js +++ b/modules/videoModule/index.js @@ -139,13 +139,15 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent return; } - const video = Object.assign({}, adUnit.mediaTypes.video, ortbVideo); + const video = Object.assign({}, ortbVideo, adUnit.mediaTypes.video); - video.context = ortbVideo.placement === PLACEMENT.INSTREAM ? 'instream' : 'outstream'; + if (!video.context) { + video.context = ortbVideo.placement === PLACEMENT.INSTREAM ? 'instream' : 'outstream'; + } const width = ortbVideo.w; const height = ortbVideo.h; - if (width && height) { + if (!video.playerSize && width && height) { video.playerSize = [width, height]; } diff --git a/modules/videojsVideoProvider.js b/modules/videojsVideoProvider.js index cc17758bd72..96e5bc4311e 100644 --- a/modules/videojsVideoProvider.js +++ b/modules/videojsVideoProvider.js @@ -35,7 +35,7 @@ https://github.com/Conviva/conviva-js-videojs/blob/master/conviva-videojs-module const setupFailMessage = 'Failed to instantiate the player'; const AD_MANAGER_EVENTS = [AD_LOADED, AD_STARTED, AD_IMPRESSION, AD_PLAY, AD_PAUSE, AD_TIME, AD_COMPLETE, AD_SKIPPED]; -export function VideojsProvider(config, vjs_, adState_, timeState_, callbackStorage_, utils) { +export function VideojsProvider(providerConfig, vjs_, adState_, timeState_, callbackStorage_, utils) { let vjs = vjs_; // Supplied callbacks are typically wrapped by handlers // we use this dict to keep track of these pairings @@ -46,7 +46,7 @@ export function VideojsProvider(config, vjs_, adState_, timeState_, callbackStor let player = null; let playerVersion = null; let playerIsSetup = false; - const {playerConfig, divId} = config; + const {playerConfig, divId} = providerConfig; let isMuted; let previousLastTimePosition = 0; let lastTimePosition = 0; @@ -521,7 +521,7 @@ export function VideojsProvider(config, vjs_, adState_, timeState_, callbackStor return; } - const adConfig = utils.getAdConfig(config); + const adConfig = utils.getAdConfig(playerConfig); player.ima(adConfig); } 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 () { From 99d2fb73c71acd31668aebf1885235169a6e1657 Mon Sep 17 00:00:00 2001 From: julian-burger-ttd <105891200+julian-burger-ttd@users.noreply.github.com> Date: Thu, 2 Mar 2023 07:47:51 -0800 Subject: [PATCH 159/375] 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> --- modules/ttdBidAdapter.js | 10 ++++++++++ modules/ttdBidAdapter.md | 4 +++- test/spec/modules/ttdBidAdapter_spec.js | 23 +++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 884c43c438a..3bf20ed43ae 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -46,6 +46,11 @@ function getRegs(bidderRequest) { } function getBidFloor(bid) { + // value from params takes precedance over value set by Floor Module + if (bid.params.bidfloor) { + return bid.params.bidfloor; + } + if (!utils.isFn(bid.getFloor)) { return null; } @@ -349,6 +354,11 @@ export const spec = { return false; } + // optional parameters + if (bid.params.bidfloor && isNaN(parseFloat(bid.params.bidfloor))) { + return false; + } + const gpid = utils.deepAccess(bid, 'ortb2Imp.ext.gpid'); if (!bid.params.placementId && !gpid) { utils.logWarn(BIDDER_CODE + ': one of params.placementId or gpid (via the GPT module https://docs.prebid.org/dev-docs/modules/gpt-pre-auction.html) must be passed'); diff --git a/modules/ttdBidAdapter.md b/modules/ttdBidAdapter.md index efdef751149..108aa1a7286 100644 --- a/modules/ttdBidAdapter.md +++ b/modules/ttdBidAdapter.md @@ -50,6 +50,7 @@ The Trade Desk bid adapter supports Banner and Video. supplySourceId: 'supplier', publisherId: '1427ab10f2e448057ed3b422', placementId: '/1111/home#header', + bidfloor: 0.45, banner: { expdir: [1, 3] }, @@ -107,7 +108,8 @@ The Trade Desk bid adapter supports Banner and Video. params: { supplySourceId: 'supplier', publisherId: '1427ab10f2e448057ed3b422', - placementId: '/1111/home#header' + placementId: '/1111/home#header', + bidfloor: 0.45 } } ] 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 () { From 17bb74b649b591c0253782722c9ea97173a76b30 Mon Sep 17 00:00:00 2001 From: geoffray-viously <95097046+geoffray-viously@users.noreply.github.com> Date: Thu, 2 Mar 2023 18:40:57 +0100 Subject: [PATCH 160/375] Add: banner requests for ViouslyBidAdapter (#9513) --- modules/viouslyBidAdapter.js | 75 +++++++++++----- test/spec/modules/ViouslyBidAdapter_spec.js | 98 ++++++++++++++++++++- 2 files changed, 150 insertions(+), 23 deletions(-) diff --git a/modules/viouslyBidAdapter.js b/modules/viouslyBidAdapter.js index f18c9c7b3db..677838dae55 100644 --- a/modules/viouslyBidAdapter.js +++ b/modules/viouslyBidAdapter.js @@ -1,7 +1,7 @@ import { deepAccess, logError, parseUrl, parseSizesInput, triggerPixel } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; -import { VIDEO } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import find from 'core-js-pure/features/array/find.js'; // eslint-disable-line prebid/validate-imports const BIDDER_CODE = 'viously'; @@ -16,7 +16,7 @@ const REQUIRED_VIOUSLY_PARAMS = ['pid']; export const spec = { code: BIDDER_CODE, // gvlid: GVLID, - supportedMediaTypes: [VIDEO], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -26,33 +26,48 @@ export const spec = { */ isBidRequestValid: function(bid) { let videoParams = deepAccess(bid, 'mediaTypes.video'); + let bannerParams = deepAccess(bid, 'mediaTypes.banner'); if (!bid.params) { logError('The bid params are missing'); return false; } - if (!videoParams) { - logError('The placement must be of video type'); + if (!bannerParams && !videoParams) { + logError('The placement must be of banner or video type'); return false; } /** - * VIDEO checks + * BANNER checks */ + if (bannerParams) { + let sizes = bannerParams.sizes; + + if (!sizes || parseSizesInput(sizes).length == 0) { + logError('mediaTypes.banner.sizes must be set for banner placement at the right format.'); + return false; + } + } + + /** + * VIDEO checks + */ let areParamsValid = true; - REQUIRED_VIDEO_PARAMS.forEach(function(videoParam) { - if (typeof videoParams[videoParam] === 'undefined') { - logError('mediaTypes.video.' + videoParam + ' must be set for video placement.'); + if (videoParams) { + REQUIRED_VIDEO_PARAMS.forEach(function(videoParam) { + if (typeof videoParams[videoParam] === 'undefined') { + logError('mediaTypes.video.' + videoParam + ' must be set for video placement.'); + areParamsValid = false; + } + }); + + if (parseSizesInput(videoParams.playerSize).length === 0) { + logError('mediaTypes.video.playerSize must be set for video placement at the right format.'); areParamsValid = false; } - }); - - if (parseSizesInput(videoParams.playerSize).length === 0) { - logError('mediaTypes.video.playerSize must be set for video placement at the right format.'); - return false; } /** @@ -132,11 +147,23 @@ export const spec = { bid_id: bidRequest.bidId }; - request.video_params = { - context: deepAccess(bidRequest, 'mediaTypes.video.context'), - playbackmethod: deepAccess(bidRequest, 'mediaTypes.video.playbackmethod'), - size: parseSizesInput(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) - }; + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + let position = deepAccess(bidRequest, 'mediaTypes.banner.pos'); + + request.type = BANNER; + + request.sizes = parseSizesInput(deepAccess(bidRequest, 'mediaTypes.banner.sizes')); + + request.position = position || 0; + } else { + request.type = VIDEO; + + request.video_params = { + context: deepAccess(bidRequest, 'mediaTypes.video.context'), + playbackmethod: deepAccess(bidRequest, 'mediaTypes.video.playbackmethod'), + size: parseSizesInput(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) + }; + } return request; }); @@ -170,16 +197,20 @@ export const spec = { currency: CURRENCY, netRevenue: true, ttl: TTL, - mediaType: 'video', + mediaType: bidResponse.type, meta: {}, // Tracking data nurl: bidResponse.nurl ? bidResponse.nurl : [] }; - if (bidResponse.ad_url) { - bid.vastUrl = bidResponse.ad_url; + if (bidResponse.type == VIDEO) { + if (bidResponse.ad_url) { + bid.vastUrl = bidResponse.ad_url; + } else { + bid.vastXml = bidResponse.ad; + } } else { - bid.vastXml = bidResponse.ad; + bid.ad = bidResponse.ad; } bidResponses.push(bid); diff --git a/test/spec/modules/ViouslyBidAdapter_spec.js b/test/spec/modules/ViouslyBidAdapter_spec.js index 612c5f87138..f8cd686581c 100644 --- a/test/spec/modules/ViouslyBidAdapter_spec.js +++ b/test/spec/modules/ViouslyBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import { deepClone, mergeDeep } from 'src/utils'; +import { BANNER, VIDEO } from 'src/mediaTypes'; import { createEidsArray } from 'modules/userId/eids.js'; import {spec as adapter} from 'modules/viouslyBidAdapter'; @@ -13,6 +14,21 @@ const TTL = 60; const HTTP_METHOD = 'POST'; const REQUEST_URL = 'https://bidder.viously.com/bid'; +const VALID_BID_BANNER = { + bidder: 'viously', + bidId: '5e6f7g8h', + adUnitCode: 'id-5678', + params: { + pid: '123e4567-e89b-12d3-a456-426614174002' + }, + mediaTypes: { + banner: { + sizes: [300, 50], + pos: 1 + } + } +}; + const VALID_BID_VIDEO = { bidder: 'viously', bidId: '5e6f7g8h', @@ -29,6 +45,24 @@ const VALID_BID_VIDEO = { } }; +const VALID_REQUEST_BANNER = { + method: HTTP_METHOD, + url: REQUEST_URL, + data: { + pid: '123e4567-e89b-12d3-a456-426614174002', + currency_code: CURRENCY, + placements: [ + { + id: 'id-5678', + bid_id: '5e6f7g8h', + sizes: ['300x50'], + type: BANNER, + position: 1 + } + ] + } +}; + const VALID_REQUEST_VIDEO = { method: HTTP_METHOD, url: REQUEST_URL, @@ -39,6 +73,7 @@ const VALID_REQUEST_VIDEO = { { id: 'id-5678', bid_id: '5e6f7g8h', + type: VIDEO, video_params: { context: 'instream', playbackmethod: [1, 2, 3, 4], @@ -68,9 +103,30 @@ describe('ViouslyAdapter', function () { describe('isBidRequestValid', function () { describe('Check method return', function () { it('should return true', function () { + expect(adapter.isBidRequestValid(VALID_BID_BANNER)).to.equal(true); expect(adapter.isBidRequestValid(VALID_BID_VIDEO)).to.equal(true); }); + it('should return true for banner with no pos', function () { + let newBid = deepClone(VALID_BID_BANNER); + let newRequest = deepClone(VALID_REQUEST_BANNER); + + delete newBid.mediaTypes.banner.pos; + newRequest.data.placements[0].position = 0; + + expect(adapter.buildRequests([newBid])).to.deep.equal(newRequest); + }); + + it('should return false because the banner size is missing', function () { + let wrongBid = deepClone(VALID_BID_BANNER); + + wrongBid.mediaTypes.banner.sizes = '123456'; + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + + delete wrongBid.mediaTypes.banner.sizes; + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + }); + it('should return false because the pid is missing', function () { let wrongBid = deepClone(VALID_BID_VIDEO); delete wrongBid.params.pid; @@ -89,6 +145,10 @@ describe('ViouslyAdapter', function () { describe('buildRequests', function () { describe('Check method return', function () { + it('should return the right formatted banner requests', function() { + expect(adapter.buildRequests([VALID_BID_BANNER])).to.deep.equal(VALID_REQUEST_BANNER); + }); + it('should return the right formatted video requests', function() { expect(adapter.buildRequests([VALID_BID_VIDEO])).to.deep.equal(VALID_REQUEST_VIDEO); }); @@ -214,7 +274,7 @@ describe('ViouslyAdapter', function () { expect(adapter.buildRequests([bid])).to.deep.equal(requests); }); - it('should return the right formatted request with endpint test', function() { + it('should return the right formatted request with endpoint test', function() { let endpoint = 'https://bid-test.viously.com/prebid'; let bid = mergeDeep(deepClone(VALID_BID_VIDEO), { @@ -259,6 +319,20 @@ describe('ViouslyAdapter', function () { 'win.domain.com' ] }, + { + bid: true, + creative_id: '1357', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-2', + bid_id: '9101112', + cpm: 1.5, + ad: 'html content', + type: 'banner', + size: '300x50', + nurl: [ + 'win.domain2.com', + 'win.domain3.com' + ] + }, { bid: true, creative_id: '1469', @@ -283,6 +357,10 @@ describe('ViouslyAdapter', function () { id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-1', bid_id: '5678' }, + { + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-2', + bid_id: '9101112' + }, { id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-3', bid_id: '2570' @@ -309,6 +387,24 @@ describe('ViouslyAdapter', function () { 'win.domain.com' ] }, + { + requestId: '9101112', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-2', + cpm: 1.5, + width: '300', + height: '50', + creativeId: '1357', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'banner', + meta: {}, + ad: 'html content', + nurl: [ + 'win.domain2.com', + 'win.domain3.com' + ] + }, { requestId: '2570', id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-3', From f0aa7cb119edd98617f5289c2c170805822f1887 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 2 Mar 2023 18:23:28 +0000 Subject: [PATCH 161/375] Prebid 7.39.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66295994170..483a94da2fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.39.0-pre", + "version": "7.39.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index a05a958dd17..ecf339ea085 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.39.0-pre", + "version": "7.39.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 30c61f8f9bcae47f10bb37faec1c364d82059488 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 2 Mar 2023 18:23:29 +0000 Subject: [PATCH 162/375] Increment version to 7.40.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 483a94da2fd..10239bd1959 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.39.0", + "version": "7.40.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index ecf339ea085..65a93076782 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.39.0", + "version": "7.40.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From accf28aabf90d478d7284a82268eea0d6cf0e502 Mon Sep 17 00:00:00 2001 From: aenrel <123280204+aenrel@users.noreply.github.com> Date: Fri, 3 Mar 2023 06:49:51 -0800 Subject: [PATCH 163/375] 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 <> --- modules/relevadRtdProvider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/relevadRtdProvider.md b/modules/relevadRtdProvider.md index c2604619edc..fcbc7a7fb36 100644 --- a/modules/relevadRtdProvider.md +++ b/modules/relevadRtdProvider.md @@ -85,7 +85,7 @@ pbjs.setConfig( | 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 [anna@relevad.com](mailto:anna@relevad.com). +If you do not have your own `partnerid, publisherid, apikey` please reach out to [info@relevad.com](mailto:info@relevad.com). ### Testing From 8112adf6386e5873f68914a026325af276d4371f Mon Sep 17 00:00:00 2001 From: Tachfine Date: Fri, 3 Mar 2023 17:50:40 +0100 Subject: [PATCH 164/375] Remove references to deprecated cookies (#9619) --- modules/criteoBidAdapter.js | 29 ---------------------- test/spec/modules/criteoBidAdapter_spec.js | 19 ++------------ 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index f7ba82d3e06..7f2d6f2a3fb 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -34,9 +34,6 @@ const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag const FAST_BID_PUBKEY_E = 65537; const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDeaWBMxHBUT55CYyboR/EZ4efghPi3CoNGfGWezpjko9P6p2EwGArtHEeS4slhu/SpSIFMjG6fdrpRoNuIAMhq1Z+Pr/+HOd1pThFKeGFr2/NhtAg+TXAzaU='; -const SID_COOKIE_NAME = 'cto_sid'; -const IDCPY_COOKIE_NAME = 'cto_idcpy'; -const LWID_COOKIE_NAME = 'cto_lwid'; const OPTOUT_COOKIE_NAME = 'cto_optout'; const BUNDLE_COOKIE_NAME = 'cto_bundle'; const GUID_RETENTION_TIME_HOUR = 24 * 30 * 13; // 13 months @@ -78,15 +75,12 @@ export const spec = { const jsonHash = { bundle: readFromAllStorages(BUNDLE_COOKIE_NAME), cw: storage.cookiesAreEnabled(), - localWebId: readFromAllStorages(LWID_COOKIE_NAME), lsw: storage.localStorageIsEnabled(), optoutCookie: readFromAllStorages(OPTOUT_COOKIE_NAME), origin: origin, requestId: requestId, - secureIdCookie: readFromAllStorages(SID_COOKIE_NAME), tld: refererInfo.domain, topUrl: refererInfo.domain, - uid: readFromAllStorages(IDCPY_COOKIE_NAME), version: '$prebid.version$'.replace(/\./g, '_'), }; @@ -106,26 +100,13 @@ export const spec = { const response = event.data; if (response.optout) { - deleteFromAllStorages(IDCPY_COOKIE_NAME); - deleteFromAllStorages(SID_COOKIE_NAME); deleteFromAllStorages(BUNDLE_COOKIE_NAME); - deleteFromAllStorages(LWID_COOKIE_NAME); saveOnAllStorages(OPTOUT_COOKIE_NAME, true, OPTOUT_RETENTION_TIME_HOUR); } else { - if (response.uid) { - saveOnAllStorages(IDCPY_COOKIE_NAME, response.uid, GUID_RETENTION_TIME_HOUR); - } - if (response.bundle) { saveOnAllStorages(BUNDLE_COOKIE_NAME, response.bundle, GUID_RETENTION_TIME_HOUR); } - - if (response.removeSid) { - deleteFromAllStorages(SID_COOKIE_NAME); - } else if (response.sid) { - saveOnAllStorages(SID_COOKIE_NAME, response.sid, GUID_RETENTION_TIME_HOUR); - } } }, true); @@ -407,16 +388,6 @@ function buildCdbUrl(context) { url += `&optout=1`; } - const sid = readFromAllStorages(SID_COOKIE_NAME); - if (sid) { - url += `&sid=${sid}`; - } - - const idcpy = readFromAllStorages(IDCPY_COOKIE_NAME); - if (idcpy) { - url += `&idcpy=${idcpy}`; - } - return url; } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index da1a0ca7f32..e958f1d0019 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -7,12 +7,9 @@ import { ADAPTER_VERSION, canFastBid, getFastBidUrl, FAST_BID_VERSION_CURRENT } from 'modules/criteoBidAdapter.js'; -import { createBid } from 'src/bidfactory.js'; -import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; import * as refererDetection from 'src/refererDetection.js'; import { config } from '../../../src/config.js'; -import * as storageManager from 'src/storageManager.js'; import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; describe('The Criteo bidding adapter', function () { @@ -128,19 +125,13 @@ describe('The Criteo bidding adapter', function () { it('forwards ids from cookies', function () { const cookieData = { 'cto_bundle': 'a', - 'cto_sid': 'b', - 'cto_lwid': 'c', - 'cto_idcpy': 'd', - 'cto_optout': 'e' + 'cto_optout': 'b' }; const expectedHashWithCookieData = { ...expectedHash, ...{ bundle: cookieData['cto_bundle'], - localWebId: cookieData['cto_lwid'], - secureIdCookie: cookieData['cto_sid'], - uid: cookieData['cto_idcpy'], optoutCookie: cookieData['cto_optout'] } }; @@ -158,19 +149,13 @@ describe('The Criteo bidding adapter', function () { it('forwards ids from local storage', function () { const localStorageData = { 'cto_bundle': 'a', - 'cto_sid': 'b', - 'cto_lwid': 'c', - 'cto_idcpy': 'd', - 'cto_optout': 'e' + 'cto_optout': 'b' }; const expectedHashWithLocalStorageData = { ...expectedHash, ...{ bundle: localStorageData['cto_bundle'], - localWebId: localStorageData['cto_lwid'], - secureIdCookie: localStorageData['cto_sid'], - uid: localStorageData['cto_idcpy'], optoutCookie: localStorageData['cto_optout'] } }; From 3dade315d1d92ff3db5d56913179056ea38f19e1 Mon Sep 17 00:00:00 2001 From: Catalin Ciocov Date: Fri, 3 Mar 2023 19:29:26 +0200 Subject: [PATCH 165/375] 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> --- modules/improvedigitalBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 94f50094a91..437fcf7d5bb 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -378,7 +378,7 @@ const ID_RAZR = { } }; - const cfgStr = JSON.stringify(cfg).replace(/<\/script>/g, '\\x3C/script>'); + const cfgStr = JSON.stringify(cfg).replace(/<\/script>/ig, '\\x3C/script>'); const s = ``; bid.ad = bid.ad.replace(/]*>/, match => match + s); From 2e105a3029436a61aba2db40393805fc0fdff25b Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Fri, 3 Mar 2023 13:06:39 -0500 Subject: [PATCH 166/375] trigger build From 55523e6bd82f811eaaf02de0650a20724a1bb45c Mon Sep 17 00:00:00 2001 From: bretg Date: Fri, 3 Mar 2023 13:53:14 -0500 Subject: [PATCH 167/375] Rubicon bid adapter: remove pchain support (#9621) * Rubicon adapter - remove pchain support * removing pchain from unit tests --- modules/rubiconBidAdapter.js | 2 -- test/spec/modules/rubiconBidAdapter_spec.js | 13 +------------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 293b0d51b33..f93e3f4e5eb 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -392,7 +392,6 @@ export const spec = { 'tk_flint', 'x_source.tid', 'l_pb_bid_id', - 'x_source.pchain', 'p_screen_res', 'rp_floor', 'rp_secure', @@ -466,7 +465,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), diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 034536d4973..592fdbc6f9c 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -319,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: { @@ -430,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', @@ -598,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]); From 7ff49b1bfc3758c51cd8780968f8b39bf0661db2 Mon Sep 17 00:00:00 2001 From: Andy Rusiecki Date: Sat, 4 Mar 2023 21:35:55 -0500 Subject: [PATCH 168/375] kargo adapter - adding prebid version to requests (#9620) --- modules/kargoBidAdapter.js | 4 +++- test/spec/modules/kargoBidAdapter_spec.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) 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/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 525c00d655c..556e06268b1 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -325,6 +325,7 @@ describe('kargo adapter tests', function () { usp: '1---' }, pageURL: 'https://www.prebid.org', + prebidVersion: '$prebid.version$', prebidRawBidRequests: [ { bidId: 1, From f67f76204d546b0102ef8788b1eaac3dd879f4f2 Mon Sep 17 00:00:00 2001 From: Justas Pupelis Date: Sun, 5 Mar 2023 16:01:50 +0200 Subject: [PATCH 169/375] Updated adf adapter to support native with type; use ortb request for natives (#9616) Co-authored-by: Justas Pupelis --- modules/adfBidAdapter.js | 113 ++----- test/spec/modules/adfBidAdapter_spec.js | 413 +++++++++++++++++------- 2 files changed, 319 insertions(+), 207 deletions(-) diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index d8e598deb8f..82bd7f03ff0 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -3,10 +3,9 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {_map, deepAccess, deepSetValue, mergeDeep, parseSizesInput} from '../src/utils.js'; +import {deepAccess, deepSetValue, mergeDeep, parseSizesInput, deepClone} from '../src/utils.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const { getConfig } = config; @@ -16,38 +15,7 @@ const BIDDER_ALIAS = [ { code: 'adformOpenRTB', gvlid: GVLID }, { code: 'adform', gvlid: GVLID } ]; -const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; -const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - icon: { - id: 2, - type: 1, - name: 'img' - }, - image: { - id: 3, - type: 3, - name: 'img' - }, - sponsoredBy: { - id: 5, - name: 'data', - type: 1 - }, - body: { - id: 4, - name: 'data', - type: 2 - }, - cta: { - id: 1, - type: 12, - name: 'data' - } -}; + const OUTSTREAM_RENDERER_URL = 'https://s2.adform.net/banners/scripts/video/outstream/render.js'; export const spec = { @@ -61,9 +29,6 @@ export const spec = { return !!(mid || (inv && mname)); }, buildRequests: (validBidRequests, bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - let app, site; const commonFpd = bidderRequest.ortb2 || {}; @@ -123,45 +88,28 @@ export const spec = { } }; - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1, - }; - if (props) { - asset.id = props.id; - let wmin, hmin, w, h; - let aRatios = bidParams.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + if (bid.nativeOrtbRequest && bid.nativeOrtbRequest.assets) { + let assets = bid.nativeOrtbRequest.assets; + let requestAssets = []; + for (let i = 0; i < assets.length; i++) { + let asset = deepClone(assets[i]); + let img = asset.img; + if (img) { + let aspectratios = img.ext && img.ext.aspectratios; + + if (aspectratios) { + let ratioWidth = parseInt(aspectratios[0].split(':')[0], 10); + let ratioHeight = parseInt(aspectratios[0].split(':')[1], 10); + img.wmin = img.wmin || 0; + img.hmin = ratioHeight * img.wmin / ratioWidth | 0; + } } - - if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; - } - - asset[props.name] = { - len: bidParams.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; + requestAssets.push(asset); } - }).filter(Boolean); - if (assets.length) { imp.native = { request: { - assets + assets: requestAssets } }; } @@ -268,7 +216,9 @@ export const spec = { }; if (bidResponse.native) { - result.native = parseNative(bidResponse); + result.native = { + ortb: bidResponse.native + }; } else { result[ mediaType === VIDEO ? 'vastXml' : 'ad' ] = bidResponse.adm; } @@ -286,25 +236,6 @@ export const spec = { registerBidder(spec); -function parseNative(bid) { - const { assets, link, imptrackers, jstracker } = bid.native; - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined, - impressionTrackers: imptrackers || undefined, - javascriptTrackers: jstracker ? [ jstracker ] : undefined - }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; - } - }); - - return result; -} - function setOnAny(collection, key) { for (let i = 0, result; i < collection.length; i++) { result = deepAccess(collection[i], key); diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index eb560bb1bae..574f559e994 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -481,6 +481,17 @@ describe('Adf adapter', function () { nativeParams: { title: { required: true, len: 140 } }, + nativeOrtbRequest: { + assets: [ + { + required: 1, + id: 0, + title: { + len: 140 + } + } + ] + }, mediaTypes: { banner: { sizes: [[100, 100], [200, 300]] @@ -547,6 +558,57 @@ describe('Adf adapter', function () { describe('native', function () { describe('assets', function () { + it('should use nativeOrtbRequest instead of nativeParams or mediaTypes', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { mid: 1000 }, + nativeParams: { + title: { required: true, len: 200 }, + image: { required: true, sizes: [150, 150] }, + icon: { required: false, sizes: [150, 150] }, + body: { required: false, len: 1140 }, + sponsoredBy: { required: true }, + cta: { required: false }, + clickUrl: { required: false }, + ortb: { + ver: '1.2', + assets: [] + } + }, + mediaTypes: { + native: { + title: { required: true, len: 140 }, + image: { required: true, sizes: [150, 50] }, + icon: { required: false, sizes: [50, 50] }, + body: { required: false, len: 140 }, + sponsoredBy: { required: true }, + cta: { required: false }, + clickUrl: { required: false } + } + }, + nativeOrtbRequest: { + assets: [ + { required: 1, title: { len: 200 } }, + { required: 1, img: { type: 3, w: 170, h: 70 } }, + { required: 0, img: { type: 1, w: 70, h: 70 } }, + { required: 0, data: { type: 2, len: 150 } }, + { required: 1, data: { type: 1 } }, + { required: 0, data: { type: 12 } }, + ] + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; + assert.ok(assets[0].title); + assert.equal(assets[0].title.len, 200); + assert.deepEqual(assets[1].img, { type: 3, w: 170, h: 70 }); + assert.deepEqual(assets[2].img, { type: 1, w: 70, h: 70 }); + assert.deepEqual(assets[3].data, { type: 2, len: 150 }); + assert.deepEqual(assets[4].data, { type: 1 }); + assert.deepEqual(assets[5].data, { type: 12 }); + assert.ok(!assets[6]); + }); + it('should set correct asset id', function () { let validBidRequests = [{ bidId: 'bidId', @@ -555,14 +617,46 @@ describe('Adf adapter', function () { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, body: { len: 140 } + }, + nativeOrtbRequest: { + assets: [ + { + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 0, + img: { + type: 3, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: [ 'image/jpg', 'image/gif' ] + } + }, + { + id: 2, + data: { + type: 2, + len: 140 + } + } + ] } }]; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.equal(assets[0].id, 0); - assert.equal(assets[1].id, 3); - assert.equal(assets[2].id, 4); + assert.equal(assets[1].id, 1); + assert.equal(assets[2].id, 2); }); + it('should add required key if it is necessary', function () { let validBidRequests = [{ bidId: 'bidId', @@ -572,9 +666,16 @@ describe('Adf adapter', function () { image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, body: { len: 140 }, sponsoredBy: { required: true, len: 140 } + }, + nativeOrtbRequest: { + assets: [ + { required: 1, title: { len: 140 } }, + { required: 0, img: { type: 3, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] } }, + { data: { type: 2, len: 140 } }, + { required: 1, data: { type: 1, len: 140 } } + ] } }]; - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.equal(assets[0].required, 1); @@ -593,8 +694,17 @@ describe('Adf adapter', function () { icon: { required: false, sizes: [50, 50] }, body: { required: false, len: 140 }, sponsoredBy: { required: true }, - cta: { required: false }, - clickUrl: { required: false } + cta: { required: false } + }, + nativeOrtbRequest: { + assets: [ + { required: 1, title: { len: 140 } }, + { required: 1, img: { type: 3, w: 150, h: 50 } }, + { required: 0, img: { type: 1, w: 50, h: 50 } }, + { required: 0, data: { type: 2, len: 140 } }, + { required: 1, data: { type: 1 } }, + { required: 0, data: { type: 12 } }, + ] } }]; @@ -609,25 +719,6 @@ describe('Adf adapter', function () { assert.ok(!assets[6]); }); - describe('icon/image sizing', function () { - it('should flatten sizes and utilise first pair', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: { mid: 1000 }, - nativeParams: { - image: { - sizes: [[200, 300], [100, 200]] - }, - } - }]; - - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; - assert.ok(assets[0].img); - assert.equal(assets[0].img.w, 200); - assert.equal(assets[0].img.h, 300); - }); - }); - it('should utilise aspect_ratios', function () { const validBidRequests = [{ bidId: 'bidId', @@ -647,6 +738,12 @@ describe('Adf adapter', function () { ratio_width: 2 }] } + }, + nativeOrtbRequest: { + assets: [ + { img: { type: 3, wmin: 100, ext: { aspectratios: ['1:3'] } } }, + { img: { type: 1, wmin: 10, ext: { aspectratios: ['2:5'] } } } + ] } }]; @@ -671,6 +768,14 @@ describe('Adf adapter', function () { icon: { aspect_ratios: [] } + }, + nativeOrtbRequest: { + request: { + assets: [ + { img: {} }, + { img: {} } + ] + } } }]; @@ -689,13 +794,18 @@ describe('Adf adapter', function () { ratio_width: 1 }] } + }, + nativeOrtbRequest: { + assets: [ + { img: { type: 3, ext: { aspectratios: ['3:1'] } } } + ] } }]; let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.ok(assets[0].img); - assert.equal(assets[0].img.wmin, 0); - assert.equal(assets[0].img.hmin, 0); + assert.ok(!assets[0].img.wmin); + assert.ok(!assets[0].img.hmin); assert.ok(!assets[1]); }); }); @@ -717,7 +827,7 @@ describe('Adf adapter', function () { let serverResponse = { body: { seatbid: [{ - bid: [{impid: '1', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}}] + bid: [{impid: '1', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 0, title: {text: 'Asset title text'}}]}}] }, { bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 1, data: {value: 'Asset title text'}}]}}] }] @@ -729,19 +839,23 @@ describe('Adf adapter', function () { { bidId: 'bidId1', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId2', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } } ] @@ -756,11 +870,11 @@ describe('Adf adapter', function () { body: { seatbid: [{ bid: [ - {impid: '1', native: {ver: '1.1', link: { url: 'link1' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}}, + {impid: '1', native: {ver: '1.1', link: { url: 'link1' }, assets: [{id: 0, title: {text: 'Asset title text'}}]}}, {impid: '4', native: {ver: '1.1', link: { url: 'link4' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}} ] }, { - bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link2' }, assets: [{id: 1, data: {value: 'Asset title text'}}]}}] + bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link2' }, assets: [{id: 0, data: {value: 'Asset title text'}}]}}] }] } }; @@ -770,45 +884,53 @@ describe('Adf adapter', function () { { bidId: 'bidId1', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId2', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId3', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId4', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } } ] }; bids = spec.interpretResponse(serverResponse, bidRequest).map(bid => { - const { requestId, native: { clickUrl } } = bid; - return [ requestId, clickUrl ]; + const { requestId, native: { ortb: { link: { url } } } } = bid; + return [ requestId, url ]; }); assert.equal(bids.length, 3); @@ -850,6 +972,34 @@ describe('Adf adapter', function () { { bidId: 'bidId1', params: { mid: 1000 }, + nativeOrtbRequest: { + assets: [ + { + id: 0, + required: 1, + title: { + len: 140 + } + }, { + id: 1, + required: 1, + img: { + type: 3, + wmin: 836, + hmin: 627, + ext: { + aspectratios: ['6:5'] + } + } + }, { + id: 2, + required: 0, + data: { + type: 2 + } + } + ] + }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -876,56 +1026,45 @@ describe('Adf adapter', function () { const bid = [ { impid: '1', - price: 93.1231, - crid: '12312312', native: { - assets: [ - { - data: null, - id: 0, - img: null, - required: 0, - title: {text: 'title', len: null}, - video: null - }, { - data: null, - id: 2, - img: {type: null, url: 'test.url.com/Files/58345/308185.jpg?bv=1', w: 30, h: 10}, - required: 0, - title: null, - video: null - }, { - data: null, - id: 3, - img: {type: null, url: 'test.url.com/Files/58345/308200.jpg?bv=1', w: 100, h: 100}, - required: 0, - title: null, - video: null - }, { - data: {type: null, len: null, value: 'body'}, - id: 4, - img: null, - required: 0, - title: null, - video: null - }, { - data: {type: null, len: null, value: 'cta'}, - id: 1, - img: null, - required: 0, - title: null, - video: null - }, { - data: {type: null, len: null, value: 'sponsoredBy'}, - id: 5, - img: null, - required: 0, - title: null, - video: null + ver: '1.1', + assets: [{ + id: 1, + required: 0, + title: { + text: 'FLS Native' + } + }, { + id: 3, + required: 0, + data: { + value: 'Adform' } - ], - link: { url: 'clickUrl', clicktrackers: ['clickTracker1', 'clickTracker2'] }, - imptrackers: ['imptrackers url1', 'imptrackers url2'], + }, { + id: 2, + required: 0, + data: { + value: 'Native banner. WOW.' + } + }, { + id: 4, + required: 0, + data: { + value: 'Oho' + } + }, { + id: 5, + required: 0, + img: { url: 'test.url.com/Files/58345/308185.jpg?bv=1', w: 30, h: 10 } + }, { + id: 0, + required: 0, + img: { url: 'test.url.com/Files/58345/308200.jpg?bv=1', w: 300, h: 300 } + }], + link: { + url: 'clickUrl', clicktrackers: [ 'clickTracker1', 'clickTracker2' ] + }, + imptrackers: ['imptracker url1', 'imptracker url2'], jstracker: 'jstracker' } } @@ -940,24 +1079,66 @@ describe('Adf adapter', function () { }; let bidRequest = { data: {}, - bids: [{ bidId: 'bidId1' }] + bids: [{ + bidId: 'bidId1', + nativeOrtbRequest: { + ver: '1.2', + assets: [{ + id: 0, + required: 1, + img: { + type: 3, + wmin: 200, + hmin: 166, + ext: { + aspectratios: ['6:5'] + } + } + }, { + id: 1, + required: 1, + title: { + len: 150 + } + }, { + id: 2, + required: 0, + data: { + type: 2 + } + }, { + id: 3, + required: 1, + data: { + type: 1 + } + }, { + id: 4, + required: 1, + data: { + type: 12 + } + }, { + id: 5, + required: 0, + img: { + type: 1, + wmin: 10, + hmin: 10, + ext: { + aspectratios: ['1:1'] + } + } + }] + }, + }] }; const result = spec.interpretResponse(serverResponse, bidRequest)[0].native; const native = bid[0].native; const assets = native.assets; - assert.deepEqual({ - clickUrl: native.link.url, - clickTrackers: native.link.clicktrackers, - impressionTrackers: native.imptrackers, - javascriptTrackers: [ native.jstracker ], - title: assets[0].title.text, - icon: {url: assets[1].img.url, width: assets[1].img.w, height: assets[1].img.h}, - image: {url: assets[2].img.url, width: assets[2].img.w, height: assets[2].img.h}, - body: assets[3].data.value, - cta: assets[4].data.value, - sponsoredBy: assets[5].data.value - }, result); + + assert.deepEqual(result, {ortb: native}); }); it('should return empty when there is no bids in response', function () { const serverResponse = { From e659e290b28b49ae1195b7eae2f0724d1e5eccf0 Mon Sep 17 00:00:00 2001 From: rishko00 <43280707+rishko00@users.noreply.github.com> Date: Mon, 6 Mar 2023 17:05:26 +0200 Subject: [PATCH 170/375] smallfix on response validation (#9623) Co-authored-by: Vasyl Rishko --- modules/smartyadsBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 9140dc41dd87d84eca93f8321f08e9179bf2f59c Mon Sep 17 00:00:00 2001 From: Abhijit Mane <54662130+lm-abhijit@users.noreply.github.com> Date: Mon, 6 Mar 2023 21:18:04 +0530 Subject: [PATCH 171/375] 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 --- integrationExamples/gpt/lemma_sample.html | 129 ++++ modules/lemmaDigitalBidAdapter.js | 570 ++++++++++++++++ modules/lemmaDigitalBidAdapter.md | 61 ++ .../modules/lemmaDigitalBidAdapter_spec.js | 623 ++++++++++++++++++ 4 files changed, 1383 insertions(+) create mode 100755 integrationExamples/gpt/lemma_sample.html create mode 100644 modules/lemmaDigitalBidAdapter.js create mode 100644 modules/lemmaDigitalBidAdapter.md create mode 100644 test/spec/modules/lemmaDigitalBidAdapter_spec.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/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js new file mode 100644 index 00000000000..35295051e43 --- /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 = spec._getSizes(videoData) || []; + 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/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); + }); + }); + }); +}); From fdf036c55522be2455589a5f50ad2f91ec034fc6 Mon Sep 17 00:00:00 2001 From: Love Sharma Date: Mon, 6 Mar 2023 13:52:16 -0500 Subject: [PATCH 172/375] read video size from playerSize (#9625) Co-authored-by: Love Sharma --- modules/lemmaDigitalBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js index 35295051e43..9fa3081a47e 100644 --- a/modules/lemmaDigitalBidAdapter.js +++ b/modules/lemmaDigitalBidAdapter.js @@ -538,7 +538,7 @@ export var spec = { 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 = spec._getSizes(videoData) || []; + var sizes = bid.mediaTypes.video ? bid.mediaTypes.video.playerSize : [] if (sizes && sizes.length > 0) { vObj = {}; if (videoData) { From 7a02f2e3d3b19396b7f35a4fe90575da5024d254 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Tue, 7 Mar 2023 15:27:24 +0100 Subject: [PATCH 173/375] 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 --- modules/gridBidAdapter.js | 55 ++-------------------- modules/gridNMBidAdapter.js | 28 +---------- test/spec/modules/gridBidAdapter_spec.js | 18 +------ test/spec/modules/gridNMBidAdapter_spec.js | 8 ---- 4 files changed, 8 insertions(+), 101 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index ce7fcc680dd..f92f64af039 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -83,7 +83,6 @@ export const spec = { return null; } let pageKeywords = null; - let jwpseg = null; let content = null; let schain = null; let userIdAsEids = null; @@ -128,16 +127,11 @@ export const spec = { endpoint = ALIAS_CONFIG[bid.bidder] && ALIAS_CONFIG[bid.bidder].endpoint; } const { params: { uid, keywords, forceBidder, multiRequest }, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; - const { pubdata, secid, pubid, source, content: bidParamsContent } = bid.params; + const { secid, pubid, source, content: bidParamsContent } = bid.params; const bidFloor = _getFloor(mediaTypes || {}, bid); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; - if (jwTargeting) { - if (!jwpseg && jwTargeting.segments) { - jwpseg = jwTargeting.segments; - } - if (!content && jwTargeting.content) { - content = jwTargeting.content; - } + if (jwTargeting && !content && jwTargeting.content) { + content = jwTargeting.content; } let impObj = { @@ -210,23 +204,12 @@ export const spec = { request.site.publisher = { id: pubid }; } - const reqJwpseg = (pubdata && pubdata.jwpseg) || (jwTargeting && jwTargeting.segments); - const siteContent = bidParamsContent || (jwTargeting && jwTargeting.content); if (siteContent) { request.site.content = siteContent; } - if (reqJwpseg && reqJwpseg.length) { - request.user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(reqJwpseg, 'jwpseg'), - }] - }; - } - requests.push(request); sources.push(source); bidsArray.push(bidObject); @@ -274,28 +257,16 @@ export const spec = { mainRequest.site.content = content; } - if (jwpseg && jwpseg.length) { - mainRequest.user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(jwpseg, 'jwpseg'), - }] - }; - } - [...requests, mainRequest].forEach((request) => { if (!request) { return; } - if (request.user) user = request.user; + user = null; const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); if (ortb2UserData && ortb2UserData.length) { - if (!user) user = { data: [] }; - user = mergeDeep(user, { - data: [...ortb2UserData] - }); + user = { data: [...ortb2UserData] }; } if (gdprConsent && gdprConsent.consentString) { @@ -650,22 +621,6 @@ function getUserIdFromFPDStorage() { return storage.getDataFromLocalStorage(USER_ID_KEY) || makeNewUserIdInFPDStorage(); } -function segmentProcessing(segment, forceSegName) { - return segment - .map((seg) => { - const value = seg && (seg.id || seg); - if (typeof value === 'string' || typeof value === 'number') { - return { - value: value.toString(), - ...(forceSegName && { name: forceSegName }), - ...(seg.name && { name: seg.name }), - }; - } - return null; - }) - .filter((seg) => !!seg); -} - function reformatKeywords(pageKeywords) { const formatedPageKeywords = {}; Object.keys(pageKeywords).forEach((name) => { diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index c49b7619c07..41689aeeb55 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -90,13 +90,12 @@ export const spec = { auctionId = bid.auctionId; } const { - params: { floorcpm, pubdata, source, secid, pubid, content, video }, + params: { floorcpm, source, secid, pubid, content, video }, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp, sizes } = bid; const bidFloor = _getFloor(mediaTypes || {}, bid, isNumber(floorcpm) && floorcpm); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; - const jwpseg = (pubdata && pubdata.jwpseg) || (jwTargeting && jwTargeting.segments); const siteContent = content || (jwTargeting && jwTargeting.content); @@ -156,15 +155,6 @@ export const spec = { request.site.content = siteContent; } - if (jwpseg && jwpseg.length) { - user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(jwpseg, 'jwpseg'), - }] - }; - } - if (gdprConsent && gdprConsent.consentString) { userExt = { consent: gdprConsent.consentString }; } @@ -429,20 +419,4 @@ export function getSyncUrl() { return SYNC_URL; } -function segmentProcessing(segment, forceSegName) { - return segment - .map((seg) => { - const value = seg && (seg.id || seg); - if (typeof value === 'string' || typeof value === 'number') { - return { - value: value.toString(), - ...(forceSegName && { name: forceSegName }), - ...(seg.name && { name: seg.name }), - }; - } - return null; - }) - .filter((seg) => !!seg); -} - registerBidder(spec); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index d411f33ab50..84616ae7f97 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -667,14 +667,6 @@ describe('TheMediaGrid Adapter', function () { const [request] = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); - expect(payload).to.have.property('user'); - expect(payload.user.data).to.deep.equal([{ - name: 'iow_labs_pub_data', - segment: [ - {name: 'jwpseg', value: jsSegments[0]}, - {name: 'jwpseg', value: jsSegments[1]} - ] - }]); expect(payload).to.have.property('site'); expect(payload.site.content).to.deep.equal(jsContent); }); @@ -793,7 +785,7 @@ describe('TheMediaGrid Adapter', function () { expect(payload.site.content.data).to.deep.equal(contentData); }); - it('should have right value in user.data when jwpsegments are present', function () { + it('should have right value in user.data', function () { const userData = [ { name: 'someName', @@ -823,13 +815,7 @@ describe('TheMediaGrid Adapter', function () { }); const [request] = spec.buildRequests([bidRequestsWithJwTargeting], {...bidderRequest, ortb2}); const payload = parseRequest(request.data); - expect(payload.user.data).to.deep.equal([{ - name: 'iow_labs_pub_data', - segment: [ - {name: 'jwpseg', value: jsSegments[0]}, - {name: 'jwpseg', value: jsSegments[1]} - ] - }, ...userData]); + expect(payload.user.data).to.deep.equal(userData); }); it('should have site.content.id filled from config ortb2.site.content.id', function () { diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index b400ef3394d..e4f06a451d2 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -245,14 +245,6 @@ describe('TheMediaGridNM Adapter', function () { requests.forEach((req, i) => { const payload = req.data; expect(req).to.have.property('data'); - expect(payload).to.have.property('user'); - expect(payload.user.data).to.deep.equal([{ - name: 'iow_labs_pub_data', - segment: [ - {name: 'jwpseg', value: jsSegments[0]}, - {name: 'jwpseg', value: jsSegments[1]} - ] - }]); expect(payload).to.have.property('site'); expect(payload.site.content).to.deep.equal(jsContent); }); From 9ad7ef9f44e71e8c570e4046c109a6a2d46745ec Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 7 Mar 2023 07:55:30 -0800 Subject: [PATCH 174/375] 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 --- src/utils/promise.js | 99 +++++++++++++++++----------- test/spec/unit/utils/promise_spec.js | 90 +------------------------ 2 files changed, 61 insertions(+), 128 deletions(-) diff --git a/src/utils/promise.js b/src/utils/promise.js index 69e40791ab1..0cf0a47eb8e 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -1,15 +1,12 @@ -import {getGlobal} from '../prebidGlobal.js'; - const SUCCESS = 0; const FAIL = 1; /** * A version of Promise that runs callbacks synchronously when it can (i.e. after it's been fulfilled or rejected). */ -export class GreedyPromise extends (getGlobal().Promise || Promise) { +export class GreedyPromise { #result; #callbacks; - #parent = null; /** * Convenience wrapper for setTimeout; takes care of returning an already fulfilled GreedyPromise when the delay is zero. @@ -24,53 +21,33 @@ export class GreedyPromise extends (getGlobal().Promise || Promise) { } constructor(resolver) { + if (typeof resolver !== 'function') { + throw new Error('resolver not a function'); + } const result = []; const callbacks = []; - function handler(type, resolveFn) { + let [resolve, reject] = [SUCCESS, FAIL].map((type) => { return function (value) { - if (!result.length) { + if (type === SUCCESS && typeof value?.then === 'function') { + value.then(resolve, reject); + } else if (!result.length) { result.push(type, value); while (callbacks.length) callbacks.shift()(); - resolveFn(value); } } + }); + try { + resolver(resolve, reject); + } catch (e) { + reject(e); } - super( - typeof resolver !== 'function' - ? resolver // let super throw an error - : (resolve, reject) => { - const rejectHandler = handler(FAIL, reject); - const resolveHandler = (() => { - const done = handler(SUCCESS, resolve); - return value => - typeof value?.then === 'function' ? value.then(done, rejectHandler) : done(value); - })(); - try { - resolver(resolveHandler, rejectHandler); - } catch (e) { - rejectHandler(e); - } - } - ); this.#result = result; this.#callbacks = callbacks; } + then(onSuccess, onError) { - if (typeof onError === 'function') { - // if an error handler is provided, attach a dummy error handler to super, - // and do the same for all promises without an error handler that precede this one in a chain. - // This is to avoid unhandled rejection events / warnings for errors that were, in fact, handled; - // since we are not using super's callback mechanisms we need to make it aware of this separately. - let node = this; - while (node) { - super.then.call(node, null, () => null); - const next = node.#parent; - node.#parent = null; // since we attached a handler already, we are no longer interested in what will happen later in the chain - node = next; - } - } const result = this.#result; - const res = new GreedyPromise((resolve, reject) => { + return new this.constructor((resolve, reject) => { const continuation = () => { let value = result[1]; let [handler, resolveFn] = result[0] === SUCCESS ? [onSuccess, resolve] : [onError, reject]; @@ -87,8 +64,50 @@ export class GreedyPromise extends (getGlobal().Promise || Promise) { } result.length ? continuation() : this.#callbacks.push(continuation); }); - res.#parent = this; - return res; + } + + catch(onError) { + return this.then(null, onError); + } + + finally(onFinally) { + let val; + return this.then( + (v) => { val = v; return onFinally(); }, + (e) => { val = this.constructor.reject(e); return onFinally() } + ).then(() => val); + } + + static #collect(promises, collector, done) { + let cnt = promises.length; + function clt() { + collector.apply(this, arguments); + if (--cnt <= 0 && done) done(); + } + promises.length === 0 && done ? done() : promises.forEach((p, i) => this.resolve(p).then( + (val) => clt(true, val, i), + (err) => clt(false, err, i) + )); + } + + static race(promises) { + return new this((resolve, reject) => { + this.#collect(promises, (success, result) => success ? resolve(result) : reject(result)); + }) + } + + static all(promises) { + return new this((resolve, reject) => { + let res = []; + this.#collect(promises, (success, val, i) => success ? res[i] = val : reject(val), () => resolve(res)); + }) + } + + static allSettled(promises) { + return new this((resolve) => { + let res = []; + this.#collect(promises, (success, val, i) => res[i] = success ? {status: 'fulfilled', value: val} : {status: 'rejected', reason: val}, () => resolve(res)) + }) } static resolve(value) { 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)]), From 2931600ff64dd21364c8ecbae10880ce75808ae9 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Wed, 8 Mar 2023 15:29:26 +0100 Subject: [PATCH 175/375] Nexx360 Bid Adapter: native support added and ortbConverter usage (#9626) * native added and ortb converter usage * Update nexx360BidAdapter.js --- modules/nexx360BidAdapter.js | 444 +++++++++----------- test/spec/modules/nexx360BidAdapter_spec.js | 353 ++++++++++------ 2 files changed, 443 insertions(+), 354 deletions(-) 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/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'); From 130efebc1828dab5b0bb956d1bacba5907d4d6d3 Mon Sep 17 00:00:00 2001 From: John Ivan Bauzon Date: Wed, 8 Mar 2023 06:47:11 -0800 Subject: [PATCH 176/375] ADJS-1271-send-envelope-param-for-lexicon (#9634) Co-authored-by: John Bauzon --- modules/gumgumBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index b12aa3ee861..2cdafd26987 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -264,7 +264,8 @@ function getEids(userId) { const idProperties = [ 'uid', 'eid', - 'lipbid' + 'lipbid', + 'envelope' ]; return Object.keys(userId).reduce(function (eids, provider) { From 69ec909d9f0637d54e2fdf2bdf64e4adddc30aef Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 8 Mar 2023 16:57:16 +0200 Subject: [PATCH 177/375] 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 --- modules/vidazooBidAdapter.js | 8 +++- test/spec/modules/vidazooBidAdapter_spec.js | 50 +++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 3f3bb66d8ae..44538e30921 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; import {bidderSettings} from '../src/bidderSettings.js'; -import { config } from '../src/config.js'; +import {config} from '../src/config.js'; const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; @@ -138,6 +138,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/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, From fd1b786ae9e8120711da7eb5033dcd28f86d4132 Mon Sep 17 00:00:00 2001 From: Antonio Gargaro <38767071+AntonioGargaro@users.noreply.github.com> Date: Wed, 8 Mar 2023 17:13:50 +0000 Subject: [PATCH 178/375] 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 --- modules/permutiveRtdProvider.js | 140 +++++--- .../spec/modules/permutiveRtdProvider_spec.js | 323 +++++++++--------- 2 files changed, 247 insertions(+), 216 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 305e175bf2c..8af963f37dc 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,37 @@ 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) { + const customCohortsData = deepAccess(segmentData, bidder) || [] + const name = 'permutive.com' const permutiveUserData = { @@ -174,6 +161,19 @@ function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformation const updatedUserKeywords = (currentUserKeywords === '') ? keywords : `${currentUserKeywords},${keywords}` deepSetValue(ortbConfig, 'ortb2.user.keywords', updatedUserKeywords) + // 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(`Updating ortb2 config for ${bidder}`, ortbConfig) + return ortbConfig } @@ -262,17 +262,6 @@ function getDefaultBidderFn (bidder) { 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 +272,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 } /** @@ -383,17 +377,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/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 5030e662ea9..207771731a7 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -2,12 +2,12 @@ import { permutiveSubmodule, storage, getSegments, - initSegments, isAcEnabled, isPermutiveOnPage, setBidderRtb, getModuleConfig, PERMUTIVE_SUBMODULE_CONFIG_KEY, + readAndSetCohorts, } from 'modules/permutiveRtdProvider.js' import { deepAccess, deepSetValue, mergeDeep } from '../../../src/utils.js' import { config } from 'src/config.js' @@ -188,11 +188,12 @@ 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([{ @@ -205,7 +206,8 @@ 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 } }) @@ -225,7 +227,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 +246,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 +271,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 +281,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,10 +307,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) @@ -316,7 +316,8 @@ describe('permutiveRtdProvider', function () { }) }) it('should merge ortb2 correctly for ac and ssps', function () { - setLocalStorage({ + const customTargetingData = { + ...getTargetingData(), '_ppam': [], '_psegs': [], '_pcrprs': ['abc', 'def', 'xyz'], @@ -324,7 +325,10 @@ describe('permutiveRtdProvider', function () { ssps: ['foo', 'bar'], cohorts: ['xyz', 'uvw'], } - }) + } + const segmentsData = transformedTargeting(customTargetingData) + setLocalStorage(customTargetingData) + const moduleConfig = { name: 'permutive', waitForIt: true, @@ -335,7 +339,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,6 +374,99 @@ describe('permutiveRtdProvider', function () { segment: expectedOtherTargetingData }]) }) + + 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() + + const bidderConfig = {} + + // Passing empty values means there is no segment data + const segmentsData = transformedTargeting({ + _pdfps: [], + _prubicons: [], + _papns: [], + _psegs: [], + _ppam: [], + _pcrprs: [], + _pssps: { ssps: [], cohorts: [] } + }) + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach(bidder => { + expect(bidderConfig[bidder].user).to.not.have.property('ext') + }) + }) + + it('should add standard and custom cohorts', function () { + const moduleConfig = getConfig() + + const bidderConfig = {} + + const segmentsData = transformedTargeting() + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach(bidder => { + const userExtData = { + // Default targeting + p_standard: segmentsData.ac, + } + + const customCohorts = segmentsData[bidder] || [] + if (customCohorts.length > 0) { + deepSetValue(userExtData, 'permutive', customCohorts) + } + + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq(userExtData) + }) + }) + + it('should add ac cohorts ONLY', function () { + const moduleConfig = getConfig() + + const bidderConfig = {} + + const segmentsData = transformedTargeting() + moduleConfig.params.acBidders.forEach((bidder) => { + // Remove custom cohorts + delete segmentsData[bidder] + }) + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach((bidder) => { + expect(bidderConfig[bidder].user.ext.data).to.deep.equal({ + p_standard: segmentsData.ac + }) + }) + }) + + it('should add custom cohorts ONLY', function () { + const moduleConfig = getConfig() + + const bidderConfig = {} + + const segmentsData = transformedTargeting() + // Empty the AC cohorts + segmentsData['ac'] = [] + + 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('Getting segments', function () { @@ -397,72 +494,18 @@ 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.permutive')).to.eql(data.appnexus) - expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - } - }) - - it('sets segment targeting for Magnite', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() - - initSegments({ adUnits }, callback, config) - - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.rubicon) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - } - }) - - it('sets segment targeting for Magnite video', function () { - const targetingData = getTargetingData() - targetingData._prubicons.push(321) - - setLocalStorage(targetingData) - - const data = transformedTargeting(targetingData) - const config = getConfig() - - const adUnits = getAdUnits().filter(adUnit => adUnit.mediaTypes.video) - expect(adUnits).to.have.lengthOf(1) - - initSegments({ adUnits }, callback, config) - - function callback() { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - if (bidder === 'rubicon') { - expect( - deepAccess(params, 'visitor.permutive'), - 'Should map all targeting values to a string', - ).to.eql(data.rubicon.map(String)) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + 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)) + } }) - } + }) }) it('sets segment targeting for Ozone', function () { @@ -470,53 +513,17 @@ describe('permutiveRtdProvider', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) - - function callback () { - 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)) - } - }) - }) - } - }) - }) + readAndSetCohorts({ adUnits }, config) - describe('Custom segment targeting', function () { - it('sets custom segment targeting for Magnite', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - 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) + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) } - } - } - - initSegments({ adUnits }, callback, config) - - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.gam) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) }) - } + }) }) }) @@ -525,73 +532,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']) + } }) - } + }) }) }) From f61e11de310e2c2961123813d6881e2b610eaf7c Mon Sep 17 00:00:00 2001 From: BaronJHYu <254878848@qq.com> Date: Thu, 9 Mar 2023 01:50:26 +0800 Subject: [PATCH 179/375] 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 --- modules/discoveryBidAdapter.js | 7 +++++- modules/mediagoBidAdapter.js | 39 ++++++---------------------------- 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 8145c9d25e7..7930efc4fa8 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -244,7 +244,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 }; } ret = { id: id, @@ -253,6 +255,7 @@ function getItems(validBidRequests, bidderRequest) { h: matchSize.h, w: matchSize.w, pos: 1, + format: sizes, }, ext: {}, tagid: globals['tagid'], @@ -294,6 +297,7 @@ function getParam(validBidRequests, bidderRequest) { const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); const page = utils.deepAccess(bidderRequest, 'refererInfo.page'); const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); + const firstPartyData = bidderRequest.ortb2; if (items && items.length) { let c = { @@ -312,6 +316,7 @@ function getParam(validBidRequests, bidderRequest) { }, ext: { eids, + firstPartyData, }, user: { buyeruid: getUserID(), 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(), From 7785060f03cbf410eae25bc7199fcdc546e7d8cf Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Thu, 9 Mar 2023 15:35:26 +0200 Subject: [PATCH 180/375] kueezRtb Bid Adapter: pass sua data to server. (#9643) --- modules/kueezRtbBidAdapter.js | 6 +++ test/spec/modules/kueezRtbBidAdapter_spec.js | 50 ++++++++++++++++++++ 2 files changed, 56 insertions(+) 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/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 7c0d8e92001..2f48fd6df8c 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -98,6 +98,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': '' + } } } }; @@ -263,6 +281,22 @@ describe('KueezRtbBidAdapter', function () { 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: { @@ -310,6 +344,22 @@ describe('KueezRtbBidAdapter', function () { 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, From 9de8066a4a5b4a008e52796be831413d2adc22dd Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 9 Mar 2023 05:41:53 -0800 Subject: [PATCH 181/375] 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 --- src/native.js | 11 +- test/spec/native_spec.js | 316 ++++++++++++++++++++++----------------- 2 files changed, 186 insertions(+), 141 deletions(-) diff --git a/src/native.js b/src/native.js index 25f8c38cb30..c4413a1a6de 100644 --- a/src/native.js +++ b/src/native.js @@ -9,8 +9,8 @@ import { isNumber, isPlainObject, logError, - triggerPixel, - pick + pick, + triggerPixel } from './utils.js'; import {includes} from './polyfill.js'; import {auctionManager} from './auctionManager.js'; @@ -385,16 +385,19 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { return keyValues; } -function assetsMessage(data, adObject, keys) { +function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {}) { const message = { message: 'assetResponse', adId: data.adId, }; + const adUnit = index.getAdUnit(adObject); let nativeResp = adObject.native; if (adObject.native.ortb) { message.ortb = adObject.native.ortb; + } else if (adUnit.mediaTypes?.native?.ortb) { + message.ortb = toOrtbNativeResponse(adObject.native, adUnit.nativeOrtbRequest); } message.assets = []; @@ -698,7 +701,7 @@ export function toOrtbNativeResponse(legacyResponse, ortbRequest) { } Object.keys(legacyResponse).filter(key => !!legacyResponse[key]).forEach(key => { - const value = legacyResponse[key]; + const value = getAssetValue(legacyResponse[key]); switch (key) { // process titles case 'title': 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' + } + }) }) }) From f16de3f89268ad93a2e20e190603ac888ccb71ba Mon Sep 17 00:00:00 2001 From: moquity <33487117+moquity@users.noreply.github.com> Date: Thu, 9 Mar 2023 17:10:47 +0200 Subject: [PATCH 182/375] main>modules\neuwoRtdProvider.js > apiUrl format handling improved, removed unnecessary parameter integrationExamples\gpt\neuwoRtdProvider_example.html > fixed render-step handling on warning (#9646) --- integrationExamples/gpt/neuwoRtdProvider_example.html | 7 ++++--- modules/neuwoRtdProvider.js | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/integrationExamples/gpt/neuwoRtdProvider_example.html b/integrationExamples/gpt/neuwoRtdProvider_example.html index 8d82d50c3e9..142a7c39613 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/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index e13c6151e5d..77daa9c258e 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -30,9 +30,10 @@ export function getBidRequestData(reqBidsConfigObj, callback, config, userConsen logInfo('NeuwoRTDModule', 'starting getBidRequestData') const wrappedArgUrl = encodeURIComponent(config.params.argUrl || getRefererInfo().page); - const url = config.params.apiUrl + [ + /* 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(); From 5ffebbafe451b347d5ef41beae96e9abdc86e5ea Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Thu, 9 Mar 2023 16:17:02 +0100 Subject: [PATCH 183/375] 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 --- modules/rubiconBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index f93e3f4e5eb..11d60e77ece 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -123,6 +123,7 @@ var sizeMap = { 278: '320x500', 282: '320x400', 288: '640x380', + 524: '1x2', 548: '500x1000', 550: '980x480', 552: '300x200', From fb31f6cbdd7d494bda05cfb4aa19b98a9fba9053 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 Mar 2023 15:46:59 +0000 Subject: [PATCH 184/375] Prebid 7.40.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10239bd1959..4b2e61bfde6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.40.0-pre", + "version": "7.40.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 65a93076782..dee9473b872 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.40.0-pre", + "version": "7.40.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 46ceb64aca74a3ecb70a7337d982208cedd569e0 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 Mar 2023 15:47:00 +0000 Subject: [PATCH 185/375] Increment version to 7.41.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b2e61bfde6..b2f1cd00afe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.40.0", + "version": "7.41.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index dee9473b872..7c47e12b43b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.40.0", + "version": "7.41.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ca3719eb924943c678fc8349015b9ec1bcb7bdfb Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Thu, 9 Mar 2023 12:35:26 -0500 Subject: [PATCH 186/375] 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 --- modules/pubwiseBidAdapter.js | 298 ++++++++++++++++-- test/spec/modules/pubwiseBidAdapter_spec.js | 316 +++++++++++++++++++- 2 files changed, 578 insertions(+), 36 deletions(-) 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/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 + }); }); From 45c2d92bddc8561e69fb00f4c8f0e1c482221b93 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 9 Mar 2023 11:43:45 -0800 Subject: [PATCH 187/375] Update creative.html (#9648) --- integrationExamples/gpt/x-domain/creative.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@ ', + '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); + }); + }); +}) +; From 8a1e0637f709f5491cf99a5b3d58bbabf5a56cb7 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Sun, 19 Mar 2023 06:29:02 -0700 Subject: [PATCH 214/375] GDPR consent management: accept static config without `getTCData` (#9664) --- modules/consentManagement.js | 9 ++-- test/spec/modules/consentManagement_spec.js | 49 +++++++++++---------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 44e8d39b832..83e5c4aab13 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -287,11 +287,6 @@ function processCmpData(consentObject, {onSuccess, onError}) { ); } - // do extra things for static config - if (userCMP === 'static') { - consentObject = consentObject.getTCData; - } - if (checkData()) { onError(`CMP returned unexpected value during lookup process.`, consentObject); } else { @@ -362,6 +357,10 @@ export function setConsentConfig(config) { if (userCMP === 'static') { if (isPlainObject(config.consentData)) { staticConsentData = config.consentData; + if (staticConsentData?.getTCData != null) { + // accept static config with or without `getTCData` - see https://github.com/prebid/Prebid.js/issues/9581 + staticConsentData = staticConsentData.getTCData; + } consentTimeout = 0; } else { logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 48b48c987e5..2cd3d011e1d 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -146,16 +146,17 @@ describe('consentManagement', function () { }); describe('static consent string setConsentConfig value', () => { - afterEach(() => { - config.resetConfig(); - }); + Object.entries({ + 'getTCData': (cfg) => ({getTCData: cfg}), + 'consent data directly': (cfg) => cfg, + }).forEach(([t, packageCfg]) => { + describe(`using ${t}`, () => { + afterEach(() => { + config.resetConfig(); + }); - it('results in user settings overriding system defaults for v2 spec', () => { - let staticConfig = { - cmpApi: 'static', - timeout: 7500, - consentData: { - getTCData: { + it('results in user settings overriding system defaults for v2 spec', () => { + const consentData = { 'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A', 'cmpId': 92, 'cmpVersion': 100, @@ -218,18 +219,22 @@ describe('consentManagement', function () { 'legitimateInterests': {} } } - } - } - }; + }; - setConsentConfig(staticConfig); - expect(userCMP).to.be.equal('static'); - expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(gdprScope).to.be.equal(false); - const consent = gdprDataHandler.getConsentData(); - expect(consent.consentString).to.eql(staticConfig.consentData.getTCData.tcString); - expect(consent.vendorData).to.eql(staticConfig.consentData.getTCData); - expect(staticConsentData).to.be.equal(staticConfig.consentData); + setConsentConfig({ + cmpApi: 'static', + timeout: 7500, + consentData: packageCfg(consentData) + }); + expect(userCMP).to.be.equal('static'); + expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + expect(gdprScope).to.be.equal(false); + const consent = gdprDataHandler.getConsentData(); + expect(consent.consentString).to.eql(consentData.tcString); + expect(consent.vendorData).to.eql(consentData); + expect(staticConsentData).to.be.equal(consentData); + }); + }); }); }); }); @@ -243,9 +248,7 @@ describe('consentManagement', function () { const staticConfig = { cmpApi: 'static', timeout: 7500, - consentData: { - getTCData: {} - } + consentData: {} } let didHookReturn; From e168e909dbcfcb254938ec275f8689e82a3c00b7 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Mon, 20 Mar 2023 10:13:59 -0600 Subject: [PATCH 215/375] Revert "Nativo Bid Adapter: adding UserId support (#9583)" (#9691) This reverts commit aa100bc72df52fe2e6c9bb7bcf41be9867c65be5. --- modules/nativoBidAdapter.js | 115 ++------------------- test/spec/modules/nativoBidAdapter_spec.js | 100 ------------------ 2 files changed, 10 insertions(+), 205 deletions(-) diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index ebf41338cbb..a92168492d0 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -133,9 +133,6 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - const requestData = new RequestData() - requestData.addBidRequestDataSource(new UserEIDs()) - // Parse values from bid requests const placementIds = new Set() const bidDataMap = BidDataMap() @@ -169,8 +166,6 @@ export const spec = { if (bidRequestFloorPriceData) { floorPriceData[bidRequest.adUnitCode] = bidRequestFloorPriceData } - - requestData.processBidRequestData(bidRequest, bidderRequest) }) bidRequestMap[bidderRequest.bidderRequestId] = bidDataMap @@ -260,12 +255,9 @@ export const spec = { params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) } - const qsParamStrings = [requestData.getRequestDataQueryString(), arrayToQS(params)] - const requestUrl = buildRequestUrl(BIDDER_ENDPOINT, qsParamStrings) - let serverRequest = { method: 'GET', - url: requestUrl + url: BIDDER_ENDPOINT + arrayToQS(params), } return serverRequest @@ -417,7 +409,7 @@ export const spec = { * Adapter can fire a ajax or pixel call to register a timeout at thier end. * @param {Object} timeoutData - Timeout specific data */ - onTimeout: function (timeoutData) { }, + onTimeout: function (timeoutData) {}, /** * Will be called when a bid from the adapter won the auction. @@ -437,7 +429,7 @@ export const spec = { * Will be called when the adserver targeting has been set for a bid from the adapter. * @param {Object} bidder - The bid of which the targeting has been set */ - onSetTargeting: function (bid) { }, + onSetTargeting: function (bid) {}, /** * Maps Prebid's bidId to Nativo's placementId values per unique bidderRequestId @@ -459,78 +451,6 @@ export const spec = { registerBidder(spec) // Utils -export class RequestData { - constructor() { - this.bidRequestDataSources = [] - } - - addBidRequestDataSource(bidRequestDataSource) { - if (!(bidRequestDataSource instanceof BidRequestDataSource)) return - - this.bidRequestDataSources.push(bidRequestDataSource) - } - - processBidRequestData(bidRequest, bidderRequest) { - for (let bidRequestDataSource of this.bidRequestDataSources) { - bidRequestDataSource.processBidRequestData(bidRequest, bidderRequest) - } - } - - getRequestDataQueryString() { - if (this.bidRequestDataSources.length == 0) return - - const queryParams = this.bidRequestDataSources.map(dataSource => dataSource.getRequestQueryString()).filter(queryString => queryString !== '') - return queryParams.join('&') - } -} - -export class BidRequestDataSource { - constructor() { - this.type = 'BidRequestDataSource' - } - processBidRequestData(bidRequest, bidderRequest) { } - getRequestQueryString() { return '' } -} - -export class UserEIDs extends BidRequestDataSource { - constructor() { - super() - this.type = 'UserEIDs' - this.qsParam = new QueryStringParam('ntv_pb_eid') - this.eids = [] - } - - processBidRequestData(bidRequest, bidderRequest) { - if (bidRequest.userIdAsEids === undefined || this.eids.length > 0) return - this.eids = bidRequest.userIdAsEids - } - - getRequestQueryString() { - if (this.eids.length === 0) return '' - - const encodedValueArray = encodeToBase64(this.eids) - this.qsParam.value = encodedValueArray - return this.qsParam.toString() - } -} - -export class QueryStringParam { - constructor(key, value) { - this.key = key - this.value = value - } -} - -QueryStringParam.prototype.toString = function () { - return `${this.key}=${this.value}` -} - -export function encodeToBase64(value) { - try { - return btoa(JSON.stringify(value)) - } catch (err) { } -} - export function parseFloorPriceData(bidRequest) { if (typeof bidRequest.getFloor !== 'function') return @@ -669,9 +589,12 @@ function appendQSParamString(str, key, value) { * @returns */ function arrayToQS(arr) { - return arr.reduce((value, obj) => { - return appendQSParamString(value, obj.key, obj.value) - }, '') + return ( + '?' + + arr.reduce((value, obj) => { + return appendQSParamString(value, obj.key, obj.value) + }, '') + ) } /** @@ -692,24 +615,6 @@ function getLargestSize(sizes, method = area) { }) } -/** - * Build the final request url - */ -export function buildRequestUrl(baseUrl, qsParamStringArray = []) { - if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) return baseUrl - - const nonEmptyQSParamStrings = qsParamStringArray.filter(qsParamString => qsParamString.trim() !== '') - - if (nonEmptyQSParamStrings.length === 0) return baseUrl - - let requestUrl = `${baseUrl}?${nonEmptyQSParamStrings[0]}` - for (let i = 1; i < nonEmptyQSParamStrings.length; i++) { - requestUrl += `&${nonEmptyQSParamStrings[i]}` - } - - return requestUrl -} - /** * Calculate the area * @param {Array} size - [width, height] @@ -740,7 +645,7 @@ export function getPageUrlFromBidRequest(bidRequest) { try { const url = new URL(paramPageUrl) return url.href - } catch (err) { } + } catch (err) {} } export function hasProtocol(url) { diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index 6b7621f64d3..4d70e6f7071 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -8,10 +8,6 @@ import { getPageUrlFromBidRequest, hasProtocol, addProtocol, - BidRequestDataSource, - RequestData, - UserEIDs, - buildRequestUrl, } from '../../../modules/nativoBidAdapter' describe('bidDataMap', function () { @@ -735,99 +731,3 @@ describe('getPageUrlFromBidRequest', () => { expect(url).not.to.be.undefined }) }) - -describe('RequestData', () => { - describe('addBidRequestDataSource', () => { - it('Adds a BidRequestDataSource', () => { - const requestData = new RequestData() - const testBidRequestDataSource = new BidRequestDataSource() - - requestData.addBidRequestDataSource(testBidRequestDataSource) - - expect(requestData.bidRequestDataSources.length == 1) - }) - - it("Doeasn't add a non BidRequestDataSource", () => { - const requestData = new RequestData() - - requestData.addBidRequestDataSource({}) - requestData.addBidRequestDataSource('test') - requestData.addBidRequestDataSource(1) - requestData.addBidRequestDataSource(true) - - expect(requestData.bidRequestDataSources.length == 0) - }) - }) - - describe('getRequestDataString', () => { - it("Doesn't append empty query strings", () => { - const requestData = new RequestData() - const testBidRequestDataSource = new BidRequestDataSource() - - requestData.addBidRequestDataSource(testBidRequestDataSource) - - let qs = requestData.getRequestDataQueryString() - expect(qs).to.be.empty - - testBidRequestDataSource.getRequestQueryString = () => { - return 'ntv_test=true' - } - qs = requestData.getRequestDataQueryString() - expect(qs).to.be.equal('ntv_test=true') - }) - }) -}) - -describe('UserEIDs', () => { - const userEids = new UserEIDs() - const eids = [{ 'testId': 1111 }] - - describe('processBidRequestData', () => { - it('Processes bid request without eids', () => { - userEids.processBidRequestData({}) - - expect(userEids.values).to.be.empty - }) - - it('Processed bid request with eids', () => { - userEids.processBidRequestData({ userIdAsEids: eids }) - - expect(userEids.values).to.not.be.empty - }) - }) - - describe('getRequestQueryString', () => { - it('Correctly prints out QS param string', () => { - const qs = userEids.getRequestQueryString() - const value = qs.slice(11) - - expect(qs).to.include('ntv_pb_eid=') - try { - expect(JSON.parse(value)).to.be.equal(eids) - } catch (err) { } - }) - }) -}) - -describe.only('buildRequestUrl', () => { - const baseUrl = 'https://www.testExchange.com' - it('Returns baseUrl if no QS strings passed', () => { - const url = buildRequestUrl(baseUrl) - expect(url).to.be.equal(baseUrl) - }) - - it('Returns baseUrl if empty QS strings passed', () => { - const url = buildRequestUrl(baseUrl, ['', '', '']) - expect(url).to.be.equal(baseUrl) - }) - - it('Returns baseUrl + QS params if QS strings passed', () => { - const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', 'ntv_foo=bar']) - expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) - }) - - it('Returns baseUrl + QS params if mixed QS strings passed', () => { - const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', '', '', 'ntv_foo=bar']) - expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) - }) -}) From a69bf7c18cfff431ffa46c626264b61e5fcb9fce Mon Sep 17 00:00:00 2001 From: preved-medved Date: Mon, 20 Mar 2023 16:37:13 +0000 Subject: [PATCH 216/375] 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> --- modules/smartytechBidAdapter.js | 9 ++++ .../spec/modules/smartytechBidAdapter_spec.js | 51 ++++++++++++++++--- 2 files changed, 53 insertions(+), 7 deletions(-) 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/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 = { From 2f52629198fffd32466df4bb3ab5866dcd17e0fd Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 21 Mar 2023 10:28:22 -0400 Subject: [PATCH 217/375] Criteo Bid Adapter: reinforce adomain type in case of missmatch (#9687) * Update criteoBidAdapter.js * Update auction.js --- modules/criteoBidAdapter.js | 2 +- src/auction.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 7f2d6f2a3fb..e6564ca19d6 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -233,7 +233,7 @@ export const spec = { bid.meta = Object.assign({}, bid.meta, { paf: pafResponseMeta }); } if (slot.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: slot.adomain }); + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [slot.adomain].flat() }); } if (slot.native) { if (bidRequest.params.nativeCallback) { diff --git a/src/auction.js b/src/auction.js index 6dd0432712f..a1e0fe13573 100644 --- a/src/auction.js +++ b/src/auction.js @@ -835,7 +835,7 @@ export const getPriceByGranularity = (granularity) => { */ export const getAdvertiserDomain = () => { return (bid) => { - return (bid.meta && bid.meta.advertiserDomains && bid.meta.advertiserDomains.length > 0) ? bid.meta.advertiserDomains[0] : ''; + return (bid.meta && bid.meta.advertiserDomains && bid.meta.advertiserDomains.length > 0) ? [bid.meta.advertiserDomains].flat()[0] : ''; } } From e8cc73d0c47dde452ffe2a2f5bc13194da2ac913 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Tue, 21 Mar 2023 15:40:03 +0100 Subject: [PATCH 218/375] 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 --- modules/liveIntentIdSystem.js | 24 +++++-- package-lock.json | 29 ++++++-- package.json | 2 +- test/spec/modules/liveIntentIdSystem_spec.js | 73 +++++++++++++------- test/spec/modules/userId_spec.js | 3 +- 5 files changed, 92 insertions(+), 39 deletions(-) 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/package-lock.json b/package-lock.json index cdb449b1289..b6a11d97b3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^4.0.0" + "live-connect-js": "^5.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16228,11 +16228,20 @@ "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", "dev": true }, + "node_modules/live-connect-common": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-1.0.0.tgz", + "integrity": "sha512-LBZsvykcGeVRYI1eqqXrrNZsoBdL2a8cpyrYPIiGAF/CpixbyRbvqGslaFw511lH294QB16J3fYYg21aYuaM2Q==", + "engines": { + "node": ">=8" + } + }, "node_modules/live-connect-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", - "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-5.0.0.tgz", + "integrity": "sha512-Bv0wQQ+/1VU0/YczEpObbWtHbuXwaHGxwg1+Pe7ZlDgBLb334CrqSQvOL1uyZw3//zs+fSO94yYaQzjjkTd5OQ==", "dependencies": { + "live-connect-common": "^1.0.0", "tiny-hashes": "1.0.1" }, "engines": { @@ -37864,11 +37873,17 @@ "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", "dev": true }, + "live-connect-common": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-1.0.0.tgz", + "integrity": "sha512-LBZsvykcGeVRYI1eqqXrrNZsoBdL2a8cpyrYPIiGAF/CpixbyRbvqGslaFw511lH294QB16J3fYYg21aYuaM2Q==" + }, "live-connect-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", - "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-5.0.0.tgz", + "integrity": "sha512-Bv0wQQ+/1VU0/YczEpObbWtHbuXwaHGxwg1+Pe7ZlDgBLb334CrqSQvOL1uyZw3//zs+fSO94yYaQzjjkTd5OQ==", "requires": { + "live-connect-common": "^1.0.0", "tiny-hashes": "1.0.1" } }, diff --git a/package.json b/package.json index 72d0174e625..978f40a52e2 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^4.0.0" + "live-connect-js": "^5.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" 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/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 () { From 3ced4069effd401bd952b4385c3db201c25dd2b7 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Tue, 21 Mar 2023 21:15:33 +0200 Subject: [PATCH 219/375] minutemediaplus Bid Adapter - pass gpp, sua and bid data to server. (#9637) --- modules/minutemediaplusBidAdapter.js | 80 +++++++--- .../modules/minutemediaplusBidAdapter_spec.js | 137 +++++++++++++++--- 2 files changed, 174 insertions(+), 43 deletions(-) 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/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'); }); From 85744b1e43874c7b69ec78d90d265fea318400b8 Mon Sep 17 00:00:00 2001 From: duancg Date: Tue, 21 Mar 2023 13:14:59 -0700 Subject: [PATCH 220/375] DistroScale bidder enhancement (#9641) --- modules/distroscaleBidAdapter.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/distroscaleBidAdapter.js b/modules/distroscaleBidAdapter.js index 005dd3e67d6..7a2038ed3f0 100644 --- a/modules/distroscaleBidAdapter.js +++ b/modules/distroscaleBidAdapter.js @@ -132,6 +132,24 @@ export const spec = { // TODO: does the fallback to window.location make sense? var pageUrl = bidderRequest?.refererInfo?.page || window.location.href; + // check if dstag is already loaded in ancestry tree + var dsloaded = 0; + try { + var win = window; + while (true) { + if (win.vx.cs_loaded) { + dsloaded = 1; + } + if (win != win.parent) { + win = win.parent; + } else { + break; + } + } + } catch (error) { + // ignore exception + } + var payload = { id: '' + (new Date()).getTime(), at: AUCTION_TYPE, @@ -149,7 +167,9 @@ export const spec = { }, imp: [], user: {}, - ext: {} + ext: { + dsloaded: dsloaded + } }; validBidRequests.forEach(b => { From 7585fed22649b75dd4c7497268af526000581dbc Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 22 Mar 2023 06:23:18 -0700 Subject: [PATCH 221/375] Disable describe.only and it.only (#9693) --- test/test_index.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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$/); From a144f5af10eda65b13a6f38fbb8e8bc338dbb32c Mon Sep 17 00:00:00 2001 From: Antonio Gargaro <38767071+AntonioGargaro@users.noreply.github.com> Date: Wed, 22 Mar 2023 14:27:16 +0000 Subject: [PATCH 222/375] 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. --- modules/permutiveRtdProvider.js | 47 ++++++++++++------- .../spec/modules/permutiveRtdProvider_spec.js | 38 +++++++-------- 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 4ff41627135..7e53a0f5271 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -133,6 +133,8 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { * @return {Object} Merged ortb2 object */ 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' @@ -158,13 +160,34 @@ function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transfo .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) { @@ -177,8 +200,7 @@ function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transfo logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}"`, customCohortsData) } - logger.logInfo(`Updating ortb2 config for ${bidder}`, ortbConfig) - + logger.logInfo(`Updated ortb2 config`, { bidder, config: ortbConfig }) return ortbConfig } @@ -256,17 +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 - }, ozone: function (bid, data, acEnabled) { if (isPStandardTargetingEnabled(data, acEnabled)) { const segments = pStandardTargeting(data, acEnabled) diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 18d413acdad..1f39d1a2cda 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -8,6 +8,9 @@ import { 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' @@ -365,9 +368,23 @@ describe('permutiveRtdProvider', function () { 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 () { @@ -544,25 +561,6 @@ describe('permutiveRtdProvider', function () { }) describe('Default segment targeting', function () { - it('sets segment targeting for Xandr', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() - - readAndSetCohorts({ adUnits }, config) - - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - 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)) - } - }) - }) - }) - it('sets segment targeting for Ozone', function () { const data = transformedTargeting() const adUnits = getAdUnits() From cd2535893d79a18a9cffe7d0fa59d45012ff2860 Mon Sep 17 00:00:00 2001 From: joseluis laso Date: Wed, 22 Mar 2023 10:53:01 -0700 Subject: [PATCH 223/375] changed the URL (#9698) --- modules/hadronRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 0bd4e6f8344..7a0299fc427 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -18,7 +18,7 @@ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'hadron'; const AU_GVLID = 561; const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebid'; -const HADRON_SEGMENT_URL = 'https://seg.hadron.ad.gt/api/v1/rtd'; +const HADRON_SEGMENT_URL = 'https://id.hadron.ad.gt/api/v1/rtd'; export const HALOID_LOCAL_NAME = 'auHadronId'; export const RTD_LOCAL_NAME = 'auHadronRtd'; export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: SUBMODULE_NAME}); From 37976fe645549968391d3a0ca0bffc9bda20444d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 22 Mar 2023 15:08:05 -0700 Subject: [PATCH 224/375] 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` --- libraries/ortbConverter/processors/default.js | 38 +--- src/fpd/enrichment.js | 63 +++++-- src/fpd/oneClient.js | 26 +++ {libraries => src}/fpd/sua.js | 4 +- test/spec/fpd/enrichment_spec.js | 173 ++++++++++++++---- .../oneClient.js} | 7 +- test/spec/fpd/sua_spec.js | 2 +- .../modules/improvedigitalBidAdapter_spec.js | 14 +- .../modules/prebidServerBidAdapter_spec.js | 16 +- 9 files changed, 242 insertions(+), 101 deletions(-) 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%) diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index 1d6bfb8424e..55812786768 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -1,10 +1,10 @@ -import {deepSetValue, logWarn, mergeDeep} from '../../../src/utils.js'; +import {deepSetValue, mergeDeep} from '../../../src/utils.js'; import {bannerResponseProcessor, fillBannerImp} from './banner.js'; import {fillVideoImp, fillVideoResponse} from './video.js'; import {setResponseMediaType} from './mediaType.js'; import {fillNativeImp, fillNativeResponse} from './native.js'; import {BID_RESPONSE, IMP, REQUEST} from '../../../src/pbjsORTB.js'; -import {config} from '../../../src/config.js'; +import {clientSectionChecker} from '../../../src/fpd/oneClient.js'; export const DEFAULT_PROCESSORS = { [REQUEST]: { @@ -15,14 +15,10 @@ export const DEFAULT_PROCESSORS = { mergeDeep(ortbRequest, bidderRequest.ortb2) } }, - // override FPD app, site, and device with getConfig('app'), etc if defined - // TODO: these should be deprecated for v8 - appFpd: fpdFromTopLevelConfig('app'), - siteFpd: fpdFromTopLevelConfig('site'), - deviceFpd: fpdFromTopLevelConfig('device'), onlyOneClient: { + // make sure only one of 'dooh', 'app', 'site' is set in request priority: -99, - fn: onlyOneClientSection + fn: clientSectionChecker('ORTB request') }, props: { // sets request properties id, tmax, test, source.tid @@ -125,29 +121,3 @@ if (FEATURES.NATIVE) { fn: fillNativeResponse } } - -function fpdFromTopLevelConfig(prop) { - return { - priority: 90, // after FPD from 'ortb2', before the rest - fn(ortbRequest) { - const data = config.getConfig(prop); - if (typeof data === 'object') { - ortbRequest[prop] = mergeDeep({}, ortbRequest[prop], data); - } - } - } -} - -export function onlyOneClientSection(ortbRequest) { - ['dooh', 'app', 'site'].reduce((found, section) => { - if (ortbRequest[section] != null && Object.keys(ortbRequest[section]).length > 0) { - if (found != null) { - logWarn(`ORTB request specifies both '${found}' and '${section}'; dropping the latter.`) - delete ortbRequest[section]; - } else { - found = section; - } - } - return found; - }, null); -} diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index fc50a057378..d6b7d2ffb04 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -3,8 +3,9 @@ import {getRefererInfo, parseDomain} from '../refererDetection.js'; import {findRootDomain} from './rootDomain.js'; import {deepSetValue, getDefinedParams, getDNT, getWindowSelf, getWindowTop, mergeDeep} from '../utils.js'; import {config} from '../config.js'; -import {getHighEntropySUA, getLowEntropySUA} from '../../libraries/fpd/sua.js'; +import {getHighEntropySUA, getLowEntropySUA} from './sua.js'; import {GreedyPromise} from '../utils/promise.js'; +import {CLIENT_SECTIONS, clientSectionChecker, hasSection} from './oneClient.js'; export const dep = { getRefererInfo, @@ -15,6 +16,8 @@ export const dep = { getLowEntropySUA, }; +const oneClient = clientSectionChecker('FPD') + /** * Enrich an ortb2 object with first party data. * @param {Promise[{}]} fpd: a promise to an ortb2 object. @@ -23,8 +26,10 @@ export const dep = { export const enrichFPD = hook('sync', (fpd) => { return GreedyPromise.all([fpd, getSUA().catch(() => null)]) .then(([ortb2, sua]) => { + const ri = dep.getRefererInfo(); + mergeLegacySetConfigs(ortb2); Object.entries(ENRICHMENTS).forEach(([section, getEnrichments]) => { - const data = getEnrichments(); + const data = getEnrichments(ortb2, ri); if (data && Object.keys(data).length > 0) { ortb2[section] = mergeDeep({}, data, ortb2[section]); } @@ -32,10 +37,28 @@ export const enrichFPD = hook('sync', (fpd) => { if (sua) { deepSetValue(ortb2, 'device.sua', Object.assign({}, sua, ortb2.device.sua)); } + ortb2 = oneClient(ortb2); + for (let section of CLIENT_SECTIONS) { + if (hasSection(ortb2, section)) { + ortb2[section] = mergeDeep({}, clientEnrichment(ortb2, ri), ortb2[section]); + break; + } + } return ortb2; }); }); +function mergeLegacySetConfigs(ortb2) { + // merge in values from "legacy" setConfig({app, site, device}) + // TODO: deprecate these eventually + ['app', 'site', 'device'].forEach(prop => { + const cfg = config.getConfig(prop); + if (cfg != null) { + ortb2[prop] = mergeDeep({}, cfg, ortb2[prop]); + } + }) +} + function winFallback(fn) { try { return fn(dep.getWindowTop()); @@ -51,20 +74,19 @@ function getSUA() { : dep.getHighEntropySUA(hints); } +function removeUndef(obj) { + return getDefinedParams(obj, Object.keys(obj)) +} + const ENRICHMENTS = { - site() { - const ri = dep.getRefererInfo(); - const domain = parseDomain(ri.page, {noLeadingWww: true}); - const keywords = winFallback((win) => win.document.querySelector('meta[name=\'keywords\']')) - ?.content?.replace?.(/\s/g, ''); - return (site => getDefinedParams(site, Object.keys(site)))({ + site(ortb2, ri) { + if (CLIENT_SECTIONS.filter(p => p !== 'site').some(hasSection.bind(null, ortb2))) { + // do not enrich site if dooh or app are set + return; + } + return removeUndef({ page: ri.page, ref: ri.ref, - domain, - keywords, - publisher: { - domain: dep.findRootDomain(domain) - } }); }, device() { @@ -92,3 +114,18 @@ const ENRICHMENTS = { return regs; } }; + +// Enrichment of properties common across dooh, app and site - will be dropped into whatever +// section is appropriate +function clientEnrichment(ortb2, ri) { + const domain = parseDomain(ri.page, {noLeadingWww: true}); + const keywords = winFallback((win) => win.document.querySelector('meta[name=\'keywords\']')) + ?.content?.replace?.(/\s/g, ''); + return removeUndef({ + domain, + keywords, + publisher: removeUndef({ + domain: dep.findRootDomain(domain) + }) + }) +} diff --git a/src/fpd/oneClient.js b/src/fpd/oneClient.js new file mode 100644 index 00000000000..67f53c73bd8 --- /dev/null +++ b/src/fpd/oneClient.js @@ -0,0 +1,26 @@ +import {logWarn} from '../utils.js'; + +// mutually exclusive ORTB sections in order of priority - 'dooh' beats 'app' & 'site' and 'app' beats 'site'; +// if one is set, the others will be removed +export const CLIENT_SECTIONS = ['dooh', 'app', 'site'] + +export function clientSectionChecker(logPrefix) { + return function onlyOneClientSection(ortb2) { + CLIENT_SECTIONS.reduce((found, section) => { + if (hasSection(ortb2, section)) { + if (found != null) { + logWarn(`${logPrefix} specifies both '${found}' and '${section}'; dropping the latter.`) + delete ortb2[section]; + } else { + found = section; + } + } + return found; + }, null); + return ortb2; + } +} + +export function hasSection(ortb2, section) { + return ortb2[section] != null && Object.keys(ortb2[section]).length > 0 +} diff --git a/libraries/fpd/sua.js b/src/fpd/sua.js similarity index 96% rename from libraries/fpd/sua.js rename to src/fpd/sua.js index b6a46763514..30b2be4c13b 100644 --- a/libraries/fpd/sua.js +++ b/src/fpd/sua.js @@ -1,5 +1,5 @@ -import {isEmptyStr, isStr, isEmpty} from '../../src/utils.js'; -import {GreedyPromise} from '../../src/utils/promise.js'; +import {isEmptyStr, isStr, isEmpty} from '../utils.js'; +import {GreedyPromise} from '../utils/promise.js'; export const SUA_SOURCE_UNKNOWN = 0; export const SUA_SOURCE_LOW_ENTROPY = 1; diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js index 3363e1867d3..328846ca081 100644 --- a/test/spec/fpd/enrichment_spec.js +++ b/test/spec/fpd/enrichment_spec.js @@ -2,6 +2,8 @@ import {dep, enrichFPD} from '../../../src/fpd/enrichment.js'; import {hook} from '../../../src/hook.js'; import {expect} from 'chai/index.mjs'; import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import {CLIENT_SECTIONS} from '../../../src/fpd/oneClient.js'; describe('FPD enrichment', () => { let sandbox; @@ -48,50 +50,86 @@ describe('FPD enrichment', () => { }); } - describe('site', () => { - it('sets page, ref, domain, and publisher.domain', () => { - const refererInfo = { - page: 'www.example.com', - ref: 'referrer.com' - }; - sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); - sandbox.stub(dep, 'findRootDomain').callsFake((dom) => `publisher.${dom}`); - return fpd().then(ortb2 => { - sinon.assert.match(ortb2.site, { + CLIENT_SECTIONS.forEach(section => { + describe(`${section}, when set`, () => { + const ORTB2 = {[section]: {ext: {}}} + + it('sets domain and publisher.domain', () => { + const refererInfo = { page: 'www.example.com', - domain: 'example.com', - ref: 'referrer.com', - publisher: { - domain: 'publisher.example.com' - } + }; + sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); + sandbox.stub(dep, 'findRootDomain').callsFake((dom) => `publisher.${dom}`); + return fpd(ORTB2).then(ortb2 => { + sinon.assert.match(ortb2[section], { + domain: 'example.com', + publisher: { + domain: 'publisher.example.com' + } + }); + }); + }) + + describe('keywords', () => { + let metaTag; + beforeEach(() => { + metaTag = document.createElement('meta'); + metaTag.name = 'keywords'; + metaTag.content = 'kw1, kw2'; + document.head.appendChild(metaTag); + }); + afterEach(() => { + document.head.removeChild(metaTag); + }); + + testWindows(() => window, () => { + it(`sets kewwords from meta tag`, () => { + return fpd(ORTB2).then(ortb2 => { + expect(ortb2[section].keywords).to.eql('kw1,kw2'); + }); + }); }); }); - }); - describe('keywords', () => { - let metaTag; + it('should not set keywords if meta tag is not present', () => { + return fpd(ORTB2).then(ortb2 => { + expect(ortb2[section].hasOwnProperty('keywords')).to.be.false; + }); + }); + }) + }) + + describe('site', () => { + describe('when mixed with app/dooh', () => { beforeEach(() => { - metaTag = document.createElement('meta'); - metaTag.name = 'keywords'; - metaTag.content = 'kw1, kw2'; - document.head.appendChild(metaTag); + sinon.stub(utils, 'logWarn'); }); + afterEach(() => { - document.head.removeChild(metaTag); + utils.logWarn.restore(); }); - testWindows(() => window, () => { - it(`sets kewwords from meta tag`, () => { - return fpd().then(ortb2 => { - expect(ortb2.site.keywords).to.eql('kw1,kw2'); - }); - }); - }); - }); + ['dooh', 'app'].forEach(prop => { + it(`should not be set when ${prop} is set`, () => { + return fpd({[prop]: {foo: 'bar'}}).then(ortb2 => { + expect(ortb2.site).to.not.exist; + sinon.assert.notCalled(utils.logWarn); // make sure we don't generate "both site and app are set" warnings + }) + }) + }) + }) - it('should not set keywords if meta tag is not present', () => { + it('sets page, ref', () => { + const refererInfo = { + page: 'www.example.com', + ref: 'referrer.com' + }; + sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); return fpd().then(ortb2 => { - expect(ortb2.site.hasOwnProperty('keywords')).to.be.false; + sinon.assert.match(ortb2.site, { + page: 'www.example.com', + ref: 'referrer.com', + }); }); }); @@ -106,6 +144,26 @@ describe('FPD enrichment', () => { expect(ortb2.site.publisher.domain).to.eql('pub.com'); }); }); + + it('respects config set through setConfig({site})', () => { + sandbox.stub(dep, 'getRefererInfo').callsFake(() => ({ + page: 'www.example.com', + ref: 'referrer.com', + })); + config.setConfig({ + site: { + ref: 'override.com', + priority: 'lower' + } + }); + return fpd({site: {priority: 'highest'}}).then(ortb2 => { + sinon.assert.match(ortb2.site, { + page: 'www.example.com', + ref: 'override.com', + priority: 'highest' + }) + }) + }) }); describe('device', () => { @@ -138,9 +196,44 @@ describe('FPD enrichment', () => { expect(ortb2.device.language).to.eql('lang'); }) }); + + it('respects setConfig({device})', () => { + win.navigator.userAgent = 'ua'; + win.navigator.language = 'lang'; + config.setConfig({ + device: { + language: 'override', + priority: 'lower' + } + }); + return fpd({device: {priority: 'highest'}}).then(ortb2 => { + sinon.assert.match(ortb2.device, { + language: 'override', + priority: 'highest', + ua: 'ua' + }) + }) + }) }); }); + describe('app', () => { + it('respects setConfig({app})', () => { + config.setConfig({ + app: { + priority: 'lower', + prop: 'value' + } + }); + return fpd({app: {priority: 'highest'}}).then(ortb2 => { + sinon.assert.match(ortb2.app, { + priority: 'highest', + prop: 'value' + }) + }) + }) + }) + describe('regs', () => { describe('gpc', () => { let win; @@ -210,4 +303,18 @@ describe('FPD enrichment', () => { }) }); }); + + it('leaves only one of app, site, dooh', () => { + return fpd({ + app: {p: 'val'}, + site: {p: 'val'}, + dooh: {p: 'val'} + }).then(ortb2 => { + expect(ortb2.app).to.not.exist; + expect(ortb2.site).to.not.exist; + sinon.assert.match(ortb2.dooh, { + p: 'val' + }) + }); + }) }); diff --git a/test/spec/ortbConverter/default_processors_spec.js b/test/spec/fpd/oneClient.js similarity index 79% rename from test/spec/ortbConverter/default_processors_spec.js rename to test/spec/fpd/oneClient.js index 48204b2c861..4ecde8d8a38 100644 --- a/test/spec/ortbConverter/default_processors_spec.js +++ b/test/spec/fpd/oneClient.js @@ -1,6 +1,7 @@ -import {onlyOneClientSection} from '../../../libraries/ortbConverter/processors/default.js'; +import {clientSectionChecker} from '../../../src/fpd/oneClient.js'; describe('onlyOneClientSection', () => { + const oneClient = clientSectionChecker(); [ [['app'], 'app'], [['site'], 'site'], @@ -11,13 +12,13 @@ describe('onlyOneClientSection', () => { ].forEach(([sections, winner]) => { it(`should leave only ${winner} in request when it contains ${sections.join(', ')}`, () => { const req = Object.fromEntries(sections.map(s => [s, {foo: 'bar'}])); - onlyOneClientSection(req); + oneClient(req); expect(Object.keys(req)).to.eql([winner]); }) }); it('should not choke if none of the sections are in the request', () => { const req = {}; - onlyOneClientSection(req); + oneClient(req); expect(req).to.eql({}); }); }); diff --git a/test/spec/fpd/sua_spec.js b/test/spec/fpd/sua_spec.js index 121922fa78d..431f47268d3 100644 --- a/test/spec/fpd/sua_spec.js +++ b/test/spec/fpd/sua_spec.js @@ -6,7 +6,7 @@ import { SUA_SOURCE_UNKNOWN, suaFromUAData, uaDataToSUA -} from '../../../libraries/fpd/sua.js'; +} from '../../../src/fpd/sua.js'; describe('uaDataToSUA', () => { Object.entries({ diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 400b9145e0b..a0a121b27e6 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,9 +1,9 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import {CONVERTER, spec} from 'modules/improvedigitalBidAdapter.js'; -import { config } from 'src/config.js'; -import { deepClone } from 'src/utils.js'; +import {config} from 'src/config.js'; +import {deepClone} from 'src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; -import { deepSetValue } from '../../../src/utils'; +import {deepSetValue} from '../../../src/utils'; // load modules that register ORTB processors import 'src/prebid.js'; import 'modules/currency.js'; @@ -13,7 +13,7 @@ import 'modules/priceFloors.js'; import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; -import {decorateAdUnitsWithNativeParams, toLegacyResponse} from '../../../src/native.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {createEidsArray} from '../../../modules/userId/eids.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; @@ -670,7 +670,7 @@ describe('Improve Digital Adapter Tests', function () { it('should not set site when app is defined in CONFIG', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns({ content: 'XYZ' }); - let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; let payload = JSON.parse(request.data); expect(payload.site).does.not.exist; expect(payload.app).does.exist; @@ -709,7 +709,7 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns(undefined); getConfigStub.withArgs('site').returns({}); - let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; let payload = JSON.parse(request.data); expect(payload.site).does.exist; expect(payload.app).does.not.exist; diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 820a57b4e83..352f9aea431 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'); From 4ca23f7f4f05e0708f78f7fb98ee0bdf7d54f3b9 Mon Sep 17 00:00:00 2001 From: Vadim Mazzherin Date: Thu, 23 Mar 2023 19:07:39 +0600 Subject: [PATCH 225/375] 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> --- modules/showheroes-bsBidAdapter.js | 7 +++- .../modules/showheroes-bsBidAdapter_spec.js | 40 ++++++++++--------- 2 files changed, 28 insertions(+), 19 deletions(-) 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/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) From 36ac6b58dbbd72f944d1f920751df0005807dd64 Mon Sep 17 00:00:00 2001 From: Vladimir Fedoseev Date: Thu, 23 Mar 2023 14:29:01 +0100 Subject: [PATCH 226/375] VIS.X fix onTimeout data (#9657) --- modules/visxBidAdapter.js | 12 ++++++------ test/spec/modules/visxBidAdapter_spec.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index ee4d9708f15..a066d93d69e 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -204,16 +204,16 @@ export const spec = { }, onTimeout: function(timeoutData) { // Call '/track/bid_timeout' with timeout data - timeoutData.forEach(({ params }) => { + const dataToSend = timeoutData.map(({ params, timeout }) => { + const data = { timeout }; if (params) { - params.forEach((item) => { - if (item && item.uid) { - item.uid = parseInt(item.uid); - } + data.params = params.map((item) => { + return item && item.uid ? { uid: parseInt(item.uid) } : {}; }); } + return data; }); - triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '//' + JSON.stringify(timeoutData)); + triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '//' + JSON.stringify(dataToSend)); } }; 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); }); From 5f98fad8dc325adbe9093de8cd4a5a7ab01d1d3f Mon Sep 17 00:00:00 2001 From: Yoko OYAMA Date: Thu, 23 Mar 2023 22:56:05 +0900 Subject: [PATCH 227/375] 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 --- modules/fluctBidAdapter.js | 41 ++++++- test/spec/modules/fluctBidAdapter_spec.js | 137 +++++++++++++++++++++- 2 files changed, 169 insertions(+), 9 deletions(-) diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index 004d44e6737..97b9847eae4 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -1,4 +1,5 @@ -import { _each, isEmpty } from '../src/utils.js'; +import { _each, deepSetValue, isEmpty } from '../src/utils.js'; +import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'fluct'; @@ -39,13 +40,12 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const serverRequests = []; - // TODO: is 'page' the right value here? - const referer = bidderRequest.refererInfo.page; + const page = bidderRequest.refererInfo.page; _each(validBidRequests, (request) => { const data = Object(); - data.referer = referer; + data.page = page; data.adUnitCode = request.adUnitCode; data.bidId = request.bidId; data.transactionId = request.transactionId; @@ -53,6 +53,21 @@ export const spec = { eids: (request.userIdAsEids || []).filter((eid) => SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) !== -1) }; + if (bidderRequest.gdprConsent) { + deepSetValue(data, 'regs.gdpr', { + consent: bidderRequest.gdprConsent.consentString, + gdprApplies: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + }); + } + if (bidderRequest.uspConsent) { + deepSetValue(data, 'regs.us_privacy', { + consent: bidderRequest.uspConsent, + }); + } + if (config.getConfig('coppa') === true) { + deepSetValue(data, 'regs.coppa', 1); + } + data.sizes = []; _each(request.sizes, (size) => { data.sizes.push({ @@ -141,8 +156,22 @@ export const spec = { * */ getUserSyncs: (syncOptions, serverResponses) => { - return []; - }, + // gdpr, us_privacy, and coppa params to be handled on the server end. + const usersyncs = serverResponses.reduce((acc, serverResponse) => [ + ...acc, + ...(serverResponse.body.usersyncs ?? []), + ], []); + const syncs = usersyncs.filter( + (sync) => ( + (sync['type'] === 'image' && syncOptions.pixelEnabled) || + (sync['type'] === 'iframe' && syncOptions.iframeEnabled) + ) + ).map((sync) => ({ + type: sync.type, + url: sync.url, + })); + return syncs; + } }; registerBidder(spec); diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js index 92a4b42f6f8..e9681c05314 100644 --- a/test/spec/modules/fluctBidAdapter_spec.js +++ b/test/spec/modules/fluctBidAdapter_spec.js @@ -54,6 +54,16 @@ describe('fluctAdapter', function () { }); describe('buildRequests', function () { + let sb; + + beforeEach(function () { + sb = sinon.sandbox.create(); + }); + + afterEach(function () { + sb.restore(); + }); + const bidRequests = [{ bidder: 'fluct', params: { @@ -70,7 +80,7 @@ describe('fluctAdapter', function () { }]; const bidderRequest = { refererInfo: { - referer: 'http://example.com' + page: 'http://example.com' } }; @@ -84,6 +94,11 @@ describe('fluctAdapter', function () { expect(request.url).to.equal('https://hb.adingo.jp/prebid?dfpUnitCode=%2F100000%2Funit_code&tagId=10000%3A100000001&groupId=1000000002'); }); + it('includes data.page by default', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.page).to.eql('http://example.com'); + }); + it('includes data.user.eids = [] by default', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.user.eids).to.eql([]); @@ -99,6 +114,11 @@ describe('fluctAdapter', function () { expect(request.data.schain).to.eql(undefined); }); + it('includes no data.regs by default', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.regs).to.eql(undefined); + }); + it('includes filtered user.eids if any exist', function () { const bidRequests2 = bidRequests.map( (bidReq) => Object.assign(bidReq, { @@ -211,6 +231,44 @@ describe('fluctAdapter', function () { ] }); }); + + it('includes data.regs.gdpr if bidderRequest.gdprConsent exists', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, bidderRequest, { + gdprConsent: { + consentString: 'gdpr-consent-string', + gdprApplies: true, + }, + }), + )[0]; + expect(request.data.regs.gdpr).to.eql({ + consent: 'gdpr-consent-string', + gdprApplies: 1, + }); + }); + + it('includes data.regs.us_privacy if bidderRequest.uspConsent exists', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, bidderRequest, { + uspConsent: 'usp-consent-string', + }), + )[0]; + expect(request.data.regs.us_privacy).to.eql({ + consent: 'usp-consent-string', + }); + }); + + it('includes data.regs.coppa if config.getConfig("coppa") is true', function () { + const cfg = { + coppa: true, + }; + sb.stub(config, 'getConfig').callsFake(key => cfg[key]); + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.regs.coppa).to.eql(1); + }); }); describe('interpretResponse', function() { @@ -247,9 +305,15 @@ describe('fluctAdapter', function () { adm: '', burl: 'https://i.adingo.jp/?test=1&et=hb&bidid=237f4d1a293f99', crid: 'test_creative', - adomain: ['test_adomain'] + adomain: ['test_adomain'], }] - }] + }], + usersyncs: [ + { + 'type': 'image', + 'url': 'https://cs.adingo.jp/sync', + }, + ], } }; @@ -338,4 +402,71 @@ describe('fluctAdapter', function () { expect(spec.interpretResponse({})).to.be.empty; }); }); + + describe('getUserSyncs', function () { + const syncOptions = {}; + const serverResponse = { + body: { + usersyncs: [ + { + type: 'image', + url: 'https://cs.adingo.jp/foo', + }, + { + type: 'image', + url: 'https://cs.adingo.jp/bar', + }, + { + type: 'iframe', + url: 'https://cs.adingo.jp/buz', + }, + ], + }, + }; + + it('returns no user syncs if syncOption.pixelEnabled !== true and syncOption.iframeEnabled !== true', function () { + const actual = spec.getUserSyncs( + syncOptions, + [serverResponse], + ); + + expect(actual).to.eql([]); + }); + + it('returns user syncs if syncOption.pixelEnabled === true', function () { + const actual = spec.getUserSyncs( + Object.assign({}, syncOptions, { + pixelEnabled: true, + }), + [serverResponse], + ); + + expect(actual).to.eql([ + { + type: 'image', + url: 'https://cs.adingo.jp/foo', + }, + { + type: 'image', + url: 'https://cs.adingo.jp/bar', + }, + ]); + }); + + it('returns user syncs if syncOption.iframeEnabled === true', function () { + const actual = spec.getUserSyncs( + Object.assign({}, syncOptions, { + iframeEnabled: true, + }), + [serverResponse], + ); + + expect(actual).to.eql([ + { + type: 'iframe', + url: 'https://cs.adingo.jp/buz', + }, + ]); + }); + }); }); From 8d435917a8d25920200e7b882462a179fbd1cfc1 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 23 Mar 2023 14:17:14 +0000 Subject: [PATCH 228/375] Prebid 7.42.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6a11d97b3c..64271d03b5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.42.0-pre", + "version": "7.42.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 978f40a52e2..76ed49a9130 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.42.0-pre", + "version": "7.42.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From c780b80cce3fc6d4391b813d17b255e9aaf72488 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 23 Mar 2023 14:17:15 +0000 Subject: [PATCH 229/375] Increment version to 7.43.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64271d03b5a..a0f5e1418d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.42.0", + "version": "7.43.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 76ed49a9130..560e952535b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.42.0", + "version": "7.43.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 8ed51d2bc914f5bd7c12d338f560dd473c11bbfa Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Fri, 24 Mar 2023 06:08:16 -0400 Subject: [PATCH 230/375] fix lint and test failures --- libraries/ortbConverter/processors/default.js | 26 - test/spec/modules/nexx360BidAdapter_spec.js | 80 +- test/spec/modules/rubiconBidAdapter_spec.js | 1528 +++++++++-------- 3 files changed, 808 insertions(+), 826 deletions(-) diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index ae6c7206693..00922608707 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -124,29 +124,3 @@ if (FEATURES.VIDEO) { fn: fillVideoResponse } } - -function fpdFromTopLevelConfig(prop) { - return { - priority: 90, // after FPD from 'ortb2', before the rest - fn(ortbRequest) { - const data = config.getConfig(prop); - if (typeof data === 'object') { - ortbRequest[prop] = mergeDeep({}, ortbRequest[prop], data); - } - } - } -} - -export function onlyOneClientSection(ortbRequest) { - ['dooh', 'app', 'site'].reduce((found, section) => { - if (ortbRequest[section] != null && Object.keys(ortbRequest[section]).length > 0) { - if (found != null) { - logWarn(`ORTB request specifies both '${found}' and '${section}'; dropping the latter.`) - delete ortbRequest[section]; - } else { - found = section; - } - } - return found; - }, null); -} diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 49807aa8b8b..9ba3ac0e5a4 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -379,46 +379,48 @@ describe('Nexx360 bid adapter tests', function () { expect(requestContent.ext.source).to.be.eql('prebid.js'); }); - it('We perform a test with a multiformat adunit', function() { - const multiformatBids = [...sampleBids]; - multiformatBids[0].mediaTypes = { - banner: { - sizes: [[300, 250], [300, 600]] - }, - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'], - protocols: [1, 2, 3, 4, 5, 6, 7, 8], - playbackmethod: [2], - skip: 1, - playback_method: ['auto_play_sound_off'] - } - }; - const request = spec.buildRequests(multiformatBids, bidderRequest); - const requestContent = request.data; - expect(requestContent.imp[0].video.ext.context).to.be.eql('outstream'); - expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); - }); + if (FEATURES.VIDEO) { + it('We perform a test with a multiformat adunit', function() { + const multiformatBids = [...sampleBids]; + multiformatBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + playback_method: ['auto_play_sound_off'] + } + }; + const request = spec.buildRequests(multiformatBids, bidderRequest); + const requestContent = request.data; + expect(requestContent.imp[0].video.ext.context).to.be.eql('outstream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }); - it('We perform a test with a instream adunit', function() { - const videoBids = [sampleBids[0]]; - videoBids[0].mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480], - mimes: ['video/mp4'], - protocols: [1, 2, 3, 4, 5, 6], - playbackmethod: [2], - skip: 1 - } - }; - const request = spec.buildRequests(videoBids, bidderRequest); - const requestContent = request.data; - expect(request).to.have.property('method').and.to.equal('POST'); - expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); - expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); - }) + it('We perform a test with a instream adunit', function() { + const videoBids = [sampleBids[0]]; + videoBids[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6], + playbackmethod: [2], + skip: 1 + } + }; + const request = spec.buildRequests(videoBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }) + } }); after(function () { sandbox.restore() diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 592fdbc6f9c..d535dc84411 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1538,709 +1538,711 @@ describe('the rubicon adapter', function () { }); }); - describe('for video requests', function () { - it('should make a well-formed video request', function () { - createVideoBidderRequest(); - - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); - - let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); - let post = request.data; - - expect(post).to.have.property('imp'); - // .with.length.of(1); - let imp = post.imp[0]; - expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(imp.exp).to.equal(undefined); // now undefined - expect(imp.video.w).to.equal(640); - expect(imp.video.h).to.equal(480); - expect(imp.video.pos).to.equal(1); - 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.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(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: $$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'); - // LiveIntent should exist - expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); - expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); - expect(post.user.ext.eids[0].uids[0].atype).to.equal(3); - expect(post.user.ext.eids[0]).to.have.property('ext').that.is.an('object'); - expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); - expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); - expect(post.user.ext.eids[0].ext.segments[1]).to.equal('segB'); - // LiveRamp should exist - expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); - expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); - expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); - // UnifiedId should exist - expect(post.user.ext.eids[2].source).to.equal('adserver.org'); - expect(post.user.ext.eids[2].uids[0].atype).to.equal(1); - expect(post.user.ext.eids[2].uids[0].id).to.equal('3000'); - // PubCommonId should exist - expect(post.user.ext.eids[3].source).to.equal('pubcid.org'); - expect(post.user.ext.eids[3].uids[0].atype).to.equal(1); - expect(post.user.ext.eids[3].uids[0].id).to.equal('4000'); - // example should exist - expect(post.user.ext.eids[4].source).to.equal('example.com'); - expect(post.user.ext.eids[4].uids[0].id).to.equal('333333'); - // id-partner.com - expect(post.user.ext.eids[5].source).to.equal('id-partner.com'); - expect(post.user.ext.eids[5].uids[0].id).to.equal('4444444'); - // CriteoId should exist - expect(post.user.ext.eids[6].source).to.equal('criteo.com'); - expect(post.user.ext.eids[6].uids[0].id).to.equal('1111'); - expect(post.user.ext.eids[6].uids[0].atype).to.equal(1); - - expect(post.regs.ext.gdpr).to.equal(1); - expect(post.regs.ext.us_privacy).to.equal('1NYN'); - expect(post).to.have.property('ext').that.is.an('object'); - expect(post.ext.prebid.targeting.includewinners).to.equal(true); - expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); - expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); - expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); - expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); - expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); - }); - - describe('ortb2imp sent to video bids', function () { - beforeEach(function () { - // initialize - if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { - delete bidderRequest.bids[0].ortb2Imp; - } - }); - - it('should add ortb values to video requests', function () { + if (FEATURES.VIDEO) { + describe('for video requests', function () { + it('should make a well-formed video request', function () { createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - bidderRequest.bids[0].ortb2Imp = { - ext: { - gpid: '/test/gpid', - data: { - pbadslot: '/test/pbadslot' - }, - prebid: { - storedauctionresponse: { - id: 'sample_video_response' - } - } - } - } - - 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'); // .with.length.of(1); let imp = post.imp[0]; - expect(imp.ext.gpid).to.equal('/test/gpid'); - expect(imp.ext.data.pbadslot).to.equal('/test/pbadslot'); - expect(imp.ext.prebid.storedauctionresponse.id).to.equal('sample_video_response'); - }); - }); + expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); + expect(imp.exp).to.equal(undefined); // now undefined + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.pos).to.equal(1); + 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.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(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: $$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'); + // LiveIntent should exist + expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); + expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); + expect(post.user.ext.eids[0].uids[0].atype).to.equal(3); + expect(post.user.ext.eids[0]).to.have.property('ext').that.is.an('object'); + expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); + expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); + expect(post.user.ext.eids[0].ext.segments[1]).to.equal('segB'); + // LiveRamp should exist + expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); + expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); + expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); + // UnifiedId should exist + expect(post.user.ext.eids[2].source).to.equal('adserver.org'); + expect(post.user.ext.eids[2].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[2].uids[0].id).to.equal('3000'); + // PubCommonId should exist + expect(post.user.ext.eids[3].source).to.equal('pubcid.org'); + expect(post.user.ext.eids[3].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[3].uids[0].id).to.equal('4000'); + // example should exist + expect(post.user.ext.eids[4].source).to.equal('example.com'); + expect(post.user.ext.eids[4].uids[0].id).to.equal('333333'); + // id-partner.com + expect(post.user.ext.eids[5].source).to.equal('id-partner.com'); + expect(post.user.ext.eids[5].uids[0].id).to.equal('4444444'); + // CriteoId should exist + expect(post.user.ext.eids[6].source).to.equal('criteo.com'); + expect(post.user.ext.eids[6].uids[0].id).to.equal('1111'); + expect(post.user.ext.eids[6].uids[0].atype).to.equal(1); + + expect(post.regs.ext.gdpr).to.equal(1); + expect(post.regs.ext.us_privacy).to.equal('1NYN'); + expect(post).to.have.property('ext').that.is.an('object'); + expect(post.ext.prebid.targeting.includewinners).to.equal(true); + expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); + expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); + expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); + }); + + describe('ortb2imp sent to video bids', function () { + beforeEach(function () { + // initialize + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; + } + }); - it('should correctly set bidfloor on imp when getfloor in scope', function () { - createVideoBidderRequest(); - // default getFloor response is empty object so should not break and not send hard_floor - bidderRequest.bids[0].getFloor = () => getFloorResponse; - sinon.spy(bidderRequest.bids[0], 'getFloor'); + it('should add ortb values to video requests', function () { + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/test/gpid', + data: { + pbadslot: '/test/pbadslot' + }, + prebid: { + storedauctionresponse: { + id: 'sample_video_response' + } + } + } + } - // make sure banner bid called with right stuff - expect( - bidderRequest.bids[0].getFloor.calledWith({ - currency: 'USD', - mediaType: '*', - size: '*' - }) - ).to.be.true; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - // not an object should work and not send - expect(request.data.imp[0].bidfloor).to.be.undefined; + expect(post).to.have.property('imp'); + // .with.length.of(1); + let imp = post.imp[0]; + expect(imp.ext.gpid).to.equal('/test/gpid'); + expect(imp.ext.data.pbadslot).to.equal('/test/pbadslot'); + expect(imp.ext.prebid.storedauctionresponse.id).to.equal('sample_video_response'); + }); + }); - // make it respond with a non USD floor should not send it - getFloorResponse = {currency: 'EUR', floor: 1.0}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.be.undefined; + it('should correctly set bidfloor on imp when getfloor in scope', function () { + createVideoBidderRequest(); + // default getFloor response is empty object so should not break and not send hard_floor + bidderRequest.bids[0].getFloor = () => getFloorResponse; + sinon.spy(bidderRequest.bids[0], 'getFloor'); - // make it respond with a non USD floor should not send it - getFloorResponse = {currency: 'EUR'}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.be.undefined; + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - // make it respond with USD floor and string floor - getFloorResponse = {currency: 'USD', floor: '1.23'}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.23); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // make it respond with USD floor and num floor - getFloorResponse = {currency: 'USD', floor: 1.23}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.23); - }); + // make sure banner bid called with right stuff + expect( + bidderRequest.bids[0].getFloor.calledWith({ + currency: 'USD', + mediaType: '*', + size: '*' + }) + ).to.be.true; - 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 = () => { - throw new Error('An exception!'); - }; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + // not an object should work and not send + expect(request.data.imp[0].bidfloor).to.be.undefined; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR', floor: 1.0}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.be.undefined; - // should have an imp - expect(request.data.imp).to.exist.and.to.be.a('array'); - expect(request.data.imp).to.have.lengthOf(1); + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.be.undefined; - // should be NO bidFloor - expect(request.data.imp[0].bidfloor).to.be.undefined; - expect(request.data.imp[0].bidfloorcur).to.be.undefined; - }); + // make it respond with USD floor and string floor + getFloorResponse = {currency: 'USD', floor: '1.23'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.23); - 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); + // make it respond with USD floor and num floor + getFloorResponse = {currency: 'USD', floor: 1.23}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.23); + }); - // should have the aliases object sent to PBS - expect(request.data.ext.prebid).to.haveOwnProperty('aliases'); - expect(request.data.ext.prebid.aliases).to.deep.equal({superRubicon: 'rubicon'}); + 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 = () => { + throw new Error('An exception!'); + }; + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - // should have the imp ext bidder params be under the alias name not rubicon superRubicon - 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'); - }); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - it('should add floors flag correctly to PBS Request', function () { - createVideoBidderRequest(); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // should have an imp + expect(request.data.imp).to.exist.and.to.be.a('array'); + expect(request.data.imp).to.have.lengthOf(1); - // should not pass if undefined - expect(request.data.ext.prebid.floors).to.be.undefined; + // should be NO bidFloor + expect(request.data.imp[0].bidfloor).to.be.undefined; + expect(request.data.imp[0].bidfloorcur).to.be.undefined; + }); - // should pass it as false - bidderRequest.bids[0].floorData = { - skipped: false, - location: 'fetch', - } - let [newRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(newRequest.data.ext.prebid.floors).to.deep.equal({ enabled: false }); - }); + 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); - it('should add multibid configuration to PBS Request', function () { - createVideoBidderRequest(); + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('aliases'); + expect(request.data.ext.prebid.aliases).to.deep.equal({superRubicon: 'rubicon'}); - const multibid = [{ - bidder: 'bidderA', - maxBids: 2 - }, { - bidder: 'bidderB', - maxBids: 2 - }]; - const expected = [{ - bidder: 'bidderA', - maxbids: 2 - }, { - bidder: 'bidderB', - maxbids: 2 - }]; + // should have the imp ext bidder params be under the alias name not rubicon superRubicon + 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'); + }); - config.setConfig({multibid: multibid}); + it('should add floors flag correctly to PBS Request', function () { + createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // should not pass if undefined + expect(request.data.ext.prebid.floors).to.be.undefined; - // should have the aliases object sent to PBS - expect(request.data.ext.prebid).to.haveOwnProperty('multibid'); - expect(request.data.ext.prebid.multibid).to.deep.equal(expected); - }); + // should pass it as false + bidderRequest.bids[0].floorData = { + skipped: false, + location: 'fetch', + } + let [newRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(newRequest.data.ext.prebid.floors).to.deep.equal({ enabled: false }); + }); - it('should pass client analytics to PBS endpoint if all modules included', function () { - createVideoBidderRequest(); - $$PREBID_GLOBAL$$.installedModules = []; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let payload = request.data; + it('should add multibid configuration to PBS Request', function () { + createVideoBidderRequest(); - expect(payload.ext.prebid.analytics).to.not.be.undefined; - expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); - }); + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); - it('should pass client analytics to PBS endpoint if rubicon analytics adapter is included', function () { - createVideoBidderRequest(); - $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter', 'rubiconAnalyticsAdapter']; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let payload = request.data; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(payload.ext.prebid.analytics).to.not.be.undefined; - expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); - }); + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('multibid'); + expect(request.data.ext.prebid.multibid).to.deep.equal(expected); + }); - it('should not pass client analytics to PBS endpoint if rubicon analytics adapter is not included', function () { - createVideoBidderRequest(); - $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter']; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let payload = request.data; + it('should pass client analytics to PBS endpoint if all modules included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = []; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; - expect(payload.ext.prebid.analytics).to.be.undefined; - }); + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); + }); - it('should send video exp param correctly when set', function () { - createVideoBidderRequest(); - config.setConfig({s2sConfig: {defaultTtl: 600}}); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; + it('should pass client analytics to PBS endpoint if rubicon analytics adapter is included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter', 'rubiconAnalyticsAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; - // should exp set to the right value according to config - let imp = post.imp[0]; - expect(imp.exp).to.equal(600); - }); + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); + }); - it('should not send video exp at all if not set in s2sConfig config', function () { - createVideoBidderRequest(); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; + it('should not pass client analytics to PBS endpoint if rubicon analytics adapter is not included', function () { + createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; - // should exp set to the right value according to config - let imp = post.imp[0]; - // bidderFactory stringifies request body before sending so removes undefined attributes: - expect(imp.exp).to.equal(undefined); - }); + expect(payload.ext.prebid.analytics).to.be.undefined; + }); - it('should send tmax as the bidderRequest timeout value', function () { - createVideoBidderRequest(); - bidderRequest.timeout = 3333; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; - expect(post.tmax).to.equal(3333); - }); + it('should send video exp param correctly when set', function () { + createVideoBidderRequest(); + config.setConfig({s2sConfig: {defaultTtl: 600}}); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - it('should send correct bidfloor to PBS', function () { - createVideoBidderRequest(); + // should exp set to the right value according to config + let imp = post.imp[0]; + expect(imp.exp).to.equal(600); + }); - bidderRequest.bids[0].params.floor = 0.1; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(0.1); + it('should not send video exp at all if not set in s2sConfig config', function () { + createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - bidderRequest.bids[0].params.floor = 5.5; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(5.5); + // should exp set to the right value according to config + let imp = post.imp[0]; + // bidderFactory stringifies request body before sending so removes undefined attributes: + expect(imp.exp).to.equal(undefined); + }); - bidderRequest.bids[0].params.floor = '1.7'; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.7); + it('should send tmax as the bidderRequest timeout value', function () { + createVideoBidderRequest(); + bidderRequest.timeout = 3333; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + expect(post.tmax).to.equal(3333); + }); - bidderRequest.bids[0].params.floor = 0; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(0); + it('should send correct bidfloor to PBS', function () { + createVideoBidderRequest(); - bidderRequest.bids[0].params.floor = undefined; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); + bidderRequest.bids[0].params.floor = 0.1; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(0.1); - bidderRequest.bids[0].params.floor = null; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); - }); + bidderRequest.bids[0].params.floor = 5.5; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(5.5); - it('should send request with proper ad position', function () { - createVideoBidderRequest(); - let positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].mediaTypes.video.pos = 1; - let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(1); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = undefined; - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(undefined); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'atf' - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(1); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'btf'; - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(3); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'foobar'; - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(undefined); - }); + bidderRequest.bids[0].params.floor = '1.7'; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.7); - it('should properly enforce video.context to be either instream or outstream', function () { - let bid = bidderRequest.bids[0]; - bid.mediaTypes = { - video: { - context: 'instream', - mimes: ['video/mp4', 'video/x-ms-wmv'], - protocols: [2, 5], - maxduration: 30, - linearity: 1, - api: [2] + bidderRequest.bids[0].params.floor = 0; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(0); + + bidderRequest.bids[0].params.floor = undefined; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); + + bidderRequest.bids[0].params.floor = null; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); + }); + + it('should send request with proper ad position', function () { + createVideoBidderRequest(); + let positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].mediaTypes.video.pos = 1; + let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(1); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = undefined; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(undefined); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'atf' + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(1); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'btf'; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(3); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'foobar'; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(undefined); + }); + + it('should properly enforce video.context to be either instream or outstream', function () { + let bid = bidderRequest.bids[0]; + bid.mediaTypes = { + video: { + context: 'instream', + mimes: ['video/mp4', 'video/x-ms-wmv'], + protocols: [2, 5], + maxduration: 30, + linearity: 1, + api: [2] + } } - } - bid.params.video = {}; + bid.params.video = {}; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const bidRequestCopy = utils.deepClone(bidderRequest.bids[0]); - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); + const bidRequestCopy = utils.deepClone(bidderRequest.bids[0]); + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - // change context to outstream, still true - bidRequestCopy.mediaTypes.video.context = 'outstream'; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); + // change context to outstream, still true + bidRequestCopy.mediaTypes.video.context = 'outstream'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - // change context to random, false now - bidRequestCopy.mediaTypes.video.context = 'random'; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // change context to random, false now + bidRequestCopy.mediaTypes.video.context = 'random'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - // change context to undefined, still false - bidRequestCopy.mediaTypes.video.context = undefined; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // change context to undefined, still false + bidRequestCopy.mediaTypes.video.context = undefined; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - // remove context, still false - delete bidRequestCopy.mediaTypes.video.context; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - }); + // remove context, still false + delete bidRequestCopy.mediaTypes.video.context; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + }); - it('should enforce the new required mediaTypes.video params', function () { - createVideoBidderRequest(); + it('should enforce the new required mediaTypes.video params', function () { + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - // change mimes to a non array, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.mimes = 'video/mp4'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // change mimes to a non array, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.mimes = 'video/mp4'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // delete mimes, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.mimes; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // delete mimes, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.mimes; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change protocols to an int not array of ints, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.protocols = 1; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // change protocols to an int not array of ints, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.protocols = 1; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // delete protocols, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.protocols; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // delete protocols, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.protocols; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change linearity to an string, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // change linearity to an string, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // delete linearity, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.linearity; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // delete linearity, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.linearity; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change api to an string, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.api = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + // change api to an string, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.api = 'string'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // delete api, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.api; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - }); + // delete api, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.api; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + }); - it('bid request is valid when video context is outstream', function () { - createVideoBidderRequestOutstream(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + it('bid request is valid when video context is outstream', function () { + createVideoBidderRequestOutstream(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const bidRequestCopy = utils.deepClone(bidderRequest); + const bidRequestCopy = utils.deepClone(bidderRequest); - let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - expect(request.data.imp[0].ext.prebid.bidder.rubicon.video.size_id).to.equal(203); - }); + let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); + 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 () { + it('should send banner request when outstream or instream video included but no rubicon video obect is present', function () { // add banner and video mediaTypes - bidderRequest.mediaTypes = { - banner: { - sizes: [[300, 250]] - }, - video: { - context: 'outstream' - } - }; - // no video object in rubicon params, so we should see one call made for banner + bidderRequest.mediaTypes = { + banner: { + sizes: [[300, 250]] + }, + video: { + context: 'outstream' + } + }; + // no video object in rubicon params, so we should see one call made for banner - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - let requests = spec.buildRequests(bidderRequest.bids, bidderRequest); + let requests = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); - bidderRequest.mediaTypes.video.context = 'instream'; + bidderRequest.mediaTypes.video.context = 'instream'; - requests = spec.buildRequests(bidderRequest.bids, bidderRequest); + requests = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); - }); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); - it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { - createVideoBidderRequestNoVideo(); + it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { + createVideoBidderRequestNoVideo(); - let bid = bidderRequest.bids[0]; - bid.mediaTypes.banner = { - sizes: [[300, 250]] - }; + let bid = bidderRequest.bids[0]; + bid.mediaTypes.banner = { + sizes: [[300, 250]] + }; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const bidRequestCopy = utils.deepClone(bidderRequest); + const bidRequestCopy = utils.deepClone(bidderRequest); - let requests = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); - }); + let requests = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); - it('should include coppa flag in video bid request', () => { - createVideoBidderRequest(); + it('should include coppa flag in video bid request', () => { + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': true - }; - return config[key]; + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.regs.coppa).to.equal(1); }); - const [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); - expect(request.data.regs.coppa).to.equal(1); - }); - it('should include first party data', () => { - createVideoBidderRequest(); + it('should include first party data', () => { + createVideoBidderRequest(); - const site = { - ext: { - data: { - page: 'home' - } - }, - content: { + const site = { + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{foo: 'bar'}] + }, + keywords: 'e,f', + rating: '4-star', data: [{foo: 'bar'}] - }, - keywords: 'e,f', - rating: '4-star', - data: [{foo: 'bar'}] - }; - const user = { - ext: { - data: { - age: 31 - } - }, - keywords: 'd', - gender: 'M', - yob: '1984', - geo: {country: 'ca'}, - data: [{foo: 'bar'}] - }; + }; + const user = { + ext: { + data: { + age: 31 + } + }, + keywords: 'd', + gender: 'M', + yob: '1984', + geo: {country: 'ca'}, + data: [{foo: 'bar'}] + }; - const ortb2 = { - site, - user - }; + const ortb2 = { + site, + user + }; - const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest); + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest); - const expected = { - site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), - user: Object.assign({}, user), - siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), - userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), - }; + const expected = { + site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), + user: Object.assign({}, user), + siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), + userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), + }; - delete request.data.site.page; - delete request.data.site.content.language; + delete request.data.site.page; + delete request.data.site.content.language; - expect(request.data.site.keywords).to.deep.equal('a,b,c'); - expect(request.data.user.keywords).to.deep.equal('d'); - expect(request.data.site.ext.data).to.deep.equal(expected.siteData); - expect(request.data.user.ext.data).to.deep.equal(expected.userData); - }); + expect(request.data.site.keywords).to.deep.equal('a,b,c'); + expect(request.data.user.keywords).to.deep.equal('d'); + expect(request.data.site.ext.data).to.deep.equal(expected.siteData); + expect(request.data.user.ext.data).to.deep.equal(expected.userData); + }); - it('should include pbadslot in bid request', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].ortb2Imp = { - ext: { - data: { - pbadslot: '1234567890' + it('should include pbadslot in bid request', function () { + createVideoBidderRequest(); + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '1234567890' + } } } - } - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); - }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); + }); - it('should NOT include storedrequests in pbs payload', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].ortb2 = { - ext: { - prebid: { - storedrequest: 'no-send-top-level-sr' + it('should NOT include storedrequests in pbs payload', function () { + createVideoBidderRequest(); + bidderRequest.bids[0].ortb2 = { + ext: { + prebid: { + storedrequest: 'no-send-top-level-sr' + } } } - } - bidderRequest.bids[0].ortb2Imp = { - ext: { - prebid: { - storedrequest: 'no-send-imp-sr' + bidderRequest.bids[0].ortb2Imp = { + ext: { + prebid: { + storedrequest: 'no-send-imp-sr' + } } } - } - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.storedrequest).to.be.undefined; - expect(request.data.imp[0].ext.prebid.storedrequest).to.be.undefined; - }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.ext.prebid.storedrequest).to.be.undefined; + expect(request.data.imp[0].ext.prebid.storedrequest).to.be.undefined; + }); - it('should include GAM ad unit in bid request', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].ortb2Imp = { - ext: { - data: { - adserver: { - adslot: '1234567890', - name: 'adServerName1' + it('should include GAM ad unit in bid request', function () { + createVideoBidderRequest(); + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '1234567890', + name: 'adServerName1' + } } } - } - }; + }; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); - }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); + }); - it('should use the integration type provided in the config instead of the default', () => { - createVideoBidderRequest(); - config.setConfig({rubicon: {int_type: 'testType'}}); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); - }); + it('should use the integration type provided in the config instead of the default', () => { + createVideoBidderRequest(); + config.setConfig({rubicon: {int_type: 'testType'}}); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); + }); - it('should pass the user.id provided in the config', function () { - config.setConfig({user: {id: '123'}}); - createVideoBidderRequest(); + it('should pass the user.id provided in the config', function () { + config.setConfig({user: {id: '123'}}); + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); - - let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); - let post = request.data; - - expect(post).to.have.property('imp') - // .with.length.of(1); - let imp = post.imp[0]; - expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(imp.exp).to.equal(undefined); - expect(imp.video.w).to.equal(640); - expect(imp.video.h).to.equal(480); - expect(imp.video.pos).to.equal(1); - 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.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(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); - expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - - // Config user.id - expect(post.user.id).to.equal('123'); - - expect(post.regs.ext.gdpr).to.equal(1); - expect(post.regs.ext.us_privacy).to.equal('1NYN'); - expect(post).to.have.property('ext').that.is.an('object'); - expect(post.ext.prebid.targeting.includewinners).to.equal(true); - expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); - expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); - expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); - expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); - expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); - }) - }); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); + + let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + let post = request.data; + + expect(post).to.have.property('imp') + // .with.length.of(1); + let imp = post.imp[0]; + expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); + expect(imp.exp).to.equal(undefined); + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.pos).to.equal(1); + 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.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(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); + expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + + // Config user.id + expect(post.user.id).to.equal('123'); + + expect(post.regs.ext.gdpr).to.equal(1); + expect(post.regs.ext.us_privacy).to.equal('1NYN'); + expect(post).to.have.property('ext').that.is.an('object'); + expect(post.ext.prebid.targeting.includewinners).to.equal(true); + expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); + expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); + expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); + }) + }); + } describe('combineSlotUrlParams', function () { it('should combine an array of slot url params', function () { @@ -3208,64 +3210,66 @@ describe('the rubicon adapter', function () { }); }); - describe('for video', function () { - beforeEach(function () { - createVideoBidderRequest(); - }); + if (FEATURES.VIDEO) { + describe('for video', function () { + beforeEach(function () { + createVideoBidderRequest(); + }); - it('should register a successful bid', function () { - let response = { - cur: 'USD', - seatbid: [{ - bid: [{ - id: '0', - impid: '/19968336/header-bid-tag-0', - adomain: ['test.com'], - price: 2, - crid: '4259970', - ext: { - bidder: { - rp: { - mime: 'application/javascript', - size_id: 201, - advid: 12345 - } - }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + it('should register a successful bid', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } }, - type: 'video' + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } } - } + }], + group: 0, + seat: 'rubicon' }], - group: 0, - seat: 'rubicon' - }], - }; + }; - const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - let bids = spec.interpretResponse({body: response}, {data: request}); + let bids = spec.interpretResponse({body: response}, {data: request}); - expect(bids).to.be.lengthOf(1); + expect(bids).to.be.lengthOf(1); - expect(bids[0].seatBidId).to.equal('0'); - expect(bids[0].creativeId).to.equal('4259970'); - expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); - expect(bids[0].mediaType).to.equal('video'); - expect(bids[0].meta.mediaType).to.equal('video'); - expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); - expect(bids[0].meta.advertiserId).to.equal(12345); - expect(bids[0].bidderCode).to.equal('rubicon'); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].width).to.equal(640); - expect(bids[0].height).to.equal(480); + expect(bids[0].seatBidId).to.equal('0'); + expect(bids[0].creativeId).to.equal('4259970'); + expect(bids[0].cpm).to.equal(2); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(480); + }); }); - }); + } if (FEATURES.NATIVE) { describe('for native', () => { @@ -3279,152 +3283,154 @@ describe('the rubicon adapter', function () { }); } - describe('for outstream video', function () { - const sandbox = sinon.createSandbox(); - beforeEach(function () { - createVideoBidderRequestOutstream(); - config.setConfig({rubicon: { - rendererConfig: { - align: 'left', - closeButton: true - }, - rendererUrl: 'https://example.test/renderer.js' - }}); - window.MagniteApex = { - renderAd: function() { - return null; + if (FEATURES.VIDEO) { + describe('for outstream video', function () { + const sandbox = sinon.createSandbox(); + beforeEach(function () { + createVideoBidderRequestOutstream(); + config.setConfig({rubicon: { + rendererConfig: { + align: 'left', + closeButton: true + }, + rendererUrl: 'https://example.test/renderer.js' + }}); + window.MagniteApex = { + renderAd: function() { + return null; + } } - } - }); + }); - afterEach(function () { - sandbox.restore(); - delete window.MagniteApex; - }); + afterEach(function () { + sandbox.restore(); + delete window.MagniteApex; + }); - it('should register a successful bid', function () { - let response = { - cur: 'USD', - seatbid: [{ - bid: [{ - id: '0', - impid: '/19968336/header-bid-tag-0', - adomain: ['test.com'], - price: 2, - crid: '4259970', - ext: { - bidder: { - rp: { - mime: 'application/javascript', - size_id: 201, - advid: 12345 - } - }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + it('should register a successful bid', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } }, - type: 'video' + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } } - } + }], + group: 0, + seat: 'rubicon' }], - group: 0, - seat: 'rubicon' - }], - }; - - const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - - let bids = spec.interpretResponse({body: response}, { data: request }); - - expect(bids).to.be.lengthOf(1); - - expect(bids[0].seatBidId).to.equal('0'); - expect(bids[0].creativeId).to.equal('4259970'); - expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); - expect(bids[0].mediaType).to.equal('video'); - expect(bids[0].meta.mediaType).to.equal('video'); - expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); - expect(bids[0].meta.advertiserId).to.equal(12345); - expect(bids[0].bidderCode).to.equal('rubicon'); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].width).to.equal(640); - expect(bids[0].height).to.equal(320); - // check custom renderer - expect(typeof bids[0].renderer).to.equal('object'); - expect(bids[0].renderer.getConfig()).to.deep.equal({ - align: 'left', - closeButton: true - }); - expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); - }); + }; - it('should render ad with Magnite renderer', function () { - let response = { - cur: 'USD', - seatbid: [{ - bid: [{ - id: '0', - impid: '/19968336/header-bid-tag-0', - adomain: ['test.com'], - price: 2, - crid: '4259970', - ext: { - bidder: { - rp: { - mime: 'application/javascript', - size_id: 201, - advid: 12345 + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + let bids = spec.interpretResponse({body: response}, { data: request }); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].seatBidId).to.equal('0'); + expect(bids[0].creativeId).to.equal('4259970'); + expect(bids[0].cpm).to.equal(2); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(320); + // check custom renderer + expect(typeof bids[0].renderer).to.equal('object'); + expect(bids[0].renderer.getConfig()).to.deep.equal({ + align: 'left', + closeButton: true + }); + expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); + }); + + it('should render ad with Magnite renderer', function () { + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' } }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' - }, - type: 'video' - } - }, - nurl: 'https://test.com/vast.xml' + nurl: 'https://test.com/vast.xml' + }], + group: 0, + seat: 'rubicon' }], - group: 0, - seat: 'rubicon' - }], - }; - - const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - - sinon.spy(window.MagniteApex, 'renderAd'); + }; - let bids = spec.interpretResponse({body: response}, {data: request}); - const bid = bids[0]; - bid.adUnitCode = 'outstream_video1_placement'; - const adUnit = document.createElement('div'); - adUnit.id = bid.adUnitCode; - document.body.appendChild(adUnit); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - bid.renderer.render(bid); + sinon.spy(window.MagniteApex, 'renderAd'); - const renderCall = window.MagniteApex.renderAd.getCall(0); - expect(renderCall.args[0]).to.deep.equal({ - closeButton: true, - collapse: true, - height: 320, - label: undefined, - placement: { - align: 'left', - attachTo: adUnit, - position: 'append', - }, - vastUrl: 'https://test.com/vast.xml', - width: 640 + let bids = spec.interpretResponse({body: response}, {data: request}); + const bid = bids[0]; + bid.adUnitCode = 'outstream_video1_placement'; + const adUnit = document.createElement('div'); + adUnit.id = bid.adUnitCode; + document.body.appendChild(adUnit); + + bid.renderer.render(bid); + + const renderCall = window.MagniteApex.renderAd.getCall(0); + expect(renderCall.args[0]).to.deep.equal({ + closeButton: true, + collapse: true, + height: 320, + label: undefined, + placement: { + align: 'left', + attachTo: adUnit, + position: 'append', + }, + vastUrl: 'https://test.com/vast.xml', + width: 640 + }); + // cleanup + adUnit.parentNode.removeChild(adUnit); }); - // cleanup - adUnit.parentNode.removeChild(adUnit); }); - }); + } describe('config with integration type', () => { it('should use the integration type provided in the config instead of the default', () => { From 9ef8d25eecb913db0727e3b4eacfd6aed0e96984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Besse?= Date: Fri, 24 Mar 2023 13:50:57 +0100 Subject: [PATCH 231/375] Adagio Bid Adapter : add new params `splitKeyword` and `dl` to bidRequest payload (#9694) * Add splitKeyword and dl params * Update the `dl` to `dataLayer` for user and check the type * Fix code convention, remove the toString on object properties --- modules/adagioBidAdapter.js | 41 +++++++++++++ modules/adagioBidAdapter.md | 6 ++ test/spec/modules/adagioBidAdapter_spec.js | 70 ++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 798ff499206..49f3fdc6e52 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -20,6 +20,7 @@ import { logInfo, logWarn, mergeDeep, + isStr, } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -924,6 +925,46 @@ export const spec = { adunit_position: getSlotPosition(bidRequest.params.adUnitElementId) // adUnitElementId à déplacer ??? }; + // Force the Split Keyword to be a String + if (bidRequest.params.splitKeyword) { + if (isStr(bidRequest.params.splitKeyword) || isNumber(bidRequest.params.splitKeyword)) { + bidRequest.params.splitKeyword = bidRequest.params.splitKeyword.toString(); + } else { + delete bidRequest.params.splitKeyword; + + logWarn(LOG_PREFIX, 'The splitKeyword param have been removed because the type is invalid, accepted type: number or string.'); + } + } + + // Force the Data Layer key and value to be a String + if (bidRequest.params.dataLayer) { + if (isStr(bidRequest.params.dataLayer) || isNumber(bidRequest.params.dataLayer) || isArray(bidRequest.params.dataLayer) || isFn(bidRequest.params.dataLayer)) { + logWarn(LOG_PREFIX, 'The dataLayer param is invalid, only object is accepted as a type.'); + delete bidRequest.params.dataLayer; + } else { + let invalidDlParam = false; + + bidRequest.params.dl = bidRequest.params.dataLayer + // Remove the dataLayer from the BidRequest to send the `dl` instead of the `dataLayer` + delete bidRequest.params.dataLayer + + Object.keys(bidRequest.params.dl).forEach((key) => { + if (bidRequest.params.dl[key]) { + if (isStr(bidRequest.params.dl[key]) || isNumber(bidRequest.params.dl[key])) { + bidRequest.params.dl[key] = bidRequest.params.dl[key].toString(); + } else { + invalidDlParam = true; + delete bidRequest.params.dl[key]; + } + } + }); + + if (invalidDlParam) { + logWarn(LOG_PREFIX, 'Some parameters of the dataLayer property have been removed because the type is invalid, accepted type: number or string.'); + } + } + } + Object.keys(features).forEach((prop) => { if (features[prop] === '') { delete features[prop]; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index e65a044c2fa..45f39fc6f2d 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -61,6 +61,8 @@ Below, the list of Adagio params and where they can be set. | debug | | x | | video | | x | | native | | x | +| splitKeyword | | x | +| dataLayer | | x | _* These params are deprecated in favor the Global configuration setup, see below._ @@ -115,6 +117,10 @@ var adUnits = [ context: 1, plcmttype: 2 }, + splitKeyword: 'splitrule-one', + dl: { + placement: '1234' + } } }] } diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 899ef392357..36e3f04b2a7 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -347,6 +347,76 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[0].features.url).to.not.exist; }); + it('should force split keyword param into a string', function() { + const bid01 = new BidRequestBuilder().withParams({ + splitKeyword: 1234 + }).build(); + const bid02 = new BidRequestBuilder().withParams({ + splitKeyword: ['1234'] + }).build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01, bid02], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.adUnits[0].params).to.exist; + expect(requests[0].data.adUnits[0].params.splitKeyword).to.exist; + expect(requests[0].data.adUnits[0].params.splitKeyword).to.equal('1234'); + expect(requests[0].data.adUnits[1].params.splitKeyword).to.not.exist; + }); + + it('should force key and value from data layer param into a string', function() { + const bid01 = new BidRequestBuilder().withParams({ + dataLayer: { + 1234: 'dlparam', + goodkey: 1234, + objectvalue: { + random: 'result' + }, + arrayvalue: ['1234'] + } + }).build(); + + const bid02 = new BidRequestBuilder().withParams({ + dataLayer: 'a random string' + }).build(); + + const bid03 = new BidRequestBuilder().withParams({ + dataLayer: 1234 + }).build(); + + const bid04 = new BidRequestBuilder().withParams({ + dataLayer: ['an array'] + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01, bid02, bid03, bid04], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.adUnits[0].params).to.exist; + expect(requests[0].data.adUnits[0].params.dataLayer).to.not.exist; + expect(requests[0].data.adUnits[0].params.dl).to.exist; + expect(requests[0].data.adUnits[0].params.dl['1234']).to.equal('dlparam'); + expect(requests[0].data.adUnits[0].params.dl.goodkey).to.equal('1234'); + expect(requests[0].data.adUnits[0].params.dl.objectvalue).to.not.exist; + expect(requests[0].data.adUnits[0].params.dl.arrayvalue).to.not.exist; + + expect(requests[0].data.adUnits[1].params).to.exist; + expect(requests[0].data.adUnits[1].params.dl).to.not.exist; + expect(requests[0].data.adUnits[1].params.dataLayer).to.not.exist; + + expect(requests[0].data.adUnits[2].params).to.exist; + expect(requests[0].data.adUnits[2].params.dl).to.not.exist; + expect(requests[0].data.adUnits[2].params.dataLayer).to.not.exist; + + expect(requests[0].data.adUnits[3].params).to.exist; + expect(requests[0].data.adUnits[3].params.dl).to.not.exist; + expect(requests[0].data.adUnits[3].params.dataLayer).to.not.exist; + }); + describe('With video mediatype', function() { context('Outstream video', function() { it('should logWarn if user does not set renderer.backupOnly: true', function() { From a6ef0c021064616b2653cf8878799437ba9e5fc7 Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Fri, 24 Mar 2023 05:56:01 -0700 Subject: [PATCH 232/375] GumGum Bid Adapter : send gpp data in bidrequest (#9707) * Add GPP support in the bid request * Change parameter name from applicabaleSection to gpp_sid --- modules/gumgumBidAdapter.js | 4 ++-- test/spec/modules/gumgumBidAdapter_spec.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 2cdafd26987..e9bdc955c1e 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -392,12 +392,12 @@ function buildRequests(validBidRequests, bidderRequest) { if (gppConsent) { data.gppConsent = { gppString: bidderRequest.gppConsent.gppString, - applicableSections: bidderRequest.gppConsent.applicableSections + gpp_sid: bidderRequest.gppConsent.applicableSections } } else if (!gppConsent && bidderRequest?.ortb2?.regs?.gpp) { data.gppConsent = { gppString: bidderRequest.ortb2.regs.gpp, - applicableSections: bidderRequest.ortb2.regs.gpp_sid + gpp_sid: bidderRequest.ortb2.regs.gpp_sid }; } if (coppa) { diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 80ca85801b6..71f356a83ae 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -491,7 +491,7 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data.gppConsent).to.exist; expect(bidRequest.data.gppConsent.gppString).to.equal(gppConsent.gppString); - expect(bidRequest.data.gppConsent.applicableSections).to.equal(gppConsent.applicableSections); + expect(bidRequest.data.gppConsent.gpp_sid).to.equal(gppConsent.applicableSections); expect(bidRequest.data.gppConsent.gppString).to.eq('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); }); it('should handle ortb2 parameters', function () { @@ -511,7 +511,7 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data.gppConsent).to.exist; expect(bidRequest.data.gppConsent.gppString).to.equal(undefined); - expect(bidRequest.data.gppConsent.applicableSections).to.equal(undefined); + expect(bidRequest.data.gppConsent.gpp_sid).to.equal(undefined); }); it('should handle ortb2 undefined parameters', function () { const ortb2 = { @@ -523,7 +523,7 @@ describe('gumgumAdapter', function () { const fakeBidRequest = { gppConsent: ortb2 }; const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data.gppConsent.gppString).to.eq(undefined) - expect(bidRequest.data.gppConsent.applicableSections).to.eq(undefined) + expect(bidRequest.data.gppConsent.gpp_sid).to.eq(undefined) }); it('should not set coppa parameter if coppa config is set to false', function () { config.setConfig({ From 09a3df9267298a82bf2c08075f15975e7bffc14e Mon Sep 17 00:00:00 2001 From: JulieLorin Date: Mon, 27 Mar 2023 16:58:24 +0200 Subject: [PATCH 233/375] FPD Enrichment: use low entropy method by default to fetch user agent data (#9711) --- src/fpd/enrichment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index d6b7d2ffb04..f812d8435d9 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -69,7 +69,7 @@ function winFallback(fn) { function getSUA() { const hints = config.getConfig('firstPartyData.uaHints'); - return Array.isArray(hints) && hints.length === 0 + return !Array.isArray(hints) || hints.length === 0 ? GreedyPromise.resolve(dep.getLowEntropySUA()) : dep.getHighEntropySUA(hints); } From 38b9a3fbe2136e1b363183277f37bddad2933e21 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Mon, 27 Mar 2023 18:46:51 +0300 Subject: [PATCH 234/375] Adkernel Bid Adapter: add adlive.io alias (#9714) * Adkernel Bid Adapter: adlive.io alias * Update adkernelBidAdapter.js --- modules/adkernelBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 5736c7c19ba..d5639f57c10 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -98,7 +98,8 @@ export const spec = { {code: 'sonic_twist'}, {code: 'displayioads'}, {code: 'rtbdemand_com'}, - {code: 'bidbuddy'} + {code: 'bidbuddy'}, + {code: 'adliveconnect'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 9045fa3e821d80a5a45b15998c23c04306515581 Mon Sep 17 00:00:00 2001 From: Paulius Imbrasas <880130+CremboC@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:50:52 +0100 Subject: [PATCH 235/375] Update Permutive RTD documentation (#9697) --- modules/permutiveRtdProvider.md | 138 +++++++++++++------------------- 1 file changed, 54 insertions(+), 84 deletions(-) diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 7d8f073357c..9399dffab93 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -13,7 +13,7 @@ gulp build --modules=rtdModule,permutiveRtdProvider > Note that the global RTD module, `rtdModule`, is a prerequisite of the Permutive RTD module. -You then need to enable the Permutive RTD in your Prebid configuration, using the below format: +You then need to enable the Permutive RTD in your Prebid configuration. Below is an example of the format: ```javascript pbjs.setConfig({ @@ -37,59 +37,16 @@ pbjs.setConfig({ The parameters below provide configurability for general behaviours of the RTD submodule, as well as enabling settings for specific use cases mentioned above (e.g. acbidders). -| Name | Type | Description | Default | -|------------------------|----------|-----------------------------------------------------------------------------------------------|---------| -| name | String | This should always be `permutive` | - | -| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | -| params | Object | | - | -| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` | -| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | -| params.transformations | Object[] | An array of configurations for ORTB2 user data transformations | | -| params.overwrites | Object | An object specifying functions for custom targeting logic for bidders. | - | - -##### The `transformations` parameter - -This array contains configurations for transformations we'll apply to the Permutive object in the ORTB2 `user.data` array. The results of these transformations will be appended to the `user.data` array that's attached to ORTB2 bid requests. - -###### Supported transformations - -| Name | ID | Config structure | Description | -|----------------|-----|---------------------------------------------------|--------------------------------------------------------------------------------------| -| IAB taxonomies | iab | { segtax: number, iabIds: Object} | Transform segment IDs from Permutive to IAB (note: alpha version, subject to change) | - -##### The `overwrites` parameter - -The keys for this object should match a bidder (e.g. `rubicon`), which then can define a function to overwrite the customer targeting logic. - -```javascript -{ - params: { - overwrites: { - rubicon: function customTargeting(bid, data, acEnabled, utils, defaultFn) { - if (defaultFn) { - bid = defaultFn(bid, data, acEnabled) - } - if (data.gam && data.gam.length) { - utils.deepSetValue(bid, 'params.visitor.permutive', data.gam) - } - } - } - } -} -``` - -###### `customTargeting` function parameters - -| Name | Description | -|--------------|--------------------------------------------------------------------------------| -| `bid` | The bid request object. | -| `data` | Permutive's targeting data read from localStorage. | -| `acEnabled` | Boolean stating whether Audience Connect is enabled via `acBidders`. | -| `utils` | An object of helpful utilities. `(deepSetValue, deepAccess, isFn, mergeDeep)`. | -| `defaultFn` | The default targeting function. | - - +## Parameters +{: .table .table-bordered .table-striped } +| Name | Type | Description | Default | +| ---------------------- | -------------------- | --------------------------------------------------------------------------------------------- | ------------------ | +| name | String | This should always be `permutive` | - | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | +| params | Object | | - | +| params.acBidders | String[] | An array of bidder codes to share cohorts with in certain versions of Prebid, see below | `[]` | +| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | #### Context @@ -100,7 +57,7 @@ As Prebid utilizes TCF vendor consent, for the Permutive RTD module to load, Per #### Instructions -1. Publisher enables GDPR rules within Prebid. +1. Publisher enables rules within Prebid GDPR module 2. Label Permutive as an exception, as shown below. ```javascript [ @@ -123,29 +80,58 @@ Before making any updates to this configuration, please ensure that this approac ## Cohort Activation with Permutive RTD Module -### _Enabling Standard Cohorts_ - **Note**: Publishers must be enabled on the above Permutive RTD Submodule to enable Standard Cohorts. -The acbidders config in the Permutive RTD module allows publishers to determine which demand partners (SSPs) will receive standard cohorts via the user.data ortb2 object. Cohorts will be sent in the `p_standard` key-value. +### _Enabling Publisher Cohorts_ + +#### Standard Cohorts + +The Permutive RTD module sets Standard Cohort IDs as bidder-specific ortb2.user.data first-party data, following the Prebid ortb2 convention. Cohorts will be sent in the `p_standard` key-value. + +For Prebid versions below 7.29.0, populate the acbidders config in the Permutive RTD with an array of bidder codes with whom you wish to share Standard Cohorts with. You also need to permission the bidders by communicating the bidder list to the Permutive team at strategicpartnershipops@permutive.com. -The Permutive RTD module sets standard cohort IDs as bidder-specific ortb2.user.data first-party data, following the Prebid ortb2 convention. +For Prebid versions 7.29.0 and above, do not populate bidder codes in acbidders for the purpose of sharing Standard Cohorts (Note: there may be other business needs that require you to populate acbidders for Prebid versions 7.29.0+, see Advertiser Cohorts below). To share Standard Cohorts with bidders in Prebid versions 7.29.0 and above, communicate the bidder list to the Permutive team at strategicpartnershipops@permutive.com. -There are **two** ways to assign which demand partner bidders (e.g. SSPs) will receive Standard Cohort information via the Audience Connector (acbidders) config: +#### _Bidder Specific Requirements for Standard Cohorts_ +For PubMatic or OpenX: Please ensure you are using Prebid.js 7.13 (or later) +For Xandr: Please ensure you are using Prebid.js 7.29 (or later) +For Equativ: Please ensure you are using Prebid.js 7.26 (or later) + +#### Custom Cohorts + +The Permutive RTD module also supports passing any of the **Custom** Cohorts created in the dashboard to some SSP partners for targeting +e.g. setting up publisher deals. For these activations, cohort IDs are set in bidder-specific locations per ad unit (custom parameters). + +Currently, bidders with known support for custom cohort targeting are: + +- Xandr +- Magnite + +When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. +There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. +Permutive cohorts will be sent in the permutive key-value. + + +### _Enabling Advertiser Cohorts_ + +If you are connecting to an Advertiser seat within Permutive to share Advertiser Cohorts, populate the acbidders config in the Permutive RTD with an array of bidder codes with whom you wish to share Advertiser Cohorts with. + +### _Managing acbidders_ + +If your business needs require you to populate acbidders with bidder codes based on the criteria above, there are **two** ways to manage it. #### Option 1 - Automated -New demand partner bidders may be added to the acbidders config directly within the Permutive Platform. +If you are using Prebid.js v7.13.0+, bidders may be added to or removed from the acbidders config directly within the Permutive Dashboard. **Permutive can do this on your behalf**. Simply contact your Permutive CSM with strategicpartnershipops@permutive.com on cc, indicating which bidders you would like added. -Or, a publisher may do this themselves within the UI using the below instructions. +Or, a publisher may do this themselves within the Permutive Dashboard using the below instructions. ##### Create Integration -In order to update acbidders via the Permutive dashboard, -it is necessary to first enable the prebid integration in the integrations page (settings). +In order to manage acbidders via the Permutive dashboard, it is necessary to first enable the Prebid integration via the integrations page (settings). **Note on Revenue Insights:** The prebid integration includes a feature for revenue insights, which is not required for the purpose of updating acbidders config. @@ -153,31 +139,15 @@ Please see [this document](https://support.permutive.com/hc/en-us/articles/36001 ##### Update acbidders -The input for the “Data Provider config” is currently a multi-input free text. -A valid “bidder code” needs to be entered in order to enable Standard Cohorts to be passed to the desired partner. -The [prebid Bidders page](https://docs.prebid.org/dev-docs/bidders.html) contains instructions and a link to a list of possible bidder codes. +The input for the “Data Provider config” is a multi-input free text. A valid “bidder code” needs to be entered in order to enable Standard or Advertiser Cohorts to be passed to the desired partner. The [prebid Bidders page](https://docs.prebid.org/dev-docs/bidders.html) contains instructions and a link to a list of possible bidder codes. -Acbidders can be added or removed from the list using this feature, however, this will not impact any acbidders that have been applied using the manual method below. +Bidders can be added or removed from acbidders using this feature, however, this will not impact any bidders that have been applied using the manual method below. #### Option 2 - Manual -As a secondary option, new demand partner bidders may be added manually. +As a secondary option, bidders may be added manually. -To do so, a Publisher may define which bidders should receive Standard Cohorts by +To do so, define which bidders should receive Standard or Advertiser Cohorts by including the _bidder code_ of any bidder in the `acBidders` array. -**Note:** If a Publisher ever needs to remove a manually-added bidder, the bidder will also need to be removed manually. - -### _Enabling Custom Cohort IDs for Targeting_ - -Separately from Standard Cohorts - The Permutive RTD module also supports passing any of the **custom** cohorts created in the dashboard to some SSP partners for targeting -e.g. setting up publisher deals. For these activations, cohort IDs are set in bidder-specific locations per ad unit (custom parameters). - -Currently, bidders with known support for custom cohort targeting are: - -- Xandr -- Magnite - -When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. -There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. -Permutive cohorts will be sent in the permutive key-value. +**Note:** If you ever need to remove a manually-added bidder, the bidder will also need to be removed manually. From 0eb76d7620c3bfccd254340fbc189985373fda07 Mon Sep 17 00:00:00 2001 From: Adish Rao <30475159+adish1997@users.noreply.github.com> Date: Mon, 27 Mar 2023 22:32:49 +0530 Subject: [PATCH 236/375] added fix and support for multibid module (#9602) Co-authored-by: adish --- modules/medianetAnalyticsAdapter.js | 176 ++++++++++++------ .../modules/medianetAnalyticsAdapter_spec.js | 28 ++- 2 files changed, 147 insertions(+), 57 deletions(-) diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index d2d3d2bf888..647a02fe7dc 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -8,8 +8,7 @@ import { logError, logInfo, triggerPixel, - uniques, - getHighestCpm + uniques } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; @@ -41,6 +40,7 @@ const PREBID_VERSION = $$PREBID_GLOBAL$$.version; const ERROR_CONFIG_JSON_PARSE = 'analytics_config_parse_fail'; const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; +const ERROR_WINNING_AUCTION_MISSING = 'winning_auction_missing'; const BID_SUCCESS = 1; const BID_NOBID = 2; const BID_TIMEOUT = 3; @@ -72,7 +72,7 @@ class ErrorLogger { this.evtid = 'projectevents'; this.project = 'prebidanalytics'; this.dn = pageDetails.domain || ''; - this.requrl = pageDetails.requrl || ''; + this.requrl = pageDetails.topmostLocation || ''; this.pbversion = PREBID_VERSION; this.cid = config.cid || ''; this.rd = additionalData; @@ -270,6 +270,52 @@ class AdSlot { } } +class BidWrapper { + constructor() { + this.bidReqs = []; + this.bidObjs = []; + } + + findReqBid(bidId) { + return this.bidReqs.find(bid => { + return bid['bidId'] === bidId + }); + } + + findBidObj(key, value) { + return this.bidObjs.find(bid => { + return bid[key] === value + }); + } + + addBidReq(bidRequest) { + this.bidReqs.push(bidRequest) + } + + addBidObj(bidObj) { + if (!(bidObj instanceof Bid)) { + bidObj = Bid.getInstance(bidObj); + } + const bidReq = this.findReqBid(bidObj.bidId); + if (bidReq instanceof Bid) { + bidReq.used = true; + } + this.bidObjs.push(bidObj); + } + + getAdSlotBids(adSlot) { + const bidResponses = this.getAdSlotBidObjs(adSlot); + return bidResponses.map((bid) => bid.getLoggingData()); + } + + getAdSlotBidObjs(adSlot) { + const bidResponses = this.bidObjs + .filter((bid) => bid.adUnitCode === adSlot); + const remResponses = this.bidReqs.filter(bid => !bid.used && bid.adUnitCode === adSlot); + return [...bidResponses, ...remResponses]; + } +} + class Bid { constructor(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes) { this.bidId = bidId; @@ -299,6 +345,9 @@ class Bid { this.floorPrice = undefined; this.floorRule = undefined; this.serverLatencyMillis = undefined; + this.used = false; + this.originalRequestId = bidId; + this.requestId = undefined; } get size() { @@ -308,8 +357,15 @@ class Bid { return this.width + 'x' + this.height; } + static getInstance(bidProps) { + const bidObj = new Bid(); + return bidProps && Object.assign(bidObj, bidProps); + } + getLoggingData() { return { + reqId: this.requestId || this.bidId, + ogReqId: this.originalRequestId, adid: this.adId, pvnm: this.bidder, src: this.src, @@ -341,7 +397,7 @@ class Auction { constructor(acid) { this.acid = acid; this.status = AUCTION_IN_PROGRESS; - this.bids = []; + this.bidWrapper = new BidWrapper(); this.adSlots = {}; this.auctionInitTime = undefined; this.auctionStartTime = undefined; @@ -378,24 +434,31 @@ class Auction { addSlot({ adUnitCode, supplyAdCode, mediaTypes, allMediaTypeSizes, tmax, adext, context }) { if (adUnitCode && this.adSlots[adUnitCode] === undefined) { this.adSlots[adUnitCode] = new AdSlot(tmax, supplyAdCode, context, adext); - this.addBid(new Bid('-1', DUMMY_BIDDER, 'client', '-1', adUnitCode, mediaTypes, allMediaTypeSizes)); + this.addBidObj(new Bid('-1', DUMMY_BIDDER, 'client', Date.now(), adUnitCode, mediaTypes, allMediaTypeSizes)); } } addBid(bid) { - this.bids.push(bid); + this.bidWrapper.addBidReq(bid); } - findBid(key, value) { - return this.bids.filter(bid => { - return bid[key] === value - })[0]; + addBidObj(bidObj) { + this.bidWrapper.addBidObj(bidObj) } - getAdslotBids(adslot) { - return this.bids - .filter((bid) => bid.adUnitCode === adslot) - .map((bid) => bid.getLoggingData()); + findReqBid(bidId) { + return this.bidWrapper.findReqBid(bidId) + } + + findBidObj(key, value) { + return this.bidWrapper.findBidObj(key, value) + } + + getAdSlotBids(adSlot) { + return this.bidWrapper.getAdSlotBids(adSlot); + } + getAdSlotBidObjs(adSlot) { + return this.bidWrapper.getAdSlotBidObjs(adSlot); } _mergeFieldsToLog(objParams) { @@ -494,41 +557,26 @@ function _getSizes(mediaTypes, sizes) { } } -/* - - The code is used to determine if the current bid is higher than the previous bid. - - If it is, then the code will return true and if not, it will return false. - */ -function canSelectCurrentBid(previousBid, currentBid) { - if (!(previousBid instanceof Bid)) return false; - - // For first bid response the previous bid will be containing bid request obj - // in which the cpm would be undefined so the current bid can directly be selected. - const isFirstBidResponse = previousBid.cpm === undefined && currentBid.cpm !== undefined; - if (isFirstBidResponse) return true; - - // if there are 2 bids, get the highest bid - const selectedBid = getHighestCpm(previousBid, currentBid); - - // Return true if selectedBid is currentBid, - // The timeToRespond field is used as an identifier for distinguishing - // between the current iterating bid and the previous bid. - return selectedBid.timeToRespond === currentBid.timeToRespond; -} - function bidResponseHandler(bid) { - const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId } = bid; - const {originalCpm, bidderCode, creativeId, adId, currency} = bid; + const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId, originalRequestId, bidder } = bid; + const {originalCpm, creativeId, adId, currency} = bid; if (!(auctions[auctionId] instanceof Auction)) { return; } - let bidObj = auctions[auctionId].findBid('bidId', requestId); - if (!canSelectCurrentBid(bidObj, bid)) { - return; + const reqId = originalRequestId || requestId; + const bidReq = auctions[auctionId].findReqBid(reqId); + + if (!(bidReq instanceof Bid)) return; + + let bidObj = auctions[auctionId].findBidObj('bidId', requestId); + let isBidOverridden = true; + if (!bidObj || bidObj.status === BID_SUCCESS) { + bidObj = {}; + isBidOverridden = false; } - Object.assign( - bidObj, - { cpm, width, height, mediaType, timeToRespond, dealId, creativeId }, + Object.assign(bidObj, bidReq, + { cpm, width, height, mediaType, timeToRespond, dealId, creativeId, originalRequestId, requestId }, { adId, currency } ); bidObj.floorPrice = deepAccess(bid, 'floorData.floorValue'); @@ -547,7 +595,7 @@ function bidResponseHandler(bid) { bidObj.status = BID_SUCCESS; } - if (bidderCode === MEDIANET_BIDDER_CODE && bid.ext instanceof Object) { + if (bidder === MEDIANET_BIDDER_CODE && bid.ext instanceof Object) { Object.assign( bidObj, { 'ext': bid.ext }, @@ -558,6 +606,7 @@ function bidResponseHandler(bid) { if (typeof bid.serverResponseTimeMs !== 'undefined') { bidObj.serverLatencyMillis = bid.serverResponseTimeMs; } + !isBidOverridden && auctions[auctionId].addBidObj(bidObj); } function noBidResponseHandler({ auctionId, bidId }) { @@ -567,11 +616,13 @@ function noBidResponseHandler({ auctionId, bidId }) { if (auctions[auctionId].hasEnded()) { return; } - let bidObj = auctions[auctionId].findBid('bidId', bidId); - if (!(bidObj instanceof Bid)) { + const bidReq = auctions[auctionId].findReqBid(bidId); + if (!(bidReq instanceof Bid) || bidReq.used) { return; } + const bidObj = {...bidReq}; bidObj.status = BID_NOBID; + auctions[auctionId].addBidObj(bidObj); } function bidTimeoutHandler(timedOutBids) { @@ -579,11 +630,13 @@ function bidTimeoutHandler(timedOutBids) { if (!(auctions[auctionId] instanceof Auction)) { return; } - let bidObj = auctions[auctionId].findBid('bidId', bidId); - if (!(bidObj instanceof Bid)) { + const bidReq = auctions[auctionId].findReqBid('bidId', bidId); + if (!(bidReq instanceof Bid) || bidReq.used) { return; } + const bidObj = {...bidReq}; bidObj.status = BID_TIMEOUT; + auctions[auctionId].addBidObj(bidObj); }) } @@ -614,13 +667,13 @@ function setTargetingHandler(params) { const winnerAdId = params[adunit][CONSTANTS.TARGETING_KEYS.AD_ID]; let winningBid; let bidAdIds = Object.keys(targetingObj).map(k => targetingObj[k]); - auctionObj.bids.filter((bid) => bidAdIds.indexOf(bid.adId) !== -1).map(function(bid) { + auctionObj.bidWrapper.bidObjs.filter((bid) => bidAdIds.indexOf(bid.adId) !== -1).map(function(bid) { bid.iwb = 1; if (bid.adId === winnerAdId) { winningBid = bid; } }); - auctionObj.bids.forEach(bid => { + auctionObj.bidWrapper.bidObjs.forEach(bid => { if (bid.bidder === DUMMY_BIDDER && bid.adUnitCode === adunit) { bid.iwb = bidAdIds.length === 0 ? 0 : 1; bid.width = deepAccess(winningBid, 'width'); @@ -633,16 +686,27 @@ function setTargetingHandler(params) { } function bidWonHandler(bid) { - const { auctionId, adUnitCode, adId } = bid; + const { auctionId, adUnitCode, adId, bidder, requestId, originalRequestId } = bid; if (!(auctions[auctionId] instanceof Auction)) { + new ErrorLogger(ERROR_WINNING_AUCTION_MISSING, { + adId, + auctionId, + adUnitCode, + bidder, + requestId, + originalRequestId + }).send(); return; } - let bidObj = auctions[auctionId].findBid('adId', adId); + let bidObj = auctions[auctionId].findBidObj('adId', adId); if (!(bidObj instanceof Bid)) { new ErrorLogger(ERROR_WINNING_BID_ABSENT, { - adId: adId, - acid: auctionId, + adId, + auctionId, adUnitCode, + bidder, + requestId, + originalRequestId }).send(); return; } @@ -696,13 +760,13 @@ function fireAuctionLog(acid, adtag, logType, adId) { let bidParams; if (logType === LOG_TYPE.RA) { - const winningBidObj = auctions[acid].findBid('adId', adId); + const winningBidObj = auctions[acid].findBidObj('adId', adId); if (!winningBidObj) return; const winLogData = winningBidObj.getLoggingData(); bidParams = [winLogData]; commonParams.lper = 1; } else { - bidParams = auctions[acid].getAdslotBids(adtag).map(({winner, ...restParams}) => restParams); + bidParams = auctions[acid].getAdSlotBids(adtag).map(({winner, ...restParams}) => restParams); delete commonParams.wts; } let mnetPresent = bidParams.filter(b => b.pvnm === MEDIANET_BIDDER_CODE).length > 0; diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index c408f23c4f4..e19c27cc2d3 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -30,9 +30,11 @@ const MOCK = { NO_BID: {'bidder': 'medianet', 'params': {'cid': 'test123', 'crid': '451466393', 'site': {}}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '303fa0c6-682f-4aea-8e4a-dc68f0d5c7d5', 'sizes': [[300, 250], [300, 600]], 'bidId': '28248b0e6aece2', 'bidderRequestId': '13fccf3809fe43', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}, BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}], BIDS_SAME_REQ_DIFF_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], + BIDS_SAME_REQ_DIFF_CPM_SAME_TIME: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], BIDS_SAME_REQ_EQUAL_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 286, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], BID_RESPONSES: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'appnexus', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aecd5', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'appnexus', 'hb_adid': '3e6e4bce5c8fb4', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'publisherId': 'test123', 'placementId': '451466393'}]}], - BID_REQUESTS: [{'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, {'bidderCode': 'appnexus', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'appnexus', 'params': {'publisherId': 'TEST_CID', 'placementId': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aecd5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}] + BID_REQUESTS: [{'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, {'bidderCode': 'appnexus', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'appnexus', 'params': {'publisherId': 'TEST_CID', 'placementId': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aecd5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}], + MULTI_BID_RESPONSES: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'bidA2', 'originalBidder': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aebecc', 'originalRequestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 3.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 3.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '3.00', 'pbMg': '3.20', 'pbHg': '3.29', 'pbAg': '3.25', 'pbDg': '3.29', 'pbCg': '3.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '3.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}] }; function performAuctionWithFloorConfig() { @@ -102,6 +104,14 @@ function performStandardAuctionMultiBidResponseNoWin() { events.emit(SET_TARGETING, MOCK.SET_TARGETING); } +function performMultiBidAuction() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + MOCK.MULTI_BID_RESPONSES.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); +} + function getQueryData(url, decode = false) { const queryArgs = url.split('?')[1].split('&'); return queryArgs.reduce((data, arg) => { @@ -309,6 +319,13 @@ describe('Media.net Analytics Adapter', function() { expect(winningBid.adid).equals('3e6e4bce5c8fb3'); }); + it('should pick winning bid if multibids with same request id and same time to respond', function() { + performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM_SAME_TIME); + let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); + medianetAnalytics.clearlogsQueue(); + }); + it('should pick winning bid if multibids with same request id and equal cpm', function() { performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_EQUAL_CPM); let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; @@ -348,5 +365,14 @@ describe('Media.net Analytics Adapter', function() { expect(errors.length).equals(1); expect(errors[0].event).equals(ERROR_WINNING_BID_ABSENT); }); + + it('can handle multi bid module', function () { + performMultiBidAuction(); + const queue = medianetAnalytics.getlogsQueue(); + expect(queue.length).equals(1); + const multiBidLog = queue.map((log) => getQueryData(log, true))[0]; + expect(multiBidLog.pvnm).to.have.ordered.members(['-2', 'medianet', 'medianet']); + expect(multiBidLog.status).to.have.ordered.members(['1', '1', '1']); + }) }); }); From faac597de4cb444ec0d80c633bed59f8875dc1d3 Mon Sep 17 00:00:00 2001 From: Zachary Carlin Date: Mon, 27 Mar 2023 14:15:09 -0400 Subject: [PATCH 237/375] Sonobi Bid Adapter: add IntentIq Id (#9649) * Add intentIq Id to trinity request. * Minimized function to needed components. * Added return statemen. * Fixed return statement linter rule. * Changes suggested in PR review. * Added date to trinity request. Updated tests. --------- Co-authored-by: Zac Carlin --- modules/sonobiBidAdapter.js | 65 +++++++++++++++++++++- test/spec/modules/sonobiBidAdapter_spec.js | 13 +++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 87358705cb5..6760a3c18ab 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -4,6 +4,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { userSync } from '../src/userSync.js'; +import { bidderSettings } from '../src/bidderSettings.js'; const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); @@ -49,6 +50,7 @@ export const spec = { return true; }, + /** * Make a server request from the list of BidRequests. * @@ -93,7 +95,7 @@ export const spec = { 'lib_name': 'prebid', 'lib_v': '$prebid.version$', 'us': 0, - + 'iqid': bidderSettings.get(BIDDER_CODE, 'storageAllowed') ? JSON.stringify(loadOrCreateFirstPartyData()) : null, }; const fpd = bidderRequest.ortb2; @@ -388,6 +390,67 @@ export function _getPlatform(context = window) { } return 'desktop'; } +/** + * Check for local storage + * Generate a UUID for the user if one does not exist in local storage + * Store the UUID in local storage for future use + * @return {object} firstPartyData - Data object containing first party information + */ +function loadOrCreateFirstPartyData() { + var localStorageEnabled; + + var FIRST_PARTY_KEY = '_iiq_fdata'; + var tryParse = function (data) { + try { + return JSON.parse(data); + } catch (err) { + return null; + } + }; + var readData = function (key) { + if (hasLocalStorage()) { + return window.localStorage.getItem(key); + } + return null; + }; + var hasLocalStorage = function () { + if (typeof localStorageEnabled != 'undefined') { return localStorageEnabled; } else { + try { + localStorageEnabled = !!window.localStorage; + return localStorageEnabled; + } catch (e) { + localStorageEnabled = false; + } + } + return false; + }; + var generateGUID = function () { + var d = new Date().getTime(); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); + }); + }; + var storeData = function (key, value) { + try { + if (hasLocalStorage()) { + window.localStorage.setItem(key, value); + } + } catch (error) { + return null; + } + }; + var firstPartyData = tryParse(readData(FIRST_PARTY_KEY)); + if (!firstPartyData || !firstPartyData.pcid) { + var firstPartyId = generateGUID(); + firstPartyData = { pcid: firstPartyId, pcidDate: Date.now() }; + } else if (firstPartyData && !firstPartyData.pcidDate) { + firstPartyData.pcidDate = Date.now(); + } + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); + return firstPartyData; +}; function newRenderer(adUnitCode, bid, rendererOptions = {}) { const renderer = Renderer.install({ diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 56ed4d5196e..b9bd0dc4d9f 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -238,6 +238,13 @@ describe('SonobiBidAdapter', function () { }); describe('.buildRequests', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + sonobi: { + storageAllowed: true + } + }; + }); let sandbox; beforeEach(function () { sinon.stub(userSync, 'canBidderRegisterSync'); @@ -389,6 +396,10 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.data.coppa).to.equal(0); }); + it('should have storageAllowed set to true', function () { + expect($$PREBID_GLOBAL$$.bidderSettings.sonobi.storageAllowed).to.be.true; + }); + it('should return a properly formatted request', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) const bidRequestsPageViewID = spec.buildRequests(bidRequest, bidderRequests) @@ -398,6 +409,8 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.data.ref).not.to.be.empty expect(bidRequests.data.s).not.to.be.empty expect(bidRequests.data.pv).to.equal(bidRequestsPageViewID.data.pv) + expect(JSON.parse(bidRequests.data.iqid).pcid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/) + expect(JSON.parse(bidRequests.data.iqid).pcidDate).to.match(/^[0-9]{13}$/) expect(bidRequests.data.hfa).to.not.exist expect(bidRequests.bidderRequests).to.eql(bidRequest); expect(bidRequests.data.ref).to.equal('overrides_top_window_location'); From 33d015d0785a4eb15b1c772e733ce9c6b47e619e Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Tue, 28 Mar 2023 06:42:40 -0600 Subject: [PATCH 238/375] Concert Bid Adapter: enable support for GPP consent and remove user sync (#9700) * collect EIDs for bid request * add ad slot positioning to payload * RPO-2012: Update local storage name-spacing for c_uid (#8) * Updates c_uid namespacing to be more specific for concert * fixes unit tests * remove console.log * RPO-2012: Add check for shared id (#9) * Adds check for sharedId * Updates cookie name * remove trailing comma * [RPO-3152] Enable Support for GPP Consent (#12) * Adds gpp consent integration to concert bid adapter * Update tests to check for gpp consent string param * removes user sync endpoint and tests * updates comment * cleans up consentAllowsPpid function * comment fix * rename variables for clarity * fixes conditional logic for consent allows function (#13) --------- Co-authored-by: antoin Co-authored-by: Antoin --- modules/concertBidAdapter.js | 65 ++++++-------- test/spec/modules/concertBidAdapter_spec.js | 93 +++------------------ 2 files changed, 36 insertions(+), 122 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index dcea60d5231..176729dd607 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -5,7 +5,6 @@ import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const BIDDER_CODE = 'concert'; const CONCERT_ENDPOINT = 'https://bids.concert.io'; -const USER_SYNC_URL = 'https://cdn.concert.io/lib/bids/sync.html'; export const spec = { code: BIDDER_CODE, @@ -47,10 +46,18 @@ export const spec = { optedOut: hasOptedOutOfPersonalization(), adapterVersion: '1.1.1', uspConsent: bidderRequest.uspConsent, - gdprConsent: bidderRequest.gdprConsent + gdprConsent: bidderRequest.gdprConsent, + gppConsent: bidderRequest.gppConsent, } }; + if (!payload.meta.gppConsent && bidderRequest.ortb2?.regs?.gpp) { + payload.meta.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + applicableSections: bidderRequest.ortb2.regs.gpp_sid + } + } + payload.slots = validBidRequests.map(bidRequest => { collectEid(eids, bidRequest); const adUnitElement = document.getElementById(bidRequest.adUnitCode) @@ -124,38 +131,6 @@ export const spec = { return bidResponses; }, - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @param {gdprConsent} object GDPR consent object. - * @param {uspConsent} string US Privacy String. - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncs = []; - if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) { - let params = []; - - if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { - params.push(`gdpr_applies=${gdprConsent.gdprApplies ? '1' : '0'}`); - } - if (gdprConsent && (typeof gdprConsent.consentString === 'string')) { - params.push(`gdpr_consent=${gdprConsent.consentString}`); - } - if (uspConsent && (typeof uspConsent === 'string')) { - params.push(`usp_consent=${uspConsent}`); - } - - syncs.push({ - type: 'iframe', - url: USER_SYNC_URL + (params.length > 0 ? `?${params.join('&')}` : '') - }); - } - return syncs; - }, - /** * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data @@ -229,16 +204,24 @@ function hasOptedOutOfPersonalization() { * @param {BidderRequest} bidderRequest Object which contains any data consent signals */ function consentAllowsPpid(bidderRequest) { - /* NOTE: We can't easily test GDPR consent, without the - * `consent-string` npm module; so will have to rely on that - * happening on the bid-server. */ - const uspConsent = !(bidderRequest?.uspConsent === 'string' && + let uspConsentAllows = true; + + // if a us privacy string was provided, but they explicitly opted out + if ( + typeof bidderRequest?.uspConsent === 'string' && bidderRequest?.uspConsent[0] === '1' && - bidderRequest?.uspConsent[2].toUpperCase() === 'Y'); + bidderRequest?.uspConsent[2].toUpperCase() === 'Y' // user has opted-out + ) { + uspConsentAllows = false; + } - const gdprConsent = bidderRequest?.gdprConsent && hasPurpose1Consent(bidderRequest?.gdprConsent); + /* + * True if the gdprConsent is null-y; or GDPR does not apply; or if purpose 1 consent was given. + * Much more nuanced GDPR requirements are tested on the bid server using the @iabtcf/core npm module; + */ + const gdprConsentAllows = hasPurpose1Consent(bidderRequest?.gdprConsent); - return (uspConsent || gdprConsent); + return (uspConsentAllows && gdprConsentAllows); } function collectEid(eids, bid) { diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 00626472ecb..d5e7140a9f7 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -57,8 +57,9 @@ describe('ConcertAdapter', function () { refererInfo: { page: 'https://www.google.com' }, - uspConsent: '1YYY', - gdprConsent: {} + uspConsent: '1YN-', + gdprConsent: {}, + gppConsent: {} }; bidResponse = { @@ -111,7 +112,7 @@ describe('ConcertAdapter', function () { expect(payload).to.have.property('meta'); expect(payload).to.have.property('slots'); - const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent']; + const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent']; const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType']; metaRequiredFields.forEach(function(field) { @@ -138,6 +139,14 @@ describe('ConcertAdapter', function () { expect(payload.meta.uid).to.not.equal(false); }); + it('should not generate uid if USP consent disallows', function() { + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, { ...bidRequest, uspConsent: '1YY' }); + const payload = JSON.parse(request.data); + + expect(payload.meta.uid).to.equal(false); + }); + it('should use sharedid if it exists', function() { storage.removeDataFromLocalStorage('c_nap'); const request = spec.buildRequests(bidRequests, { @@ -213,82 +222,4 @@ describe('ConcertAdapter', function () { expect(bids).to.have.lengthOf(0); }); }); - - describe('spec.getUserSyncs', function() { - it('should not register syncs when iframe is not enabled', function() { - const opts = { - iframeEnabled: false - } - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should not register syncs when the user has opted out', function() { - const opts = { - iframeEnabled: true - }; - storage.setDataInLocalStorage('c_nap', 'true'); - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: true - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=1'); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=0'); - }); - - it('should set gdpr consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - }); - - it('should set ccpa consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - uspConsent: '1YYY' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('usp_consent=1YY'); - }); - }); }); From bb81403721fcf8c9764ab87bcf46b3e0c6089452 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Tue, 28 Mar 2023 09:46:01 -0300 Subject: [PATCH 239/375] CORE: add bid to winningBids when marking as used (#9612) * sets bid.status to rendered * adds winnings bid when marking as used * updates tests --- package.json | 1 + src/prebid.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 560e952535b..176f496f5cc 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { + "serve": "gulp serve", "test": "gulp test", "lint": "gulp lint" }, diff --git a/src/prebid.js b/src/prebid.js index 9ad72409990..70653de0bcb 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1009,7 +1009,9 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { } if (bids.length > 0) { - bids[0].status = CONSTANTS.BID_STATUS.RENDERED; + const winningBid = bids[0]; + auctionManager.addWinningBid(winningBid); + winningBid.status = CONSTANTS.BID_STATUS.RENDERED; } }; From 11491f3cdffa7a5d29a30ee2a29223e3720f6616 Mon Sep 17 00:00:00 2001 From: philan15 <37775368+philan15@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:50:00 +0300 Subject: [PATCH 240/375] Display.io Bid Adapter: ad request parameters renaming, user session saving (#9553) * Display.io: ad request parameters renaming, user session saving * Display.io Bid Adapter: using StorageManager instead localStorage directly --- modules/displayioBidAdapter.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js index c3c6597dd1b..d46cc8ee309 100644 --- a/modules/displayioBidAdapter.js +++ b/modules/displayioBidAdapter.js @@ -2,12 +2,14 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {getWindowFromDocument, logWarn} from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; const ADAPTER_VERSION = '1.1.0'; const BIDDER_CODE = 'displayio'; const BID_TTL = 300; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const DEFAULT_CURRENCY = 'USD'; +const US_KEY = '_dio_us'; export const spec = { code: BIDDER_CODE, @@ -67,18 +69,26 @@ export const spec = { function getPayload (bid, bidderRequest) { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; - const userSession = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { - let r = Math.random() * 16 | 0; - let v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); + const storage = getStorageManager({bidderCode: BIDDER_CODE}); + const userSession = (() => { + let us = storage.getDataFromLocalStorage(US_KEY); + if (!us) { + us = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { + let r = Math.random() * 16 | 0; + let v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + storage.setDataInLocalStorage(US_KEY, us); + } + return us + })(); const { params, adUnitCode, bidId } = bid; const { siteId, placementId, renderURL, pageCategory, keywords } = params; const { refererInfo, uspConsent, gdprConsent } = bidderRequest; - const mediation = {consent: '-1', gdpr: '-1'}; + const mediation = {gdprConsent: '', gdpr: '-1'}; if (gdprConsent && 'gdprApplies' in gdprConsent) { if (gdprConsent.consentString !== undefined) { - mediation.consent = gdprConsent.consentString; + mediation.gdprConsent = gdprConsent.consentString; } if (gdprConsent.gdprApplies !== undefined) { mediation.gdpr = gdprConsent.gdprApplies ? '1' : '0'; @@ -110,7 +120,7 @@ function getPayload (bid, bidderRequest) { dnt: window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false, iabConsent: {}, mediation: { - consent: mediation.consent, + gdprConsent: mediation.gdprConsent, gdpr: mediation.gdpr, } }, From 9a180dbfc707978271c880cc24fa3420e2e8615f Mon Sep 17 00:00:00 2001 From: olafbuitelaar Date: Wed, 29 Mar 2023 17:34:55 +0200 Subject: [PATCH 241/375] CORE: allow to disable setting the pbjs global variable (#9568) * allow to disable settings the pbjs global variable * factored out all references to $$PREBID_GLOBAL$$ to use getGlobal instead updated the babel module, to directly call the getGlobal function removed eslint global exception, and added them as local exceptions * fix comments * make module use getGlobal * Isolate `installedModules` management from module namespaces * Use relative import paths in autogenerated code for `installedModules` * Remove $$PREBID_GLOBAL$$ macro ref from rubicon adapter * Revert "Remove $$PREBID_GLOBAL$$ macro ref from rubicon adapter" This reverts commit 16e25ddc536d0b96aae0b13130816812347e0128. --------- Co-authored-by: Demetrio Girardi --- .eslintrc.js | 1 - modules/addefendBidAdapter.js | 3 +- modules/adxcgAnalyticsAdapter.js | 3 +- modules/atsAnalyticsAdapter.js | 3 +- modules/consentManagement.js | 3 +- modules/consentManagementGpp.js | 3 +- modules/express.js | 12 +-- modules/id5AnalyticsAdapter.js | 3 +- modules/mabidderBidAdapter.js | 4 +- modules/marsmediaAnalyticsAdapter.js | 3 +- modules/medianetAnalyticsAdapter.js | 7 +- modules/medianetBidAdapter.js | 11 +-- modules/orbidderBidAdapter.js | 3 +- modules/pixfutureBidAdapter.js | 3 +- modules/pubCommonId.js | 5 +- modules/pubxaiAnalyticsAdapter.js | 3 +- modules/rivrAnalyticsAdapter.js | 3 +- modules/snigelBidAdapter.js | 3 +- modules/tappxBidAdapter.js | 3 +- modules/terceptAnalyticsAdapter.js | 3 +- modules/yuktamediaAnalyticsAdapter.js | 3 +- package.json | 1 + plugins/pbjsGlobals.js | 21 +++-- src/Renderer.js | 5 +- src/auction.js | 5 +- src/prebid.js | 120 +++++++++++++------------- src/prebidGlobal.js | 20 +++-- src/utils.js | 5 +- 28 files changed, 156 insertions(+), 106 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 06a5e81d9f5..fc3ad3afe66 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,7 +19,6 @@ module.exports = { 'import' ], globals: { - '$$PREBID_GLOBAL$$': false, 'BROWSERSTACK_USERNAME': false, 'BROWSERSTACK_KEY': false, 'FEATURES': 'readonly', diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js index f0a6852b084..d73c25935ee 100644 --- a/modules/addefendBidAdapter.js +++ b/modules/addefendBidAdapter.js @@ -1,4 +1,5 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'addefend'; @@ -16,7 +17,7 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { let bid = { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, auctionId: false, pageId: false, gdpr_applies: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : 'true', diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index f3bb1270334..21b6c1be783 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -3,6 +3,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; +import {getGlobal} from '../src/prebidGlobal.js'; /** * Analytics adapter from adxcg.com @@ -122,7 +123,7 @@ function send (data) { ats: adxcgAnalyticsAdapter.context.auctionTimestamp, aav: adxcgAnalyticsVersion, iob: intersectionObserverAvailable(window) ? '1' : '0', - pbv: $$PREBID_GLOBAL$$.version, + pbv: getGlobal().version, sz: window.screen.width + 'x' + window.screen.height } }); diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index 0c0227ea34a..448969c6f29 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -4,6 +4,7 @@ import CONSTANTS from '../src/constants.json'; import adaptermanager from '../src/adapterManager.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; export const storage = getStorageManager(); @@ -352,7 +353,7 @@ atsAnalyticsAdapter.callHandler = function (evtype, args) { let bidWonTimeout = atsAnalyticsAdapter.context.bidWonTimeout ? atsAnalyticsAdapter.context.bidWonTimeout : 2000; let events = []; setTimeout(() => { - let winningBids = $$PREBID_GLOBAL$$.getAllWinningBids(); + let winningBids = getGlobal().getAllWinningBids(); logInfo('ATS Analytics - winning bids: ', winningBids) // prepare format data for sending to analytics endpoint if (handlerRequest.length) { diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 83e5c4aab13..6c22b3b9da4 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -11,6 +11,7 @@ import {includes} from '../src/polyfill.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -367,7 +368,7 @@ export function setConsentConfig(config) { } } if (!addedConsentHook) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + getGlobal().requestBids.before(requestBidsHook, 50); } addedConsentHook = true; gdprDataHandler.enable(); diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index 8a9c3f999f0..3b0e1c25c6a 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -10,6 +10,7 @@ import {gppDataHandler} from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import { enrichFPD } from '../src/fpd/enrichment.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -362,7 +363,7 @@ export function setConsentConfig(config) { logInfo('consentManagement.gpp module has been activated...'); if (!addedConsentHook) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + getGlobal().requestBids.before(requestBidsHook, 50); } addedConsentHook = true; gppDataHandler.enable(); diff --git a/modules/express.js b/modules/express.js index 0b1780e3c26..a2998baed07 100644 --- a/modules/express.js +++ b/modules/express.js @@ -1,6 +1,8 @@ import { logMessage, logWarn, logError, logInfo } from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const MODULE_NAME = 'express'; +const pbjsInstance = getGlobal(); /** * Express Module @@ -12,7 +14,7 @@ const MODULE_NAME = 'express'; * * @param {Object[]} [adUnits = pbjs.adUnits] - an array of adUnits for express to operate on. */ -$$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { +pbjsInstance.express = function(adUnits = pbjsInstance.adUnits) { logMessage('loading ' + MODULE_NAME); if (adUnits.length === 0) { @@ -138,10 +140,10 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { } if (adUnits.length) { - $$PREBID_GLOBAL$$.requestBids({ + pbjsInstance.requestBids({ adUnits: adUnits, bidsBackHandler: function () { - $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); + pbjsInstance.setTargetingForGPTAsync(); fGptRefresh.apply(pads(), [ adUnits.map(function (adUnit) { return gptSlotCache[adUnit.code]; @@ -168,10 +170,10 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { } if (adUnits.length) { - $$PREBID_GLOBAL$$.requestBids({ + pbjsInstance.requestBids({ adUnits: adUnits, bidsBackHandler: function () { - $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); + pbjsInstance.setTargetingForGPTAsync(); fGptRefresh.apply(pads(), [ adUnits.map(function (adUnit) { return gptSlotCache[adUnit.code]; diff --git a/modules/id5AnalyticsAdapter.js b/modules/id5AnalyticsAdapter.js index d35cdf29fca..d0f3198e03d 100644 --- a/modules/id5AnalyticsAdapter.js +++ b/modules/id5AnalyticsAdapter.js @@ -4,6 +4,7 @@ import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; import { logInfo, logError } from '../src/utils.js'; import * as events from '../src/events.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const { EVENTS: { @@ -34,7 +35,7 @@ const FLUSH_EVENTS = [ const CONFIG_URL_PREFIX = 'https://api.id5-sync.com/analytics' const TZ = new Date().getTimezoneOffset(); -const PBJS_VERSION = $$PREBID_GLOBAL$$.version; +const PBJS_VERSION = getGlobal().version; const ID5_REDACTED = '__ID5_REDACTED__'; const isArray = Array.isArray; diff --git a/modules/mabidderBidAdapter.js b/modules/mabidderBidAdapter.js index 12da791d2c4..632403c6643 100644 --- a/modules/mabidderBidAdapter.js +++ b/modules/mabidderBidAdapter.js @@ -1,5 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import {getGlobal} from '../src/prebidGlobal.js'; + const BIDDER_CODE = 'mabidder'; export const baseUrl = 'https://prebid.ecdrsvc.com/bid'; export const spec = { @@ -33,7 +35,7 @@ export const spec = { url: baseUrl, method: 'POST', data: { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, bids: bids, url: bidderRequest.refererInfo.page || '', referer: bidderRequest.refererInfo.ref || '', diff --git a/modules/marsmediaAnalyticsAdapter.js b/modules/marsmediaAnalyticsAdapter.js index c86cc4dfbc2..f1e53a3c20c 100644 --- a/modules/marsmediaAnalyticsAdapter.js +++ b/modules/marsmediaAnalyticsAdapter.js @@ -1,6 +1,7 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; /**** * Mars Media Analytics @@ -33,7 +34,7 @@ var marsmediaAnalyticsAdapter = Object.assign(adapter( success: function() {}, error: function() {} }, - JSON.stringify({act: 'prebid_analytics', params: events, 'pbjs': $$PREBID_GLOBAL$$.getBidResponses(), ver: MARS_VERSION}), + JSON.stringify({act: 'prebid_analytics', params: events, 'pbjs': getGlobal().getBidResponses(), ver: MARS_VERSION}), { method: 'POST' } diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 647a02fe7dc..b902727a730 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -17,6 +17,7 @@ import {ajax} from '../src/ajax.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../src/auction.js'; import {includes} from '../src/polyfill.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -36,7 +37,7 @@ const PRICE_GRANULARITY = { const MEDIANET_BIDDER_CODE = 'medianet'; // eslint-disable-next-line no-undef -const PREBID_VERSION = $$PREBID_GLOBAL$$.version; +const PREBID_VERSION = getGlobal().version; const ERROR_CONFIG_JSON_PARSE = 'analytics_config_parse_fail'; const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; @@ -886,8 +887,8 @@ medianetAnalytics.enableAnalytics = function (configuration) { logError('Media.net Analytics adapter: cid is required.'); return; } - $$PREBID_GLOBAL$$.medianetGlobals = $$PREBID_GLOBAL$$.medianetGlobals || {}; - $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled = true; + getGlobal().medianetGlobals = getGlobal().medianetGlobals || {}; + getGlobal().medianetGlobals.analyticsEnabled = true; pageDetails = new PageDetail(); diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 01652a3fac0..5f54b2e3ff3 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -17,6 +17,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {Renderer} from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'medianet'; const BID_URL = 'https://prebid.media.net/rtb/prebid'; @@ -51,7 +52,7 @@ const aliases = [ { code: 'aax', gvlid: 720 }, ]; -$$PREBID_GLOBAL$$.medianetGlobals = $$PREBID_GLOBAL$$.medianetGlobals || {}; +getGlobal().medianetGlobals = getGlobal().medianetGlobals || {}; function getTopWindowReferrer() { try { @@ -177,7 +178,7 @@ function extParams(bidRequest, bidderRequests) { const coppaApplies = !!(config.getConfig('coppa')); return Object.assign({}, { customer_id: params.cid }, - { prebid_version: $$PREBID_GLOBAL$$.version }, + { prebid_version: getGlobal().version }, { gdpr_applies: gdprApplies }, (gdprApplies) && { gdpr_consent_string: gdpr.consentString || '' }, { usp_applies: uspApplies }, @@ -185,7 +186,7 @@ function extParams(bidRequest, bidderRequests) { {coppa_applies: coppaApplies}, windowSize.w !== -1 && windowSize.h !== -1 && { screen: windowSize }, userId && { user_id: userId }, - $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled && { analytics: true }, + getGlobal().medianetGlobals.analyticsEnabled && { analytics: true }, !isEmpty(sChain) && {schain: sChain} ); } @@ -358,7 +359,7 @@ function getLoggingData(event, data) { params.evtid = 'projectevents'; params.project = 'prebid'; params.acid = deepAccess(data, '0.auctionId') || ''; - params.cid = $$PREBID_GLOBAL$$.medianetGlobals.cid || ''; + params.cid = getGlobal().medianetGlobals.cid || ''; params.crid = data.map((adunit) => deepAccess(adunit, 'params.0.crid') || adunit.adUnitCode).join('|'); params.adunit_count = data.length || 0; params.dn = mnData.urlData.domain || ''; @@ -442,7 +443,7 @@ export const spec = { return false; } - Object.assign($$PREBID_GLOBAL$$.medianetGlobals, !$$PREBID_GLOBAL$$.medianetGlobals.cid && {cid: bid.params.cid}); + Object.assign(getGlobal().medianetGlobals, !getGlobal().medianetGlobals.cid && {cid: bid.params.cid}); return true; }, diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 9979b1fdc3b..b84c67ba6d2 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const storageManager = getStorageManager({bidderCode: 'orbidder'}); @@ -96,7 +97,7 @@ export const spec = { method: 'POST', options: { withCredentials: true }, data: { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, pageUrl: referer, bidId: bidRequest.bidId, auctionId: bidRequest.auctionId, diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 41b1151e72a..5019b31b90b 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -14,6 +14,7 @@ import { transformBidderParamKeywords } from '../src/utils.js'; import { auctionManager } from '../src/auctionManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const SOURCE = 'pbjs'; const storageManager = getStorageManager({bidderCode: 'pixfuture'}); @@ -132,7 +133,7 @@ export const spec = { method: 'POST', options: {withCredentials: true}, data: { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, pageUrl: referer, bidId: bidRequest.bidId, auctionId: bidRequest.auctionId, diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 1fde8f8db5b..a32e26ef6c2 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -10,6 +10,7 @@ import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const storage = getStorageManager({moduleName: 'pubCommonId', gvlid: VENDORLESS_GVLID}); @@ -168,7 +169,7 @@ export function getPubcidConfig() { return pubcidConfig; } */ export const requestBidHook = timedAuctionHook('pubCommonId', function requestBidHook(next, config) { - let adUnits = config.adUnits || $$PREBID_GLOBAL$$.adUnits; + let adUnits = config.adUnits || getGlobal().adUnits; let pubcid = null; // Pass control to the next function if not enabled @@ -292,7 +293,7 @@ export function initPubcid() { (storage.hasLocalStorage() && readValue(OPTOUT_NAME, LOCAL_STORAGE)); if (!optout) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidHook); + getGlobal().requestBids.before(requestBidHook); } } diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 96665c786bf..19a3c236942 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -3,6 +3,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; +import {getGlobal} from '../src/prebidGlobal.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -180,7 +181,7 @@ function send(data, status) { search: { auctionTimestamp: auctionTimestamp, pubxaiAnalyticsVersion: pubxaiAnalyticsVersion, - prebidVersion: $$PREBID_GLOBAL$$.version + prebidVersion: getGlobal().version } }); if (status == 'bidwon') { diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index cf14ff44571..c74ce519ab9 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -2,6 +2,7 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const analyticsType = 'endpoint'; @@ -20,7 +21,7 @@ rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; // override enableAnalytics so we can get access to the config passed in from the page rivrAnalytics.enableAnalytics = (config) => { if (window.rivraddon && window.rivraddon.analytics) { - window.rivraddon.analytics.enableAnalytics(config, {utils, ajax, pbjsGlobalVariable: $$PREBID_GLOBAL$$}); + window.rivraddon.analytics.enableAnalytics(config, {utils, ajax, pbjsGlobalVariable: getGlobal()}); rivrAnalytics.originEnableAnalytics(config); } }; diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index e0ec10c6ed6..f41fb98d436 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {deepAccess, isArray, isFn, isPlainObject} from '../src/utils.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'snigel'; const GVLID = 1076; @@ -33,7 +34,7 @@ export const spec = { test: getTestFlag(), devw: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, devh: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight, - version: $$PREBID_GLOBAL$$.version, + version: getGlobal().version, gdprApplies: gdprApplies, gdprConsentString: gdprApplies === true ? deepAccess(bidderRequest, 'gdprConsent.consentString') : undefined, gdprConsentProv: gdprApplies === true ? deepAccess(bidderRequest, 'gdprConsent.addtlConsent') : undefined, diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 1a189f5271d..a9d08415090 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -6,6 +6,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import {parseDomain} from '../src/refererDetection.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'tappx'; const GVLID_CODE = 628; @@ -473,7 +474,7 @@ function buildOneRequest(validBidRequests, bidderRequest) { payload.regs = regs; // < Payload - let pbjsv = ($$PREBID_GLOBAL$$.version !== null) ? encodeURIComponent($$PREBID_GLOBAL$$.version) : -1; + let pbjsv = (getGlobal().version !== null) ? encodeURIComponent(getGlobal().version) : -1; return { method: 'POST', diff --git a/modules/terceptAnalyticsAdapter.js b/modules/terceptAnalyticsAdapter.js index 9d215f0ceda..c17948d73d0 100644 --- a/modules/terceptAnalyticsAdapter.js +++ b/modules/terceptAnalyticsAdapter.js @@ -3,6 +3,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; +import {getGlobal} from '../src/prebidGlobal.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -123,7 +124,7 @@ function send(data, status) { search: { auctionTimestamp: auctionTimestamp, terceptAnalyticsVersion: terceptAnalyticsVersion, - prebidVersion: $$PREBID_GLOBAL$$.version + prebidVersion: getGlobal().version } }); diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 31c6daae7f6..a16e4ec8d36 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -6,6 +6,7 @@ import CONSTANTS from '../src/constants.json'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {includes as strIncludes} from '../src/polyfill.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const storage = getStorageManager(); const yuktamediaAnalyticsVersion = 'v3.1.0'; @@ -34,7 +35,7 @@ const _pageInfo = { referer: referer, refererDomain: parseUrl(referer).host, yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, - prebidVersion: $$PREBID_GLOBAL$$.version + prebidVersion: getGlobal().version }; function getParameterByName(param) { diff --git a/package.json b/package.json index 176f496f5cc..ad625cb3045 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "prebid" ], "globalVarName": "pbjs", + "defineGlobal": true, "author": "the prebid.js contributors", "license": "Apache-2.0", "engines": { diff --git a/plugins/pbjsGlobals.js b/plugins/pbjsGlobals.js index c1a08133e8e..4c8685db840 100644 --- a/plugins/pbjsGlobals.js +++ b/plugins/pbjsGlobals.js @@ -1,4 +1,3 @@ - let t = require('@babel/core').types; let prebid = require('../package.json'); const path = require('path'); @@ -28,10 +27,12 @@ function getNpmVersion(version) { module.exports = function(api, options) { const pbGlobal = options.globalVarName || prebid.globalVarName; + const defineGlobal = typeof (options.defineGlobal) !== 'undefined' ? options.defineGlobal : prebid.defineGlobal; const features = featureMap(options.disableFeatures); let replace = { '$prebid.version$': prebid.version, '$$PREBID_GLOBAL$$': pbGlobal, + '$$DEFINE_PREBID_GLOBAL$$': defineGlobal, '$$REPO_AND_VERSION$$': `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}`, '$$PREBID_DIST_URL_BASE$$': options.prebidDistUrlBase || `https://cdn.jsdelivr.net/npm/prebid.js@${getNpmVersion(prebid.version)}/dist/` }; @@ -42,6 +43,10 @@ module.exports = function(api, options) { const PREBID_ROOT = path.resolve(__dirname, '..'); + function relPath(from, toRelToProjectRoot) { + return path.relative(path.dirname(from), path.join(PREBID_ROOT, toRelToProjectRoot)); + } + function getModuleName(filename) { const modPath = path.parse(path.relative(PREBID_ROOT, filename)); if (modPath.ext.toLowerCase() !== '.js') { @@ -63,8 +68,14 @@ module.exports = function(api, options) { Program(path, state) { const modName = getModuleName(state.filename); if (modName != null) { - // append "registration" of module file to $$PREBID_GLOBAL$$.installedModules - path.node.body.push(...api.parse(`window.${pbGlobal}.installedModules.push('${modName}');`, {filename: state.filename}).program.body); + // append "registration" of module file to getGlobal().installedModules + let i = 0; + let registerName; + do { + registerName = `__r${i++}` + } while (path.scope.hasBinding(registerName)) + path.node.body.unshift(...api.parse(`import {registerModule as ${registerName}} from '${relPath(state.filename, 'src/prebidGlobal.js')}';`, {filename: state.filename}).program.body); + path.node.body.push(...api.parse(`${registerName}('${modName}');`, {filename: state.filename}).program.body); } }, StringLiteral(path) { @@ -72,7 +83,7 @@ module.exports = function(api, options) { if (path.node.value.includes(name)) { path.node.value = path.node.value.replace( new RegExp(escapeRegExp(name), 'g'), - replace[name] + replace[name].toString() ); } }); @@ -102,7 +113,7 @@ module.exports = function(api, options) { ); } else { path.replaceWith( - t.Identifier(replace[name]) + t.Identifier(replace[name].toString()) ); } } diff --git a/src/Renderer.js b/src/Renderer.js index 97e37084e89..cdee9c79e63 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -3,6 +3,9 @@ import { logError, logWarn, logMessage, deepAccess } from './utils.js'; import {find} from './polyfill.js'; +import {getGlobal} from './prebidGlobal.js'; + +const pbjsInstance = getGlobal(); const moduleCode = 'outstream'; /** @@ -129,7 +132,7 @@ export function executeRenderer(renderer, bid, doc) { } function isRendererPreferredFromAdUnit(adUnitCode) { - const adUnits = $$PREBID_GLOBAL$$.adUnits; + const adUnits = pbjsInstance.adUnits; const adUnit = find(adUnits, adUnit => { return adUnit.code === adUnitCode; }); diff --git a/src/auction.js b/src/auction.js index a1e0fe13573..2cb93b82ef7 100644 --- a/src/auction.js +++ b/src/auction.js @@ -94,6 +94,7 @@ import {GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {createBid} from './bidfactory.js'; import {adjustCpm} from './utils/cpm.js'; +import {getGlobal} from './prebidGlobal.js'; const { syncUsers } = userSync; @@ -111,6 +112,8 @@ const outstandingRequests = {}; const sourceInfo = {}; const queuedCalls = []; +const pbjsInstance = getGlobal(); + /** * Clear global state for tests */ @@ -216,7 +219,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a const bids = _bidsReceived .filter(bind.call(adUnitsFilter, this, adUnitCodes)) .reduce(groupByPlacement, {}); - _callback.apply($$PREBID_GLOBAL$$, [bids, timedOut, _auctionId]); + _callback.apply(pbjsInstance, [bids, timedOut, _auctionId]); _callback = null; } } catch (e) { diff --git a/src/prebid.js b/src/prebid.js index 70653de0bcb..7ddebed6fd9 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -52,7 +52,7 @@ import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; -const $$PREBID_GLOBAL$$ = getGlobal(); +const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; /* private variables */ @@ -67,22 +67,22 @@ const eventValidators = { loadSession(); /* Public vars */ -$$PREBID_GLOBAL$$.bidderSettings = $$PREBID_GLOBAL$$.bidderSettings || {}; +pbjsInstance.bidderSettings = pbjsInstance.bidderSettings || {}; // let the world know we are loaded -$$PREBID_GLOBAL$$.libLoaded = true; +pbjsInstance.libLoaded = true; // version auto generated from build -$$PREBID_GLOBAL$$.version = 'v$prebid.version$'; +pbjsInstance.version = 'v$prebid.version$'; logInfo('Prebid.js v$prebid.version$ loaded'); -$$PREBID_GLOBAL$$.installedModules = $$PREBID_GLOBAL$$.installedModules || []; +pbjsInstance.installedModules = pbjsInstance.installedModules || []; // create adUnit array -$$PREBID_GLOBAL$$.adUnits = $$PREBID_GLOBAL$$.adUnits || []; +pbjsInstance.adUnits = pbjsInstance.adUnits || []; // Allow publishers who enable user sync override to trigger their sync -$$PREBID_GLOBAL$$.triggerUserSyncs = triggerUserSyncs; +pbjsInstance.triggerUserSyncs = triggerUserSyncs; function checkDefinedPlacement(id) { var adUnitCodes = auctionManager.getBidsRequested().map(bidSet => bidSet.bids.map(bid => bid.adUnitCode)) @@ -277,12 +277,12 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { * @alias module:pbjs.getAdserverTargetingForAdUnitCodeStr * @return {Array} returnObj return bids array */ -$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { +pbjsInstance.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr', arguments); // call to retrieve bids array if (adunitCode) { - var res = $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode(adunitCode); + var res = pbjsInstance.getAdserverTargetingForAdUnitCode(adunitCode); return transformAdServerTargetingObj(res); } else { logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); @@ -295,7 +295,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { * @alias module:pbjs.getHighestUnusedBidResponseForAdUnitCode * @returns {Object} returnObj return bid */ -$$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCode) { +pbjsInstance.getHighestUnusedBidResponseForAdUnitCode = function (adunitCode) { if (adunitCode) { const bid = auctionManager.getAllBidsForAdUnitCode(adunitCode) .filter(isBidUsable) @@ -312,8 +312,8 @@ $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCod * @alias module:pbjs.getAdserverTargetingForAdUnitCode * @returns {Object} returnObj return bids */ -$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function (adUnitCode) { - return $$PREBID_GLOBAL$$.getAdserverTargeting(adUnitCode)[adUnitCode]; +pbjsInstance.getAdserverTargetingForAdUnitCode = function (adUnitCode) { + return pbjsInstance.getAdserverTargeting(adUnitCode)[adUnitCode]; }; /** @@ -322,7 +322,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function (adUnitCode) { * @alias module:pbjs.getAdserverTargeting */ -$$PREBID_GLOBAL$$.getAdserverTargeting = function (adUnitCode) { +pbjsInstance.getAdserverTargeting = function (adUnitCode) { logInfo('Invoking $$PREBID_GLOBAL$$.getAdserverTargeting', arguments); return targeting.getAllTargeting(adUnitCode); }; @@ -341,7 +341,7 @@ function getConsentMetadata() { } } -$$PREBID_GLOBAL$$.getConsentMetadata = function () { +pbjsInstance.getConsentMetadata = function () { logInfo('Invoking $$PREBID_GLOBAL$$.getConsentMetadata'); return getConsentMetadata(); }; @@ -372,7 +372,7 @@ function getBids(type) { * @return {Object} map | object that contains the bidRequests */ -$$PREBID_GLOBAL$$.getNoBids = function () { +pbjsInstance.getNoBids = function () { logInfo('Invoking $$PREBID_GLOBAL$$.getNoBids', arguments); return getBids('getNoBids'); }; @@ -384,7 +384,7 @@ $$PREBID_GLOBAL$$.getNoBids = function () { * @return {Object} bidResponse object */ -$$PREBID_GLOBAL$$.getNoBidsForAdUnitCode = function (adUnitCode) { +pbjsInstance.getNoBidsForAdUnitCode = function (adUnitCode) { const bids = auctionManager.getNoBids().filter(bid => bid.adUnitCode === adUnitCode); return { bids }; }; @@ -395,7 +395,7 @@ $$PREBID_GLOBAL$$.getNoBidsForAdUnitCode = function (adUnitCode) { * @return {Object} map | object that contains the bidResponses */ -$$PREBID_GLOBAL$$.getBidResponses = function () { +pbjsInstance.getBidResponses = function () { logInfo('Invoking $$PREBID_GLOBAL$$.getBidResponses', arguments); return getBids('getBidsReceived'); }; @@ -407,7 +407,7 @@ $$PREBID_GLOBAL$$.getBidResponses = function () { * @return {Object} bidResponse object */ -$$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode = function (adUnitCode) { +pbjsInstance.getBidResponsesForAdUnitCode = function (adUnitCode) { const bids = auctionManager.getBidsReceived().filter(bid => bid.adUnitCode === adUnitCode); return { bids }; }; @@ -418,7 +418,7 @@ $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode = function (adUnitCode) { * @param {function(object)} customSlotMatching gets a GoogleTag slot and returns a filter function for adUnitCode, so you can decide to match on either eg. return slot => { return adUnitCode => { return slot.getSlotElementId() === 'myFavoriteDivId'; } }; * @alias module:pbjs.setTargetingForGPTAsync */ -$$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { +pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForGPTAsync', arguments); if (!isGptPubadsDefined()) { logError('window.googletag is not defined on the page'); @@ -451,7 +451,7 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes * @alias module:pbjs.setTargetingForAst */ -$$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) { +pbjsInstance.setTargetingForAst = function (adUnitCodes) { logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); if (!targeting.isApntagDefined()) { logError('window.apntag is not defined on the page'); @@ -484,7 +484,7 @@ function reinjectNodeIfRemoved(node, doc, tagName) { * @param {string} id bid id to locate the ad * @alias module:pbjs.renderAd */ -$$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { +pbjsInstance.renderAd = hook('async', function (doc, id, options) { logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); logMessage('Calling renderAd with adId :' + id); @@ -531,8 +531,8 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { // video module const adUnitCode = bid.adUnitCode; - const adUnit = $$PREBID_GLOBAL$$.adUnits.filter(adUnit => adUnit.code === adUnitCode); - const videoModule = $$PREBID_GLOBAL$$.videoModule; + const adUnit = pbjsInstance.adUnits.filter(adUnit => adUnit.code === adUnitCode); + const videoModule = pbjsInstance.videoModule; if (adUnit.video && videoModule) { videoModule.renderBid(adUnit.video.divId, bid); return; @@ -589,11 +589,11 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { * @param {string| Array} adUnitCode the adUnitCode(s) to remove * @alias module:pbjs.removeAdUnit */ -$$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { +pbjsInstance.removeAdUnit = function (adUnitCode) { logInfo('Invoking $$PREBID_GLOBAL$$.removeAdUnit', arguments); if (!adUnitCode) { - $$PREBID_GLOBAL$$.adUnits = []; + pbjsInstance.adUnits = []; return; } @@ -606,9 +606,9 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { } adUnitCodes.forEach((adUnitCode) => { - for (let i = $$PREBID_GLOBAL$$.adUnits.length - 1; i >= 0; i--) { - if ($$PREBID_GLOBAL$$.adUnits[i].code === adUnitCode) { - $$PREBID_GLOBAL$$.adUnits.splice(i, 1); + for (let i = pbjsInstance.adUnits.length - 1; i >= 0; i--) { + if (pbjsInstance.adUnits[i].code === adUnitCode) { + pbjsInstance.adUnits.splice(i, 1); } } }); @@ -624,7 +624,7 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { * @param {String} requestOptions.auctionId * @alias module:pbjs.requestBids */ -$$PREBID_GLOBAL$$.requestBids = (function() { +pbjsInstance.requestBids = (function() { const delegate = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId, ttlBuffer, ortb2, metrics, defer } = {}) { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); @@ -653,7 +653,7 @@ $$PREBID_GLOBAL$$.requestBids = (function() { // if the request does not specify adUnits, clone the global adUnit array; // otherwise, if the caller goes on to use addAdUnits/removeAdUnits, any asynchronous logic // in any hook might see their effects. - let adUnits = req.adUnits || $$PREBID_GLOBAL$$.adUnits; + let adUnits = req.adUnits || pbjsInstance.adUnits; req.adUnits = (isArray(adUnits) ? adUnits.slice() : [adUnits]); req.metrics = newMetrics(); @@ -761,7 +761,7 @@ export function executeCallbacks(fn, reqBidsConfigObj) { } // This hook will execute all storage callbacks which were registered before gdpr enforcement hook was added. Some bidders, user id modules use storage functions when module is parsed but gdpr enforcement hook is not added at that stage as setConfig callbacks are yet to be called. Hence for such calls we execute all the stored callbacks just before requestBids. At this hook point we will know for sure that gdprEnforcement module is added or not -$$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); +pbjsInstance.requestBids.before(executeCallbacks, 49); /** * @@ -769,9 +769,9 @@ $$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); * @param {Array|Object} adUnitArr Array of adUnits or single adUnit Object. * @alias module:pbjs.addAdUnits */ -$$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { +pbjsInstance.addAdUnits = function (adUnitArr) { logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, isArray(adUnitArr) ? adUnitArr : [adUnitArr]); + pbjsInstance.adUnits.push.apply(pbjsInstance.adUnits, isArray(adUnitArr) ? adUnitArr : [adUnitArr]); // emit event events.emit(ADD_AD_UNITS); }; @@ -792,7 +792,7 @@ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { * * Currently `bidWon` is the only event that accepts an `id` parameter. */ -$$PREBID_GLOBAL$$.onEvent = function (event, handler, id) { +pbjsInstance.onEvent = function (event, handler, id) { logInfo('Invoking $$PREBID_GLOBAL$$.onEvent', arguments); if (!isFn(handler)) { logError('The event handler provided is not a function and was not set on event "' + event + '".'); @@ -813,7 +813,7 @@ $$PREBID_GLOBAL$$.onEvent = function (event, handler, id) { * @param {string} id an identifier in the context of the event (see `$$PREBID_GLOBAL$$.onEvent`) * @alias module:pbjs.offEvent */ -$$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { +pbjsInstance.offEvent = function (event, handler, id) { logInfo('Invoking $$PREBID_GLOBAL$$.offEvent', arguments); if (id && !eventValidators[event].call(null, id)) { return; @@ -827,7 +827,7 @@ $$PREBID_GLOBAL$$.offEvent = function (event, handler, id) { * * @alias module:pbjs.getEvents */ -$$PREBID_GLOBAL$$.getEvents = function () { +pbjsInstance.getEvents = function () { logInfo('Invoking $$PREBID_GLOBAL$$.getEvents'); return events.getEvents(); }; @@ -838,7 +838,7 @@ $$PREBID_GLOBAL$$.getEvents = function () { * @param {string} bidderCode [description] * @alias module:pbjs.registerBidAdapter */ -$$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { +pbjsInstance.registerBidAdapter = function (bidderAdaptor, bidderCode) { logInfo('Invoking $$PREBID_GLOBAL$$.registerBidAdapter', arguments); try { adapterManager.registerBidAdapter(bidderAdaptor(), bidderCode); @@ -852,7 +852,7 @@ $$PREBID_GLOBAL$$.registerBidAdapter = function (bidderAdaptor, bidderCode) { * @param {Object} options [description] * @alias module:pbjs.registerAnalyticsAdapter */ -$$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { +pbjsInstance.registerAnalyticsAdapter = function (options) { logInfo('Invoking $$PREBID_GLOBAL$$.registerAnalyticsAdapter', arguments); try { adapterManager.registerAnalyticsAdapter(options); @@ -867,7 +867,7 @@ $$PREBID_GLOBAL$$.registerAnalyticsAdapter = function (options) { * @alias module:pbjs.createBid * @return {Object} bidResponse [description] */ -$$PREBID_GLOBAL$$.createBid = function (statusCode) { +pbjsInstance.createBid = function (statusCode) { logInfo('Invoking $$PREBID_GLOBAL$$.createBid', arguments); return createBid(statusCode); }; @@ -899,14 +899,14 @@ const enableAnalyticsCb = hook('async', function (config) { } }, 'enableAnalyticsCb'); -$$PREBID_GLOBAL$$.enableAnalytics = function (config) { +pbjsInstance.enableAnalytics = function (config) { enableAnalyticsCallbacks.push(enableAnalyticsCb.bind(this, config)); }; /** * @alias module:pbjs.aliasBidder */ -$$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { +pbjsInstance.aliasBidder = function (bidderCode, alias, options) { logInfo('Invoking $$PREBID_GLOBAL$$.aliasBidder', arguments); if (bidderCode && alias) { adapterManager.aliasBidAdapter(bidderCode, alias, options); @@ -918,9 +918,9 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { /** * @alias module:pbjs.aliasRegistry */ -$$PREBID_GLOBAL$$.aliasRegistry = adapterManager.aliasRegistry; +pbjsInstance.aliasRegistry = adapterManager.aliasRegistry; config.getConfig('aliasRegistry', config => { - if (config.aliasRegistry === 'private') delete $$PREBID_GLOBAL$$.aliasRegistry; + if (config.aliasRegistry === 'private') delete pbjsInstance.aliasRegistry; }); /** @@ -962,7 +962,7 @@ config.getConfig('aliasRegistry', config => { * Get all of the bids that have been rendered. Useful for [troubleshooting your integration](http://prebid.org/dev-docs/prebid-troubleshooting-guide.html). * @return {Array} A list of bids that have been rendered. */ -$$PREBID_GLOBAL$$.getAllWinningBids = function () { +pbjsInstance.getAllWinningBids = function () { return auctionManager.getAllWinningBids(); }; @@ -970,7 +970,7 @@ $$PREBID_GLOBAL$$.getAllWinningBids = function () { * Get all of the bids that have won their respective auctions. * @return {Array} A list of bids that have won their respective auctions. */ -$$PREBID_GLOBAL$$.getAllPrebidWinningBids = function () { +pbjsInstance.getAllPrebidWinningBids = function () { return auctionManager.getBidsReceived() .filter(bid => bid.status === CONSTANTS.BID_STATUS.BID_TARGETING_SET); }; @@ -982,7 +982,7 @@ $$PREBID_GLOBAL$$.getAllPrebidWinningBids = function () { * @alias module:pbjs.getHighestCpmBids * @return {Array} array containing highest cpm bid object(s) */ -$$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { +pbjsInstance.getHighestCpmBids = function (adUnitCode) { return targeting.getWinningBids(adUnitCode); }; @@ -994,7 +994,7 @@ $$PREBID_GLOBAL$$.getHighestCpmBids = function (adUnitCode) { * * @alias module:pbjs.markWinningBidAsUsed */ -$$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { +pbjsInstance.markWinningBidAsUsed = function (markBidRequest) { let bids = []; if (markBidRequest.adUnitCode && markBidRequest.adId) { @@ -1020,10 +1020,10 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { * @param {Object} options * @alias module:pbjs.getConfig */ -$$PREBID_GLOBAL$$.getConfig = config.getAnyConfig; -$$PREBID_GLOBAL$$.readConfig = config.readAnyConfig; -$$PREBID_GLOBAL$$.mergeConfig = config.mergeConfig; -$$PREBID_GLOBAL$$.mergeBidderConfig = config.mergeBidderConfig; +pbjsInstance.getConfig = config.getAnyConfig; +pbjsInstance.readConfig = config.readAnyConfig; +pbjsInstance.mergeConfig = config.mergeConfig; +pbjsInstance.mergeBidderConfig = config.mergeBidderConfig; /** * Set Prebid config options. @@ -1031,10 +1031,10 @@ $$PREBID_GLOBAL$$.mergeBidderConfig = config.mergeBidderConfig; * * @param {Object} options Global Prebid configuration object. Must be JSON - no JavaScript functions are allowed. */ -$$PREBID_GLOBAL$$.setConfig = config.setConfig; -$$PREBID_GLOBAL$$.setBidderConfig = config.setBidderConfig; +pbjsInstance.setConfig = config.setConfig; +pbjsInstance.setBidderConfig = config.setBidderConfig; -$$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); +pbjsInstance.que.push(() => listenMessagesFromCreative()); /** * This queue lets users load Prebid asynchronously, but run functions the same way regardless of whether it gets loaded @@ -1056,7 +1056,7 @@ $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); * the Prebid script has been fully loaded. * @alias module:pbjs.cmd.push */ -$$PREBID_GLOBAL$$.cmd.push = function (command) { +pbjsInstance.cmd.push = function (command) { if (typeof command === 'function') { try { command.call(); @@ -1068,7 +1068,7 @@ $$PREBID_GLOBAL$$.cmd.push = function (command) { } }; -$$PREBID_GLOBAL$$.que.push = $$PREBID_GLOBAL$$.cmd.push; +pbjsInstance.que.push = pbjsInstance.cmd.push; function processQueue(queue) { queue.forEach(function (cmd) { @@ -1086,10 +1086,10 @@ function processQueue(queue) { /** * @alias module:pbjs.processQueue */ -$$PREBID_GLOBAL$$.processQueue = function () { +pbjsInstance.processQueue = function () { hook.ready(); - processQueue($$PREBID_GLOBAL$$.que); - processQueue($$PREBID_GLOBAL$$.cmd); + processQueue(pbjsInstance.que); + processQueue(pbjsInstance.cmd); }; -export default $$PREBID_GLOBAL$$; +export default pbjsInstance; diff --git a/src/prebidGlobal.js b/src/prebidGlobal.js index 5eed4b3670f..4cbc3e10ad1 100644 --- a/src/prebidGlobal.js +++ b/src/prebidGlobal.js @@ -1,13 +1,21 @@ // if $$PREBID_GLOBAL$$ already exists in global document scope, use it, if not, create the object // global defination should happen BEFORE imports to avoid global undefined errors. -window.$$PREBID_GLOBAL$$ = (window.$$PREBID_GLOBAL$$ || {}); -window.$$PREBID_GLOBAL$$.cmd = window.$$PREBID_GLOBAL$$.cmd || []; -window.$$PREBID_GLOBAL$$.que = window.$$PREBID_GLOBAL$$.que || []; +/* global $$DEFINE_PREBID_GLOBAL$$ */ +const scope = !$$DEFINE_PREBID_GLOBAL$$ ? {} : window; +const global = scope.$$PREBID_GLOBAL$$ = scope.$$PREBID_GLOBAL$$ || {}; +global.cmd = global.cmd || []; +global.que = global.que || []; // create a pbjs global pointer -window._pbjsGlobals = window._pbjsGlobals || []; -window._pbjsGlobals.push('$$PREBID_GLOBAL$$'); +if (scope === window) { + scope._pbjsGlobals = scope._pbjsGlobals || []; + scope._pbjsGlobals.push('$$PREBID_GLOBAL$$'); +} export function getGlobal() { - return window.$$PREBID_GLOBAL$$; + return global; +} + +export function registerModule(name) { + global.installedModules.push(name); } diff --git a/src/utils.js b/src/utils.js index 8df45bab9d2..17b910ff722 100644 --- a/src/utils.js +++ b/src/utils.js @@ -3,6 +3,7 @@ import clone from 'just-clone'; import {find, includes} from './polyfill.js'; import CONSTANTS from './constants.json'; import {GreedyPromise} from './utils/promise.js'; +import {getGlobal} from './prebidGlobal.js'; export { default as deepAccess } from 'dlv/index.js'; export { dset as deepSetValue } from 'dset'; @@ -21,6 +22,8 @@ let consoleErrorExists = Boolean(consoleExists && window.console.error); let eventEmitter; +const pbjsInstance = getGlobal(); + export function _setEventEmitter(emitFn) { // called from events.js - this hoop is to avoid circular imports eventEmitter = emitFn; @@ -707,7 +710,7 @@ export function getKeyByValue(obj, value) { } } -export function getBidderCodes(adUnits = $$PREBID_GLOBAL$$.adUnits) { +export function getBidderCodes(adUnits = pbjsInstance.adUnits) { // this could memoize adUnits return adUnits.map(unit => unit.bids.map(bid => bid.bidder) .reduce(flatten, [])).reduce(flatten, []).filter((bidder) => typeof bidder !== 'undefined').filter(uniques); From dd4bd963f94ac74284f65d437b88a256b6af6adb Mon Sep 17 00:00:00 2001 From: Fernando Canteruccio Date: Wed, 29 Mar 2023 12:38:10 -0300 Subject: [PATCH 242/375] chore: update `getAudiencesAsBidderOrtb2` implementation and test (#9720) --- modules/airgridRtdProvider.js | 24 ++++++++++++++------ test/spec/modules/airgridRtdProvider_spec.js | 12 +++++++--- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 3578cc4b87e..174502d1757 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -8,7 +8,6 @@ import { config } from '../src/config.js'; import { submodule } from '../src/hook.js'; import { - mergeDeep, deepSetValue, deepAccess, } from '../src/utils.js'; @@ -85,13 +84,24 @@ function setAudiencesToAppNexusAdUnits(adUnits, audiences) { * @param {Array} audiences * @return {{}} a map from bidder code to ORTB2 config */ -export function getAudiencesAsBidderOrtb2(rtdConfig, audiences) { +export function setAudiencesAsBidderOrtb2(rtdConfig, audiences) { const bidders = deepAccess(rtdConfig, 'params.bidders'); - if (!bidders || bidders.length === 0) return {}; - const agOrtb2 = {} - deepSetValue(agOrtb2, 'ortb2.user.ext.data.airgrid', audiences || []); + if (!bidders || bidders.length === 0 || !audiences || audiences.length === 0) return; - return Object.fromEntries(bidders.map(bidder => [bidder, agOrtb2])); + const keywords = audiences.map( + (audienceId) => `perid=${audienceId}` + ).join(','); + + config.mergeBidderConfig({ + bidders: bidders, + config: { + ortb2: { + site: { + keywords, + } + } + } + }) } export function setAudiencesUsingAppNexusAuctionKeywords(audiences) { @@ -131,7 +141,7 @@ export function passAudiencesToBidders( const audiences = getMatchedAudiencesFromStorage(); if (audiences.length > 0) { setAudiencesUsingAppNexusAuctionKeywords(audiences); - mergeDeep(bidConfig?.ortb2Fragments?.bidder, getAudiencesAsBidderOrtb2(rtdConfig, audiences)); + setAudiencesAsBidderOrtb2(rtdConfig, audiences) if (adUnits) { setAudiencesToAppNexusAdUnits(adUnits, audiences); } diff --git a/test/spec/modules/airgridRtdProvider_spec.js b/test/spec/modules/airgridRtdProvider_spec.js index 5b1df6f9d4c..c587ef1a133 100644 --- a/test/spec/modules/airgridRtdProvider_spec.js +++ b/test/spec/modules/airgridRtdProvider_spec.js @@ -110,12 +110,18 @@ describe('airgrid RTD Submodule', function () { .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) .returns(JSON.stringify(MATCHED_AUDIENCES)); const audiences = agRTD.getMatchedAudiencesFromStorage(); - const bidderOrtb2 = agRTD.getAudiencesAsBidderOrtb2(RTD_CONFIG.dataProviders[0], audiences); - const bidders = RTD_CONFIG.dataProviders[0].params.bidders; + agRTD.setAudiencesAsBidderOrtb2(RTD_CONFIG.dataProviders[0], audiences); + + const bidderConfig = config.getBidderConfig() + const bidders = RTD_CONFIG.dataProviders[0].params.bidders + const bidderOrtb2 = bidderConfig + Object.keys(bidderOrtb2).forEach((bidder) => { if (bidders.indexOf(bidder) === -1) return; - expect(deepAccess(bidderOrtb2[bidder], 'ortb2.user.ext.data.airgrid')).to.eql(MATCHED_AUDIENCES); + MATCHED_AUDIENCES.forEach((audience) => { + expect(deepAccess(bidderOrtb2[bidder], 'ortb2.site.keywords')).to.contain(audience); + }) }); }); From a07ab71d2d16383cf0e5ae69d9d02d9d9d17d1a6 Mon Sep 17 00:00:00 2001 From: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Wed, 29 Mar 2023 20:18:46 +0200 Subject: [PATCH 243/375] RTBHouse Bid Adapter: change `source.tid` to contain `auctionId` and populate imp-level `ext.tid` (#9726) * RTBHouse Bid Adapter: add global vendor list id * structured user agent - browsers.brands * fix lint errors * Added sda into rtbhouse adapter * spreading ortb2: user & site props * examples reverted * init version * using mergedeep * removed wrong imp array augm.; slot imp augm. with addtl check * [SUA] merging ortb2.device into request * fledge auctionConfig adapted to our bid response structure * new bidder response structure for fledge * make sure bidderRequest has proper flag turned on * fledge endpoint hardcoded; code cleanups * remove obsolete function * obsolete function removed * [RTB House] Process FLEDGE request/response (#4) * [SDA & SUA] refactor using mergedeep * [FLEDGE] fledge auctionConfig adapted to our bid response structure * [FLEDGE] new bidder response structure for fledge * [FLEDGE] make sure bidderRequest has proper flag turned on * [FLEDGE] fledge endpoint hardcoded; code cleanups * [FLEDGE] remove obsolete functions * fixed lint errors * fledge test suites; adapter: delete imp.ext.ae when no fledge (#5) * RTBHouse Bid Adapter: use auctionId for source.tid * RTBHouse bid adapter: fixed source.tid tests * Imp level transaction id + mapSource fix * lint: removed obsolete whitespaces * RTBHouse Bid Adapter: change `source.tid` to contain `auctionId` and populate imp-level `ext.tid` (#8) * RTBHouse Bid Adapter: use auctionId for source.tid * Imp level transaction id + mapSource fix * lint: removed obsolete whitespaces --------- Co-authored-by: Leandro Otani Co-authored-by: rtbh-lotani <83652735+rtbh-lotani@users.noreply.github.com> Co-authored-by: Tomasz Swirski --- modules/rtbhouseBidAdapter.js | 13 ++++++-- test/spec/modules/rtbhouseBidAdapter_spec.js | 32 ++++++++++++++------ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 9f498014a8e..c01ce5e7db7 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -56,7 +56,7 @@ export const spec = { site: mapSite(validBidRequests, bidderRequest), cur: DEFAULT_CURRENCY_ARR, test: validBidRequests[0].params.test || 0, - source: mapSource(validBidRequests[0]), + source: mapSource(validBidRequests[0], bidderRequest), }; if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { @@ -228,6 +228,13 @@ function mapImpression(slot, bidderRequest) { delete imp.ext.ae; } } + + const tid = deepAccess(slot, 'ortb2Imp.ext.tid'); + if (tid) { + imp.ext = imp.ext || {}; + imp.ext.tid = tid; + } + return imp; } @@ -283,9 +290,9 @@ function mapSite(slot, bidderRequest) { * @param {object} slot Ad Unit Params by Prebid * @returns {object} Source by OpenRTB 2.5 §3.2.2 */ -function mapSource(slot) { +function mapSource(slot, bidderRequest) { const source = { - tid: slot.transactionId, + tid: bidderRequest?.auctionId || '', }; return source; diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 6d41df7605b..792d3ab0a9e 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -53,16 +53,18 @@ describe('RTBHouseAdapter', () => { describe('buildRequests', function () { let bidRequests; - const bidderRequest = { - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'https://example.com', - 'stack': ['https://example.com'] - } - }; + let bidderRequest; beforeEach(() => { + bidderRequest = { + 'auctionId': 'bidderrequest-auction-id', + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com', + 'stack': ['https://example.com'] + } + }; bidRequests = [ { 'bidder': 'rtbhouse', @@ -82,6 +84,11 @@ describe('RTBHouseAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', 'transactionId': 'example-transaction-id', + 'ortb2Imp': { + 'ext': { + 'tid': 'ortb2Imp-transaction-id-1' + } + }, 'schain': { 'ver': '1.0', 'complete': 1, @@ -203,7 +210,7 @@ describe('RTBHouseAdapter', () => { const bidRequest = Object.assign([], bidRequests); const request = spec.buildRequests(bidRequest, bidderRequest); const data = JSON.parse(request.data); - expect(data.source.tid).to.equal('example-transaction-id'); + expect(data.source.tid).to.equal('bidderrequest-auction-id'); }); it('should include bidfloor from floor module if avaiable', () => { @@ -256,6 +263,13 @@ describe('RTBHouseAdapter', () => { expect(data.source).to.have.deep.property('tid'); }); + it('should include impression level transaction id when provided', () => { + const bidRequest = Object.assign([], bidRequests); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].ext.tid).to.equal('ortb2Imp-transaction-id-1'); + }); + it('should not include invalid schain', () => { const bidRequest = Object.assign([], bidRequests); bidRequest[0].schain = { From e582396729658f26ebeb1c2deb4c6f84222bf05c Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Wed, 29 Mar 2023 13:21:40 -0600 Subject: [PATCH 244/375] Adloox AdServer Video : lengthen test timeouts (#9728) * Adloox AdServer Video : lengthen test timeout * Update adlooxAdServerVideo_spec.js * Update adlooxAdServerVideo_spec.js * Update realvuAnalyticsAdapter_spec.js * Update realvuAnalyticsAdapter_spec.js * change to 5500 * change realvu to 3500 * Update adlooxAdServerVideo_spec.js --- test/spec/modules/adlooxAdServerVideo_spec.js | 1 + test/spec/modules/realvuAnalyticsAdapter_spec.js | 1 + 2 files changed, 2 insertions(+) diff --git a/test/spec/modules/adlooxAdServerVideo_spec.js b/test/spec/modules/adlooxAdServerVideo_spec.js index 170982a51bd..ba9261e1aba 100644 --- a/test/spec/modules/adlooxAdServerVideo_spec.js +++ b/test/spec/modules/adlooxAdServerVideo_spec.js @@ -235,6 +235,7 @@ describe('Adloox Ad Server Video', function () { it('should fetch, retry on withoutCredentials, follow and return a wrapped blob that expires', function (done) { BID.responseTimestamp = utils.timestamp(); BID.ttl = 30; + this.timeout(5000) const clock = sandbox.useFakeTimers(BID.responseTimestamp); diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js index e51a4e2e3a2..221efc2d374 100644 --- a/test/spec/modules/realvuAnalyticsAdapter_spec.js +++ b/test/spec/modules/realvuAnalyticsAdapter_spec.js @@ -50,6 +50,7 @@ describe('RealVu', function() { describe('Analytics Adapter.', function () { it('enableAnalytics', function () { + this.timeout(3500) const config = { options: { partnerId: '1Y', From 81fd1112be520d40f0dd36d3acb6876a0328f816 Mon Sep 17 00:00:00 2001 From: Bernhard Pickenbrock Date: Wed, 29 Mar 2023 21:22:53 +0200 Subject: [PATCH 245/375] Smaato: Adapters that accept geolocation data from bid parameters should also accept it from ortb2.(device|user).geo (#9676) (#9725) --- modules/smaatoBidAdapter.js | 20 +++++++++++--- test/spec/modules/smaatoBidAdapter_spec.js | 32 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 8c2d9d4ff17..c1eb1bb8489 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -68,11 +68,23 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { deepSetValue(requestTemplate, 'regs.ext.gpp_sid', ortb2.regs.gpp_sid); } + if (ortb2.device?.ifa !== undefined) { + deepSetValue(requestTemplate, 'device.ifa', ortb2.device.ifa); + } + + if (ortb2.device?.geo !== undefined) { + deepSetValue(requestTemplate, 'device.geo', ortb2.device.geo); + } + if (deepAccess(bidRequest, 'params.app')) { - const geo = deepAccess(bidRequest, 'params.app.geo'); - deepSetValue(requestTemplate, 'device.geo', geo); - const ifa = deepAccess(bidRequest, 'params.app.ifa'); - deepSetValue(requestTemplate, 'device.ifa', ifa); + if (!deepAccess(requestTemplate, 'device.geo')) { + const geo = deepAccess(bidRequest, 'params.app.geo'); + deepSetValue(requestTemplate, 'device.geo', geo); + } + if (!deepAccess(requestTemplate, 'device.ifa')) { + const ifa = deepAccess(bidRequest, 'params.app.ifa'); + deepSetValue(requestTemplate, 'device.ifa', ifa); + } } const eids = deepAccess(bidRequest, 'userIdAsEids'); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 7891a49b9db..7f727d5d9e3 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -356,6 +356,13 @@ describe('smaatoBidAdapterTest', () => { keywords: 'a,b', gender: 'M', yob: 1984 + }, + device: { + ifa: 'ifa', + geo: { + lat: 53.5488, + lon: 9.9872 + } } }; @@ -368,6 +375,9 @@ describe('smaatoBidAdapterTest', () => { expect(req.user.ext.consent).to.equal(CONSENT_STRING); expect(req.site.keywords).to.eql('power tools,drills'); expect(req.site.publisher.id).to.equal('publisherId'); + expect(req.device.ifa).to.equal('ifa'); + expect(req.device.geo.lat).to.equal(53.5488); + expect(req.device.geo.lon).to.equal(9.9872); }); it('has no user ids', () => { @@ -1007,6 +1017,28 @@ describe('smaatoBidAdapterTest', () => { expect(req.device.ifa).to.equal(DEVICE_ID); }); + it('when geo and ifa info present and fpd present, then prefer fpd', () => { + const ortb2 = { + device: { + ifa: 'ifa', + geo: { + lat: 53.5488, + lon: 9.9872 + } + } + }; + + const inAppBidRequest = utils.deepClone(inAppBidRequestWithoutAppParams); + inAppBidRequest.params.app = {ifa: DEVICE_ID, geo: LOCATION}; + + const reqs = spec.buildRequests([inAppBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.device.geo.lat).to.equal(53.5488); + expect(req.device.geo.lon).to.equal(9.9872); + expect(req.device.ifa).to.equal('ifa'); + }); + it('when ifa is present but geo is missing, then add only ifa to device object', () => { const inAppBidRequest = utils.deepClone(inAppBidRequestWithoutAppParams); inAppBidRequest.params.app = {ifa: DEVICE_ID}; From a2a1a674c45844e65f39eebe7e6e649685a5645d Mon Sep 17 00:00:00 2001 From: Gena Date: Thu, 30 Mar 2023 15:25:59 +0300 Subject: [PATCH 246/375] Add 9 Dots Media alias (#9699) * Add 9 Dots Media alias * lint * Wrap aliases in quotes in test --- modules/adtelligentBidAdapter.js | 9 +++++++- package-lock.json | 2 +- .../modules/adtelligentBidAdapter_spec.js | 21 ++++++++++--------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index a7c83d9c190..ebba4f3ec6b 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -24,6 +24,7 @@ const HOST_GETTERS = { pgam: () => 'ghb.pgamssp.com', ocm: () => 'ghb.cenarius.orangeclickmedia.com', vidcrunchllc: () => 'ghb.platform.vidcrunch.com', + '9dotsmedia': () => 'ghb.platform.audiodots.com' } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -39,12 +40,18 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'appaloosa', 'bidsxchange', 'streamkey', 'janet', + aliases: [ + 'onefiftytwomedia', + 'appaloosa', + 'bidsxchange', + 'streamkey', + 'janet', { code: 'selectmedia', gvlid: 775 }, { code: 'navelix', gvlid: 380 }, 'pgam', { code: 'ocm', gvlid: 1148 }, { code: 'vidcrunchllc', gvlid: 1145 }, + '9dotsmedia' ], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { diff --git a/package-lock.json b/package-lock.json index a0f5e1418d2..d4f05f4329c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.41.0-pre", + "version": "7.42.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index d0ef69ccf08..c57e51c44fc 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -11,16 +11,17 @@ const EXPECTED_ENDPOINTS = [ 'https://ghb.adtelligent.com/v2/auction/' ]; const aliasEP = { - appaloosa: 'https://ghb.hb.appaloosa.media/v2/auction/', - appaloosa_publisherSuffix: 'https://ghb.hb.appaloosa.media/v2/auction/', - onefiftytwomedia: 'https://ghb.ads.152media.com/v2/auction/', - navelix: 'https://ghb.hb.navelix.com/v2/auction/', - bidsxchange: 'https://ghb.hbd.bidsxchange.com/v2/auction/', - streamkey: 'https://ghb.hb.streamkey.net/v2/auction/', - janet: 'https://ghb.bidder.jmgads.com/v2/auction/', - pgam: 'https://ghb.pgamssp.com/v2/auction/', - ocm: 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', - vidcrunchllc: 'https://ghb.platform.vidcrunch.com/v2/auction/', + 'appaloosa': 'https://ghb.hb.appaloosa.media/v2/auction/', + 'appaloosa_publisherSuffix': 'https://ghb.hb.appaloosa.media/v2/auction/', + 'onefiftytwomedia': 'https://ghb.ads.152media.com/v2/auction/', + 'navelix': 'https://ghb.hb.navelix.com/v2/auction/', + 'bidsxchange': 'https://ghb.hbd.bidsxchange.com/v2/auction/', + 'streamkey': 'https://ghb.hb.streamkey.net/v2/auction/', + 'janet': 'https://ghb.bidder.jmgads.com/v2/auction/', + 'pgam': 'https://ghb.pgamssp.com/v2/auction/', + 'ocm': 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', + 'vidcrunchllc': 'https://ghb.platform.vidcrunch.com/v2/auction/', + '9dotsmedia': 'https://ghb.platform.audiodots.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; From fbb71ad1c34638368bbf5df24ec5a8d4deeb80dd Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 30 Mar 2023 07:00:53 -0700 Subject: [PATCH 247/375] Core: allow restriction of cookies / localStorage through `bidderSettings.*.storageAllowed` (#9660) * Core: allow restriction of cookies / localStorage through `bidderSettings.*.storageAllowed` * Add test cases --- modules/userId/index.js | 11 +- src/storageManager.js | 105 +++++------------- test/spec/unit/core/storageManager_spec.js | 118 +++++++++++++++------ 3 files changed, 119 insertions(+), 115 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 82ebaf2b14e..4c8d4c0be38 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -133,13 +133,15 @@ import adapterManager, {gdprDataHandler} from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; import {hook, module, ready as hooksReady} from '../../src/hook.js'; import {buildEidPermissions, createEidsArray, USER_IDS_CONFIG} from './eids.js'; -import {getCoreStorageManager} from '../../src/storageManager.js'; +import {getCoreStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE} from '../../src/storageManager.js'; import { cyrb53Hash, deepAccess, + deepSetValue, delayExecution, getPrebidInternal, isArray, + isEmpty, isEmptyStr, isFn, isGptPubadsDefined, @@ -147,8 +149,7 @@ import { isPlainObject, logError, logInfo, - logWarn, - isEmpty, deepSetValue + logWarn } from '../../src/utils.js'; import {getPPID as coreGetPPID} from '../../src/adserver.js'; import {defer, GreedyPromise} from '../../src/utils/promise.js'; @@ -158,8 +159,8 @@ import {newMetrics, timedAuctionHook, useMetrics} from '../../src/utils/perfMetr import {findRootDomain} from '../../src/fpd/rootDomain.js'; const MODULE_NAME = 'User ID'; -const COOKIE = 'cookie'; -const LOCAL_STORAGE = 'html5'; +const COOKIE = STORAGE_TYPE_COOKIES; +const LOCAL_STORAGE = STORAGE_TYPE_LOCALSTORAGE; const DEFAULT_SYNC_DELAY = 500; const NO_AUCTION_DELAY = 0; const CONSENT_DATA_COOKIE_STORAGE_CONFIG = { diff --git a/src/storageManager.js b/src/storageManager.js index eaf35603c60..b8906b131e4 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -7,6 +7,9 @@ const moduleTypeWhiteList = ['core', 'prebid-module']; export let storageCallbacks = []; +export const STORAGE_TYPE_LOCALSTORAGE = 'html5'; +export const STORAGE_TYPE_COOKIES = 'cookie'; + /** * Storage options * @typedef {Object} storageOptions @@ -26,20 +29,22 @@ export let storageCallbacks = []; * @param {storageOptions} options */ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}, {bidderSettings = defaultBidderSettings} = {}) { - function isBidderAllowed() { + function isBidderAllowed(storageType) { if (bidderCode == null) { return true; } const storageAllowed = bidderSettings.get(bidderCode, 'storageAllowed'); - return storageAllowed == null ? false : storageAllowed; + if (!storageAllowed || storageAllowed === true) return !!storageAllowed; + if (Array.isArray(storageAllowed)) return storageAllowed.some((e) => e === storageType); + return storageAllowed === storageType; } if (moduleTypeWhiteList.includes(moduleType)) { gvlid = gvlid || VENDORLESS_GVLID; } - function isValid(cb) { - if (!isBidderAllowed()) { + function isValid(cb, storageType) { + if (!isBidderAllowed(storageType)) { logInfo(`bidderSettings denied access to device storage for bidder '${bidderCode}'`); const result = {valid: false}; return cb(result); @@ -63,6 +68,17 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } } + function schedule(operation, storageType, done) { + if (done && typeof done === 'function') { + storageCallbacks.push(function() { + let result = isValid(operation, storageType); + done(result); + }); + } else { + return isValid(operation, storageType); + } + } + /** * @param {string} key * @param {string} value @@ -83,14 +99,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = document.cookie = `${key}=${encodeURIComponent(value)}${expiresPortion}; path=/${domainPortion}${sameSite ? `; SameSite=${sameSite}` : ''}${secure}`; } } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_COOKIES, done); }; /** @@ -105,14 +114,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return null; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_COOKIES, done); }; /** @@ -133,14 +135,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return false; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -153,14 +148,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return false; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_COOKIES, done); } /** @@ -173,14 +161,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = window.localStorage.setItem(key, value); } } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -194,14 +175,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return null; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -213,14 +187,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = window.localStorage.removeItem(key); } } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -237,14 +204,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } return false; } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -273,14 +233,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = } } - if (done && typeof done === 'function') { - storageCallbacks.push(function() { - let result = isValid(cb); - done(result); - }); - } else { - return isValid(cb); - } + return schedule(cb, STORAGE_TYPE_COOKIES, done); } return { diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index 58bf8c9eb25..d4b3c8e583e 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -142,11 +142,11 @@ describe('storage manager', function() { const COOKIE = 'test-cookie'; const LS_KEY = 'test-localstorage'; - function mockBidderSettings() { + function mockBidderSettings(val) { return { get(bidder, key) { if (bidder === ALLOWED_BIDDER && key === ALLOW_KEY) { - return true; + return val; } else { return undefined; } @@ -157,39 +157,89 @@ describe('storage manager', function() { Object.entries({ disallowed: ['denied_bidder', false], allowed: [ALLOWED_BIDDER, true] - }).forEach(([test, [bidderCode, shouldWork]]) => { - describe(`for ${test} bidders`, () => { - let mgr; - - beforeEach(() => { - mgr = newStorageManager({bidderCode: bidderCode}, {bidderSettings: mockBidderSettings()}); - }) - - afterEach(() => { - mgr.setCookie(COOKIE, 'delete', new Date().toUTCString()); - mgr.removeDataFromLocalStorage(LS_KEY); - }) - - const testDesc = (desc) => `should ${shouldWork ? '' : 'not'} ${desc}`; - - it(testDesc('allow cookies'), () => { - mgr.setCookie(COOKIE, 'value'); - expect(mgr.getCookie(COOKIE)).to.equal(shouldWork ? 'value' : null); - }); - - it(testDesc('allow localStorage'), () => { - mgr.setDataInLocalStorage(LS_KEY, 'value'); - expect(mgr.getDataFromLocalStorage(LS_KEY)).to.equal(shouldWork ? 'value' : null); - }); - - it(testDesc('report localStorage as available'), () => { - expect(mgr.hasLocalStorage()).to.equal(shouldWork); - }); - - it(testDesc('report cookies as available'), () => { - expect(mgr.cookiesAreEnabled()).to.equal(shouldWork); + }).forEach(([t, [bidderCode, isBidderAllowed]]) => { + describe(`for ${t} bidders`, () => { + Object.entries({ + 'all': { + configValues: [ + true, + ['html5', 'cookie'] + ], + shouldWork: { + html5: true, + cookie: true + } + }, + 'none': { + configValues: [ + false, + [] + ], + shouldWork: { + html5: false, + cookie: false + } + }, + 'localStorage': { + configValues: [ + 'html5', + ['html5'] + ], + shouldWork: { + html5: true, + cookie: false + } + }, + 'cookies': { + configValues: [ + 'cookie', + ['cookie'] + ], + shouldWork: { + html5: false, + cookie: true + } + } + }).forEach(([t, {configValues, shouldWork: {cookie, html5}}]) => { + describe(`when ${t} is allowed`, () => { + configValues.forEach(configValue => describe(`storageAllowed = ${configValue}`, () => { + let mgr; + + beforeEach(() => { + mgr = newStorageManager({bidderCode: bidderCode}, {bidderSettings: mockBidderSettings(configValue)}); + }) + + afterEach(() => { + mgr.setCookie(COOKIE, 'delete', new Date().toUTCString()); + mgr.removeDataFromLocalStorage(LS_KEY); + }) + + function scenario(type, desc, fn) { + const shouldWork = isBidderAllowed && ({html5, cookie})[type]; + it(`${shouldWork ? '' : 'NOT'} ${desc}`, () => fn(shouldWork)); + } + + scenario('cookie', 'allow cookies', (shouldWork) => { + mgr.setCookie(COOKIE, 'value'); + expect(mgr.getCookie(COOKIE)).to.equal(shouldWork ? 'value' : null); + }); + + scenario('html5', 'allow localStorage', (shouldWork) => { + mgr.setDataInLocalStorage(LS_KEY, 'value'); + expect(mgr.getDataFromLocalStorage(LS_KEY)).to.equal(shouldWork ? 'value' : null); + }); + + scenario('html5', 'report localStorage as available', (shouldWork) => { + expect(mgr.hasLocalStorage()).to.equal(shouldWork); + }); + + scenario('cookie', 'report cookies as available', (shouldWork) => { + expect(mgr.cookiesAreEnabled()).to.equal(shouldWork); + }); + })); + }); }); }); }); - }) + }); }); From 6fff248acf3e5c1f9b34ceeab46e94234169bb6b Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 30 Mar 2023 15:05:31 +0000 Subject: [PATCH 248/375] Prebid 7.43.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d4f05f4329c..c4f7728ef54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.43.0-pre", + "version": "7.43.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index ad625cb3045..36a2a4a35bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.43.0-pre", + "version": "7.43.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From d9354cc5c480a8c52b2a70b7a010d0b6ab59033a Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 30 Mar 2023 15:05:31 +0000 Subject: [PATCH 249/375] Increment version to 7.44.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4f7728ef54..276fa15775a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.43.0", + "version": "7.44.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 36a2a4a35bb..c4efb745c42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.43.0", + "version": "7.44.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 67184b035a46e042cd86049f2b5fec70bfce8cdf Mon Sep 17 00:00:00 2001 From: JulianRSL <88969792+JulianRSL@users.noreply.github.com> Date: Thu, 30 Mar 2023 18:33:52 +0300 Subject: [PATCH 250/375] IntentIQ Analytics Module : initial release (#9322) * Adding IIQ analytical adapter * IIQ Analytical adapter MD * Test fix * Test fix * Modules Documentation * Test change --------- Co-authored-by: Julian --- modules/intentIqAnalyticsAdapter.js | 241 ++++++++++++++++++ modules/intentIqAnalyticsAdapter.md | 19 ++ modules/intentIqIdSystem.js | 48 +++- modules/intentIqIdSystem.md | 80 ++++++ modules/userId/eids.js | 40 +-- .../modules/intentIqAnalyticsAdapter_spec.js | 129 ++++++++++ test/spec/modules/intentIqIdSystem_spec.js | 37 +++ 7 files changed, 573 insertions(+), 21 deletions(-) create mode 100644 modules/intentIqAnalyticsAdapter.js create mode 100644 modules/intentIqAnalyticsAdapter.md create mode 100644 modules/intentIqIdSystem.md create mode 100644 test/spec/modules/intentIqAnalyticsAdapter_spec.js diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js new file mode 100644 index 00000000000..9e9370a9ef0 --- /dev/null +++ b/modules/intentIqAnalyticsAdapter.js @@ -0,0 +1,241 @@ +import { logInfo, logError } from '../src/utils.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import { ajax } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { config } from '../src/config.js'; + +const MODULE_NAME = 'iiqAnalytics' +const analyticsType = 'endpoint'; +const defaultUrl = 'https://reports.intentiq.com/report'; +const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +const prebidVersion = '$prebid.version$'; +const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); + +const FIRST_PARTY_KEY = '_iiq_fdata'; +const FIRST_PARTY_DATA_KEY = '_iiq_fdata'; +const GROUP_LS_KEY = '_iiq_group'; +const PRECENT_LS_KEY = '_iiq_precent'; +const JSVERSION = 5.3 + +const PARAMS_NAMES = { + abPercentage: 'abPercentage', + abTestGroup: 'abGroup', + pbPauseUntill: 'pbPauseUntil', + pbMonitoringEnabled: 'pbMonitoringEnabled', + isInTestGroup: 'isInTestGroup', + enhanceRequests: 'enhanceRequests', + wasSubscribedForPrebid: 'wasSubscribedForPrebid', + hadEids: 'hadEids', + userActualPercentage: 'userPercentage', + ABTestingConfigurationSource: 'ABTestingConfigurationSource', + lateConfiguration: 'lateConfiguration', + jsverion: 'jsversion', + eidsNames: 'eidsNames', + requestRtt: 'rtt', + clientType: 'clientType', + adserverDeviceType: 'AdserverDeviceType', + terminationCause: 'terminationCause', + callCount: 'callCount', + manualCallCount: 'mcc', + pubprovidedidsFailedToregister: 'ppcc', + noDataCount: 'noDataCount', + profile: 'profile', + isProfileDeterministic: 'pidDeterministic', + siteId: 'sid', + hadEidsInLocalStorage: 'idls', + auctionStartTime: 'ast', + eidsReadTime: 'eidt', + agentId: 'aid', + auctionEidsLegth: 'aeidln', + wasServerCalled: 'wsrvcll', + refferer: 'vrref', + isInBrowserBlacklist: 'inbbl', + prebidVersion: 'pbjsver', + partnerId: 'partnerId' +}; + +var initOptions = { + lsValueInitialized: false +} + +// Events needed +const { + EVENTS: { + BID_WON + } +} = CONSTANTS; + +let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analyticsType }), { + track({ eventType, args }) { + switch (eventType) { + case BID_WON: + bidWon(args); + break; + default: + break; + } + } +}); + +function readData(key) { + try { + if (storage.hasLocalStorage()) { + return storage.getDataFromLocalStorage(key); + } + if (storage.cookiesAreEnabled()) { + return storage.getCookie(key); + } + } catch (error) { + logError(error); + } +} + +function initLsValues() { + if (initOptions.lsValueInitialized) return; + initOptions.fpid = readData(FIRST_PARTY_KEY); + let iiqArr = config.getConfig('userSync.userIds').filter(m => m.name == 'intentIqId'); + if (iiqArr && iiqArr.length > 0) initOptions.lsValueInitialized = true; + if (!iiqArr) iiqArr = []; + if (iiqArr.length == 0) { + iiqArr.push({ + 'params': { + 'partner': -1, + 'group': 'U', + 'percentage': -1 + } + }) + } + if (iiqArr && iiqArr.length > 0) { + if (iiqArr[0].params && iiqArr[0].params.partner && !isNaN(iiqArr[0].params.partner)) { + initOptions.partner = iiqArr[0].params.partner; + initOptions.userGroup = iiqArr[0].params.group || 'U'; + initOptions.userPercentage = iiqArr[0].params.percentage || '-1'; + + initOptions.currentGroup = readData(GROUP_LS_KEY + '_' + initOptions.partner) + initOptions.currentPercentage = readData(PRECENT_LS_KEY + '_' + initOptions.partner) + } + } +} + +function initReadLsIds() { + if (isNaN(initOptions.partner) || initOptions.partner == -1) return; + try { + initOptions.dataInLs = null; + let iData = readData(FIRST_PARTY_DATA_KEY + '_' + initOptions.partner) + if (iData) { + initOptions.lsIdsInitialized = true; + let pData = JSON.parse(iData); + initOptions.dataInLs = pData.data; + initOptions.eidl = pData.eidl || -1; + } + } catch (e) { + logError(e) + } +} + +function bidWon(args) { + if (!initOptions.lsValueInitialized) { initLsValues(); } + if (initOptions.lsValueInitialized && !initOptions.lsIdsInitialized) { initReadLsIds(); } + if (!initOptions.manualReport) { ajax(constructFulllUrl(preparePayload(args, true)), undefined, null, { method: 'GET' }); } + + logInfo('IIQ ANALYTICS -> BID WON') +} + +function getRandom(start, end) { + return Math.floor((Math.random() * (end - start + 1)) + start); +} + +function preparePayload(data) { + let result = getDefaultDataObject(); + + result[PARAMS_NAMES.partnerId] = initOptions.partner; + result[PARAMS_NAMES.prebidVersion] = prebidVersion; + result[PARAMS_NAMES.refferer] = getRefferer(); + result[PARAMS_NAMES.userActualPercentage] = initOptions.userPercentage; + + if (initOptions.userGroup && initOptions.userGroup != '') { result[PARAMS_NAMES.ABTestingConfigurationSource] = 'group'; } else if (initOptions.userPercentage && !isNaN(initOptions.userPercentage)) { result[PARAMS_NAMES.ABTestingConfigurationSource] = 'percentage'; } + + result[PARAMS_NAMES.abPercentage] = initOptions.currentPercentage; + result[PARAMS_NAMES.abTestGroup] = initOptions.currentGroup; + + result[PARAMS_NAMES.isInTestGroup] = initOptions.currentGroup == 'A'; + + result[PARAMS_NAMES.agentId] = REPORTER_ID; + + fillPrebidEventData(data, result); + + fillEidsData(result); + + return result; +} + +function fillEidsData(result) { + if (initOptions.lsIdsInitialized) { + result[PARAMS_NAMES.hadEidsInLocalStorage] = initOptions.eidl && initOptions.eidl > 0; + result[PARAMS_NAMES.auctionEidsLegth] = initOptions.eidl || -1; + } +} + +function fillPrebidEventData(eventData, result) { + if (eventData.bidderCode) { result.bidderCode = eventData.bidderCode; } + if (eventData.cpm) { result.cpm = eventData.cpm; } + if (eventData.currency) { result.currency = eventData.currency; } + if (eventData.originalCpm) { result.originalCpm = eventData.originalCpm; } + if (eventData.originalCurrency) { result.originalCurrency = eventData.originalCurrency; } + if (eventData.status) { result.status = eventData.status; } + if (eventData.auctionId) { result.prebidAuctionId = eventData.auctionId; } + + result.biddingPlatformId = 1; + result.partnerAuctionId = 'BW'; +} + +function getDefaultDataObject() { + return { + 'inbbl': false, + 'pbjsver': prebidVersion, + 'partnerAuctionId': 'BW', + 'reportSource': 'pbjs', + 'abPercentage': -1, + 'abGroup': 'U', + 'userPercentage': -1, + 'jsversion': JSVERSION, + 'partnerId': -1, + 'biddingPlatformId': 1, + 'idls': false, + 'ast': -1, + 'aeidln': -1 + } +} + +function constructFulllUrl(data) { + let report = [] + data = btoa(JSON.stringify(data)) + report.push(data) + return defaultUrl + '?pid=' + initOptions.partner + + '&mct=1' + + ((iiqAnalyticsAnalyticsAdapter.initOptions && iiqAnalyticsAnalyticsAdapter.initOptions.fpid) + ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') + + '&agid=' + REPORTER_ID + + '&jsver=' + JSVERSION + + '&source=pbjs' + + '&payload=' + JSON.stringify(report) +} + +function getRefferer() { + return document.referrer; +} + +iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapter.enableAnalytics; + +iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig) { + iiqAnalyticsAnalyticsAdapter.originEnableAnalytics(myConfig); // call the base class function +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: iiqAnalyticsAnalyticsAdapter, + code: MODULE_NAME +}); + +export default iiqAnalyticsAnalyticsAdapter; diff --git a/modules/intentIqAnalyticsAdapter.md b/modules/intentIqAnalyticsAdapter.md new file mode 100644 index 00000000000..eb7e0d6b0d6 --- /dev/null +++ b/modules/intentIqAnalyticsAdapter.md @@ -0,0 +1,19 @@ +# Overview + +Module Name: iiqAnalytics +Module Type: Analytics Adapter +Maintainer: julian@intentiq.com + +# Description + +By using this Intent IQ adapter, you will be able to obtain comprehensive analytics and metrics regarding the performance of the Intent IQ Unified ID module. This includes how the module impacts your revenue, CPMs, and fill rates related to bidders and domains. + +## Intent IQ Universal ID Registration + +No registration for this module is required. + +## Intent IQ Universal IDConfiguration + +IMPORTANT: requires Intent IQ Universal ID module be installed and configured. [(How-To)](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) + +No aaditional configuration for this module is required. We will use the configuration provided for Intent IQ Universal IQ module. diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 563435dee65..9588f2e53a3 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -15,11 +15,20 @@ const PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; export const FIRST_PARTY_KEY = '_iiq_fdata'; export var FIRST_PARTY_DATA_KEY = '_iiq_fdata'; +export var GROUP_LS_KEY = '_iiq_group'; +export var WITH_IIQ = 'A'; +export var WITHOUT_IIQ = 'B'; +export var PRECENT_LS_KEY = '_iiq_precent'; +export var DEFAULT_PERCENTAGE = 100; export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); const INVALID_ID = 'INVALID_ID'; +function getRandom(start, end) { + return Math.floor(Math.random() * (end - start + 1) + start); +} + /** * Generate standard UUID string * @return {string} @@ -44,6 +53,9 @@ export function readData(key) { if (storage.hasLocalStorage()) { return storage.getDataFromLocalStorage(key); } + if (localStorage) { + return localStorage.getItem(key) + } if (storage.cookiesAreEnabled()) { return storage.getCookie(key); } @@ -65,6 +77,9 @@ function storeData(key, value) { if (value) { if (storage.hasLocalStorage()) { storage.setDataInLocalStorage(key, value); + } else + if (localStorage) { + localStorage.setItem(key, value) } const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); if (storage.cookiesAreEnabled()) { @@ -103,7 +118,7 @@ export const intentIqIdSubmodule = { * @param {{string}} value * @returns {{intentIqId: {string}}|undefined} */ - decode(value) { + decode(value, config) { return value && value != '' && INVALID_ID != value ? { 'intentIqId': value } : undefined; }, /** @@ -118,6 +133,32 @@ export const intentIqIdSubmodule = { logError('User ID - intentIqId submodule requires a valid partner to be defined'); return; } + + if (isNaN(configParams.percentage)) { + logInfo(MODULE_NAME + ' AB Testing percentage is not defined. Setting default value = ' + DEFAULT_PERCENTAGE); + configParams.percentage = DEFAULT_PERCENTAGE; + } + + if (isNaN(configParams.percentage) || configParams.percentage < 0 || configParams.percentage > 100) { + logError(MODULE_NAME + 'Percentage - intentIqId submodule requires a valid percentage value'); + return false; + } + + configParams.group = readData(GROUP_LS_KEY + '_' + configParams.partner); + let percentage = readData(PRECENT_LS_KEY + '_' + configParams.partner); + + if (!configParams.group || !percentage || isNaN(percentage) || percentage != configParams.percentage) { + logInfo(MODULE_NAME + 'Generating new Group. Current test group: ' + configParams.group + ', current percentage: ' + percentage + ' , configured percentage: ' + configParams.percentage); + if (configParams.percentage > getRandom(1, 100)) { configParams.group = WITH_IIQ; } else configParams.group = WITHOUT_IIQ; + storeData(GROUP_LS_KEY + '_' + configParams.partner, configParams.group) + storeData(PRECENT_LS_KEY + '_' + configParams.partner, configParams.percentage + '') + logInfo(MODULE_NAME + 'New group: ' + configParams.group) + } + if (configParams.group == WITHOUT_IIQ) { + logInfo(MODULE_NAME + 'Group "B". Passive Mode ON.'); + return true; + } + if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { FIRST_PARTY_DATA_KEY += '_' + configParams.partner; } let rrttStrtTime = 0; @@ -157,6 +198,11 @@ export const intentIqIdSubmodule = { partnerData.cttl = respJson.cttl; shouldUpdateLs = true; } + + if ('eidl' in respJson) { + partnerData.eidl = respJson.eidl; + } + // If should save and data is empty, means we should save as INVALID_ID if (respJson.data == '') { respJson.data = INVALID_ID; diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md new file mode 100644 index 00000000000..70c8919d0d2 --- /dev/null +++ b/modules/intentIqIdSystem.md @@ -0,0 +1,80 @@ +# Intent IQ Universal ID module + +By leveraging the Intent IQ identity graph, our module helps publishers, SSPs, and DSPs overcome the challenges of monetizing cookie-less inventory and preparing for a future without 3rd-party cookies. Our solution implements 1st-party data clustering and provides Intent IQ person IDs with over 90% coverage and unmatched accuracy in supported countries while remaining privacy-friendly and CCPA compliant. This results in increased CPMs, higher fill rates, and, ultimately, lifting overall revenue + +# All you need is a few basic steps to start using our solution. + +## Registration + +Navigate to [our portal ](https://www.intentiq.com/) and contact our team for partner ID. +check our [documentation](https://pbmodule.documents.intentiq.com/) to get more information about our solution and how utilze it's full potential + +## Integration + +``` +gulp build –modules=intentIqIdSystem +``` +We recommend including the Intent IQ Analytics adapter module for improved visibility + +## Configuration + +### Parameters + +Please find below list of paramters that could be used in configuring Intent IQ Universal ID module + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| ------------------------------ | -------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | +| name | Required | String | The name of this module: "intentIqId" | `"intentIqId"` | +| params | Required | Object | Details for IntentIqId initialization. | | +| params.partner | Required | Number | This is the partner ID value obtained from registering with IntentIQ. | `1177538` | +| params.percentage | Required | Number | This a percentage value for our A/B testing group distribution. The values supposed to be in range of 0 to 100. We suggest to set it to 95 percent for optimal balance ofbetween prefromance and preceision. | `95` | +| params.pcid | Optional | String | This is the partner cookie ID, it is a dynamic value attached to the request. | `"g3hC52b"` | +| params.pai | Optional | String | This is the partner customer ID / advertiser ID, it is a dynamic value attached to the request. | `"advertiser1"` | + +### Configuration example + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: "intentIqId", + params: { + partner: 123456, // valid partner id + percentage: 95, + }, + storage: { + type: "html5", + name: "intentIqId", // set localstorage with this name + expires: 60, + refreshInSeconds: 4 * 3600, // refresh ID every 4 hours to ensure it's fresh + }, + }, + ], + syncDelay: 3000, + }, +}); +``` + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: "intentIqId", + params: { + partner: 123456 // valid partner id + pcid: PCID_VARIABLE, // string value, dynamically loaded into a variable before setting the configuration + pai: PAI_VARIABLE , // string value, dynamically loaded into a variable before setting the configuration + percentage: 95 + }, + storage: { + type: "html5", + name: "intentIqId", // set localstorage with this name + expires: 60 + } + }], + syncDelay: 3000 + } +}); +``` + diff --git a/modules/userId/eids.js b/modules/userId/eids.js index a96dbaee4af..99fca9df3c5 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -7,12 +7,12 @@ export const USER_IDS_CONFIG = { // GrowthCode 'growthCodeId': { - getValue: function(data) { + getValue: function (data) { return data.gc_id }, source: 'growthcode.io', atype: 1, - getUidExt: function(data) { + getUidExt: function (data) { const extendedData = pick(data, [ 'h1', 'h2', @@ -61,7 +61,7 @@ export const USER_IDS_CONFIG = { 'tdid': { source: 'adserver.org', atype: 1, - getUidExt: function() { + getUidExt: function () { return { rtiPartner: 'TDID' }; @@ -70,12 +70,12 @@ export const USER_IDS_CONFIG = { // id5Id 'id5id': { - getValue: function(data) { + getValue: function (data) { return data.uid }, source: 'id5-sync.com', atype: 1, - getUidExt: function(data) { + getUidExt: function (data) { if (data.ext) { return data.ext; } @@ -86,14 +86,14 @@ export const USER_IDS_CONFIG = { 'ftrackId': { source: 'flashtalking.com', atype: 1, - getValue: function(data) { + getValue: function (data) { let value = ''; if (data && data.ext && data.ext.DeviceID) { value = data.ext.DeviceID; } return value; }, - getUidExt: function(data) { + getUidExt: function (data) { return data && data.ext; } }, @@ -102,7 +102,7 @@ export const USER_IDS_CONFIG = { 'parrableId': { source: 'parrable.com', atype: 1, - getValue: function(parrableId) { + getValue: function (parrableId) { if (parrableId.eid) { return parrableId.eid; } @@ -113,7 +113,7 @@ export const USER_IDS_CONFIG = { } return null; }, - getUidExt: function(parrableId) { + getUidExt: function (parrableId) { const extendedData = pick(parrableId, [ 'ibaOptout', 'ccpaOptout' @@ -132,12 +132,12 @@ export const USER_IDS_CONFIG = { // liveIntentId 'lipb': { - getValue: function(data) { + getValue: function (data) { return data.lipbid; }, source: 'liveintent.com', atype: 3, - getEidExt: function(data) { + getEidExt: function (data) { if (Array.isArray(data.segments) && data.segments.length) { return { segments: data.segments @@ -173,16 +173,16 @@ export const USER_IDS_CONFIG = { // merkleId 'merkleId': { atype: 3, - getSource: function(data) { + getSource: function (data) { if (data?.ext?.ssp) { return `${data.ext.ssp}.merkleinc.com` } return 'merkleinc.com' }, - getValue: function(data) { + getValue: function (data) { return data.id; }, - getUidExt: function(data) { + getUidExt: function (data) { if (data.keyID) { return { keyID: data.keyID @@ -262,7 +262,7 @@ export const USER_IDS_CONFIG = { 'uid2': { source: 'uidapi.com', atype: 3, - getValue: function(data) { + getValue: function (data) { return data.id; } }, @@ -331,7 +331,7 @@ export const USER_IDS_CONFIG = { '33acrossId': { source: '33across.com', atype: 1, - getValue: function(data) { + getValue: function (data) { return data.envelope; } }, @@ -356,19 +356,19 @@ export const USER_IDS_CONFIG = { // OneKey Data 'oneKeyData': { - getValue: function(data) { + getValue: function (data) { if (data && Array.isArray(data.identifiers) && data.identifiers[0]) { return data.identifiers[0].value; } }, source: 'paf', atype: 1, - getEidExt: function(data) { + getEidExt: function (data) { if (data && data.preferences) { - return {preferences: data.preferences}; + return { preferences: data.preferences }; } }, - getUidExt: function(data) { + getUidExt: function (data) { if (data && Array.isArray(data.identifiers) && data.identifiers[0]) { const id = data.identifiers[0]; return { diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..3e9193987c1 --- /dev/null +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -0,0 +1,129 @@ +import { expect } from 'chai'; +import iiqAnalyticsAnalyticsAdapter from 'modules/intentIqAnalyticsAdapter.js' +// import * as utils from 'src/utils.js'; +// import { server } from 'test/mocks/xhr.js'; +// import adapterManager from 'src/adapterManager.js'; +import { config } from 'src/config.js'; +let events = require('src/events'); +let constants = require('src/constants.json'); + +const partner = 10; +const pai = '11'; +const pcid = '12'; +const userPercentage = 0; +const defaultPercentage = 100; +const defaultConfigParams = { params: { partner: partner } }; +const percentageConfigParams = { params: { partner: partner, percentage: userPercentage } }; +const paiConfigParams = { params: { partner: partner, pai: pai } }; +const pcidConfigParams = { params: { partner: partner, pcid: pcid } }; +const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid } }; +const responseHeader = { 'Content-Type': 'application/json' } + +const testData = { data: 'test' } + +const FIRST_PARTY_DATA_KEY = '_iiq_fdata' +const PRECENT_LS_KEY = '_iiq_precent' +const GROUP_LS_KEY = '_iiq_group' +const WITH_IIQ = 'A' +const WITHOUT_IIQ = 'B' + +const USERID_CONFIG = [ + { + 'name': 'intentIqId', + 'params': { + 'partner': partner, + 'unpack': null, + 'percentage': 100, + }, + 'storage': { + 'type': 'html5', + 'name': 'intentIqId', + 'expires': 60, + 'refreshInSeconds': 14400 + } + } +] + +let wonRequest = { + 'bidderCode': 'pubmatic', + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '23caeb34c55da51', + 'requestId': '87615b45ca4973', + 'transactionId': '5e69fd76-8c86-496a-85ce-41ae55787a50', + 'auctionId': '0cbd3a43-ff45-47b8-b002-16d3946b23bf', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 5, + 'currency': 'USD', + 'ttl': 300, + 'referrer': '', + 'adapterCode': 'pubmatic', + 'originalCpm': 5, + 'originalCurrency': 'USD', + 'responseTimestamp': 1669644710345, + 'requestTimestamp': 1669644710109, + 'bidder': 'testbidder', + 'adUnitCode': 'addUnitCode', + 'timeToRespond': 236, + 'pbLg': '5.00', + 'pbMg': '5.00', + 'pbHg': '5.00', + 'pbAg': '5.00', + 'pbDg': '5.00', + 'pbCg': '', + 'size': '728x90', + 'status': 'rendered', + +}; + +// adapterManager.registerAnalyticsAdapter({ +// code: 'iiqAnalytics', +// adapter: iiqAnalyticsAnalyticsAdapter +// }); + +describe('IntentIQ tests all', function () { + let ixhr; + let requests = []; + + beforeEach(function () { + sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns( + USERID_CONFIG + ); + ixhr = sinon.useFakeXMLHttpRequest(); + requests = []; + ixhr.onCreate = (a) => { + requests.push(a); + }; + sinon.stub(events, 'getEvents').returns([]); + iiqAnalyticsAnalyticsAdapter.enableAnalytics({ + provider: 'iiqAnalytics', + }); + sinon.spy(iiqAnalyticsAnalyticsAdapter, 'track'); + }); + afterEach(function () { + config.getConfig.restore(); + events.getEvents.restore(); + requests = []; + ixhr.restore(); + iiqAnalyticsAnalyticsAdapter.disableAnalytics(); + iiqAnalyticsAnalyticsAdapter.track.restore(); + }); + + describe('IntentIQ tests', function () { + it('IIQ Analytical Adapter bid win report', function () { + iiqAnalyticsAnalyticsAdapter.enableAnalytics({ + provider: 'iiqAnalytics' + }); + + localStorage.setItem(PRECENT_LS_KEY + '_' + partner, '95') + localStorage.setItem(GROUP_LS_KEY + '_' + partner, 'A') + localStorage.setItem(FIRST_PARTY_DATA_KEY + '_' + partner, '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344"}') + + events.emit(constants.EVENTS.BID_WON, wonRequest); + expect(requests[0].url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1&agid=') + expect(requests[0].url).to.contain('&jsver=5.3&source=pbjs&payload=') + }); + }); +}); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index f9bfa577e3a..e555af1d4c0 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -6,12 +6,23 @@ import { server } from 'test/mocks/xhr.js'; const partner = 10; const pai = '11'; const pcid = '12'; +const userPercentage = 0; +const defaultPercentage = 100; const defaultConfigParams = { params: { partner: partner } }; +const percentageConfigParams = { params: { partner: partner, percentage: userPercentage } }; const paiConfigParams = { params: { partner: partner, pai: pai } }; const pcidConfigParams = { params: { partner: partner, pcid: pcid } }; const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid } }; const responseHeader = { 'Content-Type': 'application/json' } +const testData = { data: 'test' } + +const FIRST_PARTY_DATA_KEY = '_iiq_fdata' +const PRECENT_LS_KEY = '_iiq_precent' +const GROUP_LS_KEY = '_iiq_group' +const WITH_IIQ = 'A' +const WITHOUT_IIQ = 'B' + describe('IntentIQ tests', function () { let logErrorStub; let testLSValue = { @@ -228,4 +239,30 @@ describe('IntentIQ tests', function () { expect(callBackSpy.calledOnce).to.be.true; expect(callBackSpy.args[0][0]).to.be.equal(testLSValueWithData.data); }); + + it('Default percentage and group without user config 100 %', function () { + localStorage.clear(); + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + if (submoduleCallback) { + submoduleCallback(callBackSpy); + } + let ls_percent_data = localStorage.getItem(PRECENT_LS_KEY + '_' + partner) + let ls_group_data = localStorage.getItem(GROUP_LS_KEY + '_' + partner) + expect(ls_group_data).to.be.equal(WITH_IIQ); + expect(ls_percent_data).to.be.equal(defaultPercentage + ''); + expect(server.requests.length).to.be.equal(1); + }); + + it('User configuration perentage 0 %', function () { + localStorage.clear(); + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(percentageConfigParams).callback; + expect(submoduleCallback).to.be.undefined; + let ls_percent_data = localStorage.getItem(PRECENT_LS_KEY + '_' + partner) + let ls_group_data = localStorage.getItem(GROUP_LS_KEY + '_' + partner) + expect(ls_group_data).to.be.equal(WITHOUT_IIQ); + expect(ls_percent_data).to.be.equal(userPercentage + ''); + expect(server.requests.length).to.be.equal(0); + }); }); From 3e86ef8e24e4bb1b27738224eb26a06a2eae3500 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Thu, 30 Mar 2023 10:21:08 -0600 Subject: [PATCH 251/375] Revert "IntentIQ Analytics Module : initial release (#9322)" (#9734) This reverts commit 67184b035a46e042cd86049f2b5fec70bfce8cdf. --- modules/intentIqAnalyticsAdapter.js | 241 ------------------ modules/intentIqAnalyticsAdapter.md | 19 -- modules/intentIqIdSystem.js | 48 +--- modules/intentIqIdSystem.md | 80 ------ modules/userId/eids.js | 40 +-- .../modules/intentIqAnalyticsAdapter_spec.js | 129 ---------- test/spec/modules/intentIqIdSystem_spec.js | 37 --- 7 files changed, 21 insertions(+), 573 deletions(-) delete mode 100644 modules/intentIqAnalyticsAdapter.js delete mode 100644 modules/intentIqAnalyticsAdapter.md delete mode 100644 modules/intentIqIdSystem.md delete mode 100644 test/spec/modules/intentIqAnalyticsAdapter_spec.js diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js deleted file mode 100644 index 9e9370a9ef0..00000000000 --- a/modules/intentIqAnalyticsAdapter.js +++ /dev/null @@ -1,241 +0,0 @@ -import { logInfo, logError } from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; -import { ajax } from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { config } from '../src/config.js'; - -const MODULE_NAME = 'iiqAnalytics' -const analyticsType = 'endpoint'; -const defaultUrl = 'https://reports.intentiq.com/report'; -const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); -const prebidVersion = '$prebid.version$'; -const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); - -const FIRST_PARTY_KEY = '_iiq_fdata'; -const FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -const GROUP_LS_KEY = '_iiq_group'; -const PRECENT_LS_KEY = '_iiq_precent'; -const JSVERSION = 5.3 - -const PARAMS_NAMES = { - abPercentage: 'abPercentage', - abTestGroup: 'abGroup', - pbPauseUntill: 'pbPauseUntil', - pbMonitoringEnabled: 'pbMonitoringEnabled', - isInTestGroup: 'isInTestGroup', - enhanceRequests: 'enhanceRequests', - wasSubscribedForPrebid: 'wasSubscribedForPrebid', - hadEids: 'hadEids', - userActualPercentage: 'userPercentage', - ABTestingConfigurationSource: 'ABTestingConfigurationSource', - lateConfiguration: 'lateConfiguration', - jsverion: 'jsversion', - eidsNames: 'eidsNames', - requestRtt: 'rtt', - clientType: 'clientType', - adserverDeviceType: 'AdserverDeviceType', - terminationCause: 'terminationCause', - callCount: 'callCount', - manualCallCount: 'mcc', - pubprovidedidsFailedToregister: 'ppcc', - noDataCount: 'noDataCount', - profile: 'profile', - isProfileDeterministic: 'pidDeterministic', - siteId: 'sid', - hadEidsInLocalStorage: 'idls', - auctionStartTime: 'ast', - eidsReadTime: 'eidt', - agentId: 'aid', - auctionEidsLegth: 'aeidln', - wasServerCalled: 'wsrvcll', - refferer: 'vrref', - isInBrowserBlacklist: 'inbbl', - prebidVersion: 'pbjsver', - partnerId: 'partnerId' -}; - -var initOptions = { - lsValueInitialized: false -} - -// Events needed -const { - EVENTS: { - BID_WON - } -} = CONSTANTS; - -let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analyticsType }), { - track({ eventType, args }) { - switch (eventType) { - case BID_WON: - bidWon(args); - break; - default: - break; - } - } -}); - -function readData(key) { - try { - if (storage.hasLocalStorage()) { - return storage.getDataFromLocalStorage(key); - } - if (storage.cookiesAreEnabled()) { - return storage.getCookie(key); - } - } catch (error) { - logError(error); - } -} - -function initLsValues() { - if (initOptions.lsValueInitialized) return; - initOptions.fpid = readData(FIRST_PARTY_KEY); - let iiqArr = config.getConfig('userSync.userIds').filter(m => m.name == 'intentIqId'); - if (iiqArr && iiqArr.length > 0) initOptions.lsValueInitialized = true; - if (!iiqArr) iiqArr = []; - if (iiqArr.length == 0) { - iiqArr.push({ - 'params': { - 'partner': -1, - 'group': 'U', - 'percentage': -1 - } - }) - } - if (iiqArr && iiqArr.length > 0) { - if (iiqArr[0].params && iiqArr[0].params.partner && !isNaN(iiqArr[0].params.partner)) { - initOptions.partner = iiqArr[0].params.partner; - initOptions.userGroup = iiqArr[0].params.group || 'U'; - initOptions.userPercentage = iiqArr[0].params.percentage || '-1'; - - initOptions.currentGroup = readData(GROUP_LS_KEY + '_' + initOptions.partner) - initOptions.currentPercentage = readData(PRECENT_LS_KEY + '_' + initOptions.partner) - } - } -} - -function initReadLsIds() { - if (isNaN(initOptions.partner) || initOptions.partner == -1) return; - try { - initOptions.dataInLs = null; - let iData = readData(FIRST_PARTY_DATA_KEY + '_' + initOptions.partner) - if (iData) { - initOptions.lsIdsInitialized = true; - let pData = JSON.parse(iData); - initOptions.dataInLs = pData.data; - initOptions.eidl = pData.eidl || -1; - } - } catch (e) { - logError(e) - } -} - -function bidWon(args) { - if (!initOptions.lsValueInitialized) { initLsValues(); } - if (initOptions.lsValueInitialized && !initOptions.lsIdsInitialized) { initReadLsIds(); } - if (!initOptions.manualReport) { ajax(constructFulllUrl(preparePayload(args, true)), undefined, null, { method: 'GET' }); } - - logInfo('IIQ ANALYTICS -> BID WON') -} - -function getRandom(start, end) { - return Math.floor((Math.random() * (end - start + 1)) + start); -} - -function preparePayload(data) { - let result = getDefaultDataObject(); - - result[PARAMS_NAMES.partnerId] = initOptions.partner; - result[PARAMS_NAMES.prebidVersion] = prebidVersion; - result[PARAMS_NAMES.refferer] = getRefferer(); - result[PARAMS_NAMES.userActualPercentage] = initOptions.userPercentage; - - if (initOptions.userGroup && initOptions.userGroup != '') { result[PARAMS_NAMES.ABTestingConfigurationSource] = 'group'; } else if (initOptions.userPercentage && !isNaN(initOptions.userPercentage)) { result[PARAMS_NAMES.ABTestingConfigurationSource] = 'percentage'; } - - result[PARAMS_NAMES.abPercentage] = initOptions.currentPercentage; - result[PARAMS_NAMES.abTestGroup] = initOptions.currentGroup; - - result[PARAMS_NAMES.isInTestGroup] = initOptions.currentGroup == 'A'; - - result[PARAMS_NAMES.agentId] = REPORTER_ID; - - fillPrebidEventData(data, result); - - fillEidsData(result); - - return result; -} - -function fillEidsData(result) { - if (initOptions.lsIdsInitialized) { - result[PARAMS_NAMES.hadEidsInLocalStorage] = initOptions.eidl && initOptions.eidl > 0; - result[PARAMS_NAMES.auctionEidsLegth] = initOptions.eidl || -1; - } -} - -function fillPrebidEventData(eventData, result) { - if (eventData.bidderCode) { result.bidderCode = eventData.bidderCode; } - if (eventData.cpm) { result.cpm = eventData.cpm; } - if (eventData.currency) { result.currency = eventData.currency; } - if (eventData.originalCpm) { result.originalCpm = eventData.originalCpm; } - if (eventData.originalCurrency) { result.originalCurrency = eventData.originalCurrency; } - if (eventData.status) { result.status = eventData.status; } - if (eventData.auctionId) { result.prebidAuctionId = eventData.auctionId; } - - result.biddingPlatformId = 1; - result.partnerAuctionId = 'BW'; -} - -function getDefaultDataObject() { - return { - 'inbbl': false, - 'pbjsver': prebidVersion, - 'partnerAuctionId': 'BW', - 'reportSource': 'pbjs', - 'abPercentage': -1, - 'abGroup': 'U', - 'userPercentage': -1, - 'jsversion': JSVERSION, - 'partnerId': -1, - 'biddingPlatformId': 1, - 'idls': false, - 'ast': -1, - 'aeidln': -1 - } -} - -function constructFulllUrl(data) { - let report = [] - data = btoa(JSON.stringify(data)) - report.push(data) - return defaultUrl + '?pid=' + initOptions.partner + - '&mct=1' + - ((iiqAnalyticsAnalyticsAdapter.initOptions && iiqAnalyticsAnalyticsAdapter.initOptions.fpid) - ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') + - '&agid=' + REPORTER_ID + - '&jsver=' + JSVERSION + - '&source=pbjs' + - '&payload=' + JSON.stringify(report) -} - -function getRefferer() { - return document.referrer; -} - -iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapter.enableAnalytics; - -iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig) { - iiqAnalyticsAnalyticsAdapter.originEnableAnalytics(myConfig); // call the base class function -}; - -adapterManager.registerAnalyticsAdapter({ - adapter: iiqAnalyticsAnalyticsAdapter, - code: MODULE_NAME -}); - -export default iiqAnalyticsAnalyticsAdapter; diff --git a/modules/intentIqAnalyticsAdapter.md b/modules/intentIqAnalyticsAdapter.md deleted file mode 100644 index eb7e0d6b0d6..00000000000 --- a/modules/intentIqAnalyticsAdapter.md +++ /dev/null @@ -1,19 +0,0 @@ -# Overview - -Module Name: iiqAnalytics -Module Type: Analytics Adapter -Maintainer: julian@intentiq.com - -# Description - -By using this Intent IQ adapter, you will be able to obtain comprehensive analytics and metrics regarding the performance of the Intent IQ Unified ID module. This includes how the module impacts your revenue, CPMs, and fill rates related to bidders and domains. - -## Intent IQ Universal ID Registration - -No registration for this module is required. - -## Intent IQ Universal IDConfiguration - -IMPORTANT: requires Intent IQ Universal ID module be installed and configured. [(How-To)](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) - -No aaditional configuration for this module is required. We will use the configuration provided for Intent IQ Universal IQ module. diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 9588f2e53a3..563435dee65 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -15,20 +15,11 @@ const PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; export const FIRST_PARTY_KEY = '_iiq_fdata'; export var FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -export var GROUP_LS_KEY = '_iiq_group'; -export var WITH_IIQ = 'A'; -export var WITHOUT_IIQ = 'B'; -export var PRECENT_LS_KEY = '_iiq_precent'; -export var DEFAULT_PERCENTAGE = 100; export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); const INVALID_ID = 'INVALID_ID'; -function getRandom(start, end) { - return Math.floor(Math.random() * (end - start + 1) + start); -} - /** * Generate standard UUID string * @return {string} @@ -53,9 +44,6 @@ export function readData(key) { if (storage.hasLocalStorage()) { return storage.getDataFromLocalStorage(key); } - if (localStorage) { - return localStorage.getItem(key) - } if (storage.cookiesAreEnabled()) { return storage.getCookie(key); } @@ -77,9 +65,6 @@ function storeData(key, value) { if (value) { if (storage.hasLocalStorage()) { storage.setDataInLocalStorage(key, value); - } else - if (localStorage) { - localStorage.setItem(key, value) } const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); if (storage.cookiesAreEnabled()) { @@ -118,7 +103,7 @@ export const intentIqIdSubmodule = { * @param {{string}} value * @returns {{intentIqId: {string}}|undefined} */ - decode(value, config) { + decode(value) { return value && value != '' && INVALID_ID != value ? { 'intentIqId': value } : undefined; }, /** @@ -133,32 +118,6 @@ export const intentIqIdSubmodule = { logError('User ID - intentIqId submodule requires a valid partner to be defined'); return; } - - if (isNaN(configParams.percentage)) { - logInfo(MODULE_NAME + ' AB Testing percentage is not defined. Setting default value = ' + DEFAULT_PERCENTAGE); - configParams.percentage = DEFAULT_PERCENTAGE; - } - - if (isNaN(configParams.percentage) || configParams.percentage < 0 || configParams.percentage > 100) { - logError(MODULE_NAME + 'Percentage - intentIqId submodule requires a valid percentage value'); - return false; - } - - configParams.group = readData(GROUP_LS_KEY + '_' + configParams.partner); - let percentage = readData(PRECENT_LS_KEY + '_' + configParams.partner); - - if (!configParams.group || !percentage || isNaN(percentage) || percentage != configParams.percentage) { - logInfo(MODULE_NAME + 'Generating new Group. Current test group: ' + configParams.group + ', current percentage: ' + percentage + ' , configured percentage: ' + configParams.percentage); - if (configParams.percentage > getRandom(1, 100)) { configParams.group = WITH_IIQ; } else configParams.group = WITHOUT_IIQ; - storeData(GROUP_LS_KEY + '_' + configParams.partner, configParams.group) - storeData(PRECENT_LS_KEY + '_' + configParams.partner, configParams.percentage + '') - logInfo(MODULE_NAME + 'New group: ' + configParams.group) - } - if (configParams.group == WITHOUT_IIQ) { - logInfo(MODULE_NAME + 'Group "B". Passive Mode ON.'); - return true; - } - if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { FIRST_PARTY_DATA_KEY += '_' + configParams.partner; } let rrttStrtTime = 0; @@ -198,11 +157,6 @@ export const intentIqIdSubmodule = { partnerData.cttl = respJson.cttl; shouldUpdateLs = true; } - - if ('eidl' in respJson) { - partnerData.eidl = respJson.eidl; - } - // If should save and data is empty, means we should save as INVALID_ID if (respJson.data == '') { respJson.data = INVALID_ID; diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md deleted file mode 100644 index 70c8919d0d2..00000000000 --- a/modules/intentIqIdSystem.md +++ /dev/null @@ -1,80 +0,0 @@ -# Intent IQ Universal ID module - -By leveraging the Intent IQ identity graph, our module helps publishers, SSPs, and DSPs overcome the challenges of monetizing cookie-less inventory and preparing for a future without 3rd-party cookies. Our solution implements 1st-party data clustering and provides Intent IQ person IDs with over 90% coverage and unmatched accuracy in supported countries while remaining privacy-friendly and CCPA compliant. This results in increased CPMs, higher fill rates, and, ultimately, lifting overall revenue - -# All you need is a few basic steps to start using our solution. - -## Registration - -Navigate to [our portal ](https://www.intentiq.com/) and contact our team for partner ID. -check our [documentation](https://pbmodule.documents.intentiq.com/) to get more information about our solution and how utilze it's full potential - -## Integration - -``` -gulp build –modules=intentIqIdSystem -``` -We recommend including the Intent IQ Analytics adapter module for improved visibility - -## Configuration - -### Parameters - -Please find below list of paramters that could be used in configuring Intent IQ Universal ID module - -| Param under userSync.userIds[] | Scope | Type | Description | Example | -| ------------------------------ | -------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | -| name | Required | String | The name of this module: "intentIqId" | `"intentIqId"` | -| params | Required | Object | Details for IntentIqId initialization. | | -| params.partner | Required | Number | This is the partner ID value obtained from registering with IntentIQ. | `1177538` | -| params.percentage | Required | Number | This a percentage value for our A/B testing group distribution. The values supposed to be in range of 0 to 100. We suggest to set it to 95 percent for optimal balance ofbetween prefromance and preceision. | `95` | -| params.pcid | Optional | String | This is the partner cookie ID, it is a dynamic value attached to the request. | `"g3hC52b"` | -| params.pai | Optional | String | This is the partner customer ID / advertiser ID, it is a dynamic value attached to the request. | `"advertiser1"` | - -### Configuration example - -```javascript -pbjs.setConfig({ - userSync: { - userIds: [ - { - name: "intentIqId", - params: { - partner: 123456, // valid partner id - percentage: 95, - }, - storage: { - type: "html5", - name: "intentIqId", // set localstorage with this name - expires: 60, - refreshInSeconds: 4 * 3600, // refresh ID every 4 hours to ensure it's fresh - }, - }, - ], - syncDelay: 3000, - }, -}); -``` - -```javascript -pbjs.setConfig({ - userSync: { - userIds: [{ - name: "intentIqId", - params: { - partner: 123456 // valid partner id - pcid: PCID_VARIABLE, // string value, dynamically loaded into a variable before setting the configuration - pai: PAI_VARIABLE , // string value, dynamically loaded into a variable before setting the configuration - percentage: 95 - }, - storage: { - type: "html5", - name: "intentIqId", // set localstorage with this name - expires: 60 - } - }], - syncDelay: 3000 - } -}); -``` - diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 99fca9df3c5..a96dbaee4af 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -7,12 +7,12 @@ export const USER_IDS_CONFIG = { // GrowthCode 'growthCodeId': { - getValue: function (data) { + getValue: function(data) { return data.gc_id }, source: 'growthcode.io', atype: 1, - getUidExt: function (data) { + getUidExt: function(data) { const extendedData = pick(data, [ 'h1', 'h2', @@ -61,7 +61,7 @@ export const USER_IDS_CONFIG = { 'tdid': { source: 'adserver.org', atype: 1, - getUidExt: function () { + getUidExt: function() { return { rtiPartner: 'TDID' }; @@ -70,12 +70,12 @@ export const USER_IDS_CONFIG = { // id5Id 'id5id': { - getValue: function (data) { + getValue: function(data) { return data.uid }, source: 'id5-sync.com', atype: 1, - getUidExt: function (data) { + getUidExt: function(data) { if (data.ext) { return data.ext; } @@ -86,14 +86,14 @@ export const USER_IDS_CONFIG = { 'ftrackId': { source: 'flashtalking.com', atype: 1, - getValue: function (data) { + getValue: function(data) { let value = ''; if (data && data.ext && data.ext.DeviceID) { value = data.ext.DeviceID; } return value; }, - getUidExt: function (data) { + getUidExt: function(data) { return data && data.ext; } }, @@ -102,7 +102,7 @@ export const USER_IDS_CONFIG = { 'parrableId': { source: 'parrable.com', atype: 1, - getValue: function (parrableId) { + getValue: function(parrableId) { if (parrableId.eid) { return parrableId.eid; } @@ -113,7 +113,7 @@ export const USER_IDS_CONFIG = { } return null; }, - getUidExt: function (parrableId) { + getUidExt: function(parrableId) { const extendedData = pick(parrableId, [ 'ibaOptout', 'ccpaOptout' @@ -132,12 +132,12 @@ export const USER_IDS_CONFIG = { // liveIntentId 'lipb': { - getValue: function (data) { + getValue: function(data) { return data.lipbid; }, source: 'liveintent.com', atype: 3, - getEidExt: function (data) { + getEidExt: function(data) { if (Array.isArray(data.segments) && data.segments.length) { return { segments: data.segments @@ -173,16 +173,16 @@ export const USER_IDS_CONFIG = { // merkleId 'merkleId': { atype: 3, - getSource: function (data) { + getSource: function(data) { if (data?.ext?.ssp) { return `${data.ext.ssp}.merkleinc.com` } return 'merkleinc.com' }, - getValue: function (data) { + getValue: function(data) { return data.id; }, - getUidExt: function (data) { + getUidExt: function(data) { if (data.keyID) { return { keyID: data.keyID @@ -262,7 +262,7 @@ export const USER_IDS_CONFIG = { 'uid2': { source: 'uidapi.com', atype: 3, - getValue: function (data) { + getValue: function(data) { return data.id; } }, @@ -331,7 +331,7 @@ export const USER_IDS_CONFIG = { '33acrossId': { source: '33across.com', atype: 1, - getValue: function (data) { + getValue: function(data) { return data.envelope; } }, @@ -356,19 +356,19 @@ export const USER_IDS_CONFIG = { // OneKey Data 'oneKeyData': { - getValue: function (data) { + getValue: function(data) { if (data && Array.isArray(data.identifiers) && data.identifiers[0]) { return data.identifiers[0].value; } }, source: 'paf', atype: 1, - getEidExt: function (data) { + getEidExt: function(data) { if (data && data.preferences) { - return { preferences: data.preferences }; + return {preferences: data.preferences}; } }, - getUidExt: function (data) { + getUidExt: function(data) { if (data && Array.isArray(data.identifiers) && data.identifiers[0]) { const id = data.identifiers[0]; return { diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js deleted file mode 100644 index 3e9193987c1..00000000000 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ /dev/null @@ -1,129 +0,0 @@ -import { expect } from 'chai'; -import iiqAnalyticsAnalyticsAdapter from 'modules/intentIqAnalyticsAdapter.js' -// import * as utils from 'src/utils.js'; -// import { server } from 'test/mocks/xhr.js'; -// import adapterManager from 'src/adapterManager.js'; -import { config } from 'src/config.js'; -let events = require('src/events'); -let constants = require('src/constants.json'); - -const partner = 10; -const pai = '11'; -const pcid = '12'; -const userPercentage = 0; -const defaultPercentage = 100; -const defaultConfigParams = { params: { partner: partner } }; -const percentageConfigParams = { params: { partner: partner, percentage: userPercentage } }; -const paiConfigParams = { params: { partner: partner, pai: pai } }; -const pcidConfigParams = { params: { partner: partner, pcid: pcid } }; -const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid } }; -const responseHeader = { 'Content-Type': 'application/json' } - -const testData = { data: 'test' } - -const FIRST_PARTY_DATA_KEY = '_iiq_fdata' -const PRECENT_LS_KEY = '_iiq_precent' -const GROUP_LS_KEY = '_iiq_group' -const WITH_IIQ = 'A' -const WITHOUT_IIQ = 'B' - -const USERID_CONFIG = [ - { - 'name': 'intentIqId', - 'params': { - 'partner': partner, - 'unpack': null, - 'percentage': 100, - }, - 'storage': { - 'type': 'html5', - 'name': 'intentIqId', - 'expires': 60, - 'refreshInSeconds': 14400 - } - } -] - -let wonRequest = { - 'bidderCode': 'pubmatic', - 'width': 728, - 'height': 90, - 'statusMessage': 'Bid available', - 'adId': '23caeb34c55da51', - 'requestId': '87615b45ca4973', - 'transactionId': '5e69fd76-8c86-496a-85ce-41ae55787a50', - 'auctionId': '0cbd3a43-ff45-47b8-b002-16d3946b23bf', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 5, - 'currency': 'USD', - 'ttl': 300, - 'referrer': '', - 'adapterCode': 'pubmatic', - 'originalCpm': 5, - 'originalCurrency': 'USD', - 'responseTimestamp': 1669644710345, - 'requestTimestamp': 1669644710109, - 'bidder': 'testbidder', - 'adUnitCode': 'addUnitCode', - 'timeToRespond': 236, - 'pbLg': '5.00', - 'pbMg': '5.00', - 'pbHg': '5.00', - 'pbAg': '5.00', - 'pbDg': '5.00', - 'pbCg': '', - 'size': '728x90', - 'status': 'rendered', - -}; - -// adapterManager.registerAnalyticsAdapter({ -// code: 'iiqAnalytics', -// adapter: iiqAnalyticsAnalyticsAdapter -// }); - -describe('IntentIQ tests all', function () { - let ixhr; - let requests = []; - - beforeEach(function () { - sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns( - USERID_CONFIG - ); - ixhr = sinon.useFakeXMLHttpRequest(); - requests = []; - ixhr.onCreate = (a) => { - requests.push(a); - }; - sinon.stub(events, 'getEvents').returns([]); - iiqAnalyticsAnalyticsAdapter.enableAnalytics({ - provider: 'iiqAnalytics', - }); - sinon.spy(iiqAnalyticsAnalyticsAdapter, 'track'); - }); - afterEach(function () { - config.getConfig.restore(); - events.getEvents.restore(); - requests = []; - ixhr.restore(); - iiqAnalyticsAnalyticsAdapter.disableAnalytics(); - iiqAnalyticsAnalyticsAdapter.track.restore(); - }); - - describe('IntentIQ tests', function () { - it('IIQ Analytical Adapter bid win report', function () { - iiqAnalyticsAnalyticsAdapter.enableAnalytics({ - provider: 'iiqAnalytics' - }); - - localStorage.setItem(PRECENT_LS_KEY + '_' + partner, '95') - localStorage.setItem(GROUP_LS_KEY + '_' + partner, 'A') - localStorage.setItem(FIRST_PARTY_DATA_KEY + '_' + partner, '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344"}') - - events.emit(constants.EVENTS.BID_WON, wonRequest); - expect(requests[0].url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1&agid=') - expect(requests[0].url).to.contain('&jsver=5.3&source=pbjs&payload=') - }); - }); -}); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index e555af1d4c0..f9bfa577e3a 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -6,23 +6,12 @@ import { server } from 'test/mocks/xhr.js'; const partner = 10; const pai = '11'; const pcid = '12'; -const userPercentage = 0; -const defaultPercentage = 100; const defaultConfigParams = { params: { partner: partner } }; -const percentageConfigParams = { params: { partner: partner, percentage: userPercentage } }; const paiConfigParams = { params: { partner: partner, pai: pai } }; const pcidConfigParams = { params: { partner: partner, pcid: pcid } }; const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid } }; const responseHeader = { 'Content-Type': 'application/json' } -const testData = { data: 'test' } - -const FIRST_PARTY_DATA_KEY = '_iiq_fdata' -const PRECENT_LS_KEY = '_iiq_precent' -const GROUP_LS_KEY = '_iiq_group' -const WITH_IIQ = 'A' -const WITHOUT_IIQ = 'B' - describe('IntentIQ tests', function () { let logErrorStub; let testLSValue = { @@ -239,30 +228,4 @@ describe('IntentIQ tests', function () { expect(callBackSpy.calledOnce).to.be.true; expect(callBackSpy.args[0][0]).to.be.equal(testLSValueWithData.data); }); - - it('Default percentage and group without user config 100 %', function () { - localStorage.clear(); - let callBackSpy = sinon.spy(); - let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; - if (submoduleCallback) { - submoduleCallback(callBackSpy); - } - let ls_percent_data = localStorage.getItem(PRECENT_LS_KEY + '_' + partner) - let ls_group_data = localStorage.getItem(GROUP_LS_KEY + '_' + partner) - expect(ls_group_data).to.be.equal(WITH_IIQ); - expect(ls_percent_data).to.be.equal(defaultPercentage + ''); - expect(server.requests.length).to.be.equal(1); - }); - - it('User configuration perentage 0 %', function () { - localStorage.clear(); - let callBackSpy = sinon.spy(); - let submoduleCallback = intentIqIdSubmodule.getId(percentageConfigParams).callback; - expect(submoduleCallback).to.be.undefined; - let ls_percent_data = localStorage.getItem(PRECENT_LS_KEY + '_' + partner) - let ls_group_data = localStorage.getItem(GROUP_LS_KEY + '_' + partner) - expect(ls_group_data).to.be.equal(WITHOUT_IIQ); - expect(ls_percent_data).to.be.equal(userPercentage + ''); - expect(server.requests.length).to.be.equal(0); - }); }); From 8455bd220715512b955ffdbd7b495c7b031cf26b Mon Sep 17 00:00:00 2001 From: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:13:45 +0300 Subject: [PATCH 252/375] Taboola Bid Adapter: resolve AUCTION_PRICE macro (#9735) * replace-macro * replace-macro * replace-macro --- modules/taboolaBidAdapter.js | 4 +- test/spec/modules/taboolaBidAdapter_spec.js | 101 ++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 026d5a098df..a833d8ec552 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -278,7 +278,7 @@ function getBid(bids, currency, bidResponse) { if (!bidResponse) { return; } - const { + let { price: cpm, nurl, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} } = bidResponse; let requestId = bids[bidResponse.impid - 1].bidId; @@ -286,6 +286,8 @@ function getBid(bids, currency, bidResponse) { meta.advertiserDomains = advertiserDomains } + ad = replaceAuctionPrice(ad, cpm); + return { requestId, ttl, diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 0c01244033e..206a0142043 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -668,6 +668,107 @@ describe('Taboola Adapter', function () { const res = spec.interpretResponse(serverResponse, request); expect(res).to.deep.equal(expectedRes); }); + + it('should replace AUCTION_PRICE macro in adm', function () { + const multiRequest = { + bids: [ + { + ...createBidRequest(), + ...displayBidRequestParams + }, + { + ...createBidRequest(), + ...displayBidRequestParams + } + ] + } + const multiServerResponseWithMacro = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '2', + 'price': 0.34, + 'adid': '2785119545551083381', + 'adm': 'ADM2,\\nwp:\'${AUCTION_PRICE}\'', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + }, + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.35, + 'adid': '2785119545551083381', + 'adm': 'ADM2,\\nwp:\'${AUCTION_PRICE}\'', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' + } + }; + const [bid] = multiServerResponseWithMacro.body.seatbid[0].bid; + const expectedRes = [ + { + requestId: multiRequest.bids[1].bidId, + cpm: multiServerResponseWithMacro.body.seatbid[0].bid[0].price, + creativeId: bid.crid, + ttl: 60, + netRevenue: true, + currency: multiServerResponseWithMacro.body.cur, + mediaType: 'banner', + ad: 'ADM2,\\nwp:\'0.34\'', + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + }, + { + requestId: multiRequest.bids[0].bidId, + cpm: multiServerResponseWithMacro.body.seatbid[0].bid[1].price, + creativeId: bid.crid, + ttl: 60, + netRevenue: true, + currency: multiServerResponseWithMacro.body.cur, + mediaType: 'banner', + ad: 'ADM2,\\nwp:\'0.35\'', + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + } + ]; + const res = spec.interpretResponse(multiServerResponseWithMacro, multiRequest); + expect(res).to.deep.equal(expectedRes); + }); }) describe('getUserSyncs', function () { From 78c1dd30a8e646d3d1e49a890578b885960ab887 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 30 Mar 2023 11:26:28 -0700 Subject: [PATCH 253/375] Prebid Server adapter: fledge support (#9342) * Move fledge logic to fledge module * Change fledge interpretResponse API: from {bidId, ...fledgeAuctionConfig} to {bidId, config: fledgeAuctionConfig} * Fledge ORTB processors * PBS adapter fledge impl * Update modules/fledgeForGpt.js Co-authored-by: Laurentiu Badea * fix fledge tests to pass adUnitCode * Update text * Fix test case to reflect check on `navigator.joinAdInterestGroup` * Adjust warnings * Name change --------- Co-authored-by: Laurentiu Badea --- libraries/ortbConverter/converter.js | 5 +- modules/fledgeForGpt.js | 60 +++++- modules/openxOrtbBidAdapter.js | 12 +- modules/prebidServerBidAdapter/index.js | 12 +- .../prebidServerBidAdapter/ortbConverter.js | 14 +- modules/rtbhouseBidAdapter.js | 8 +- src/adapterManager.js | 7 - src/adapters/bidderFactory.js | 43 +++-- test/spec/modules/fledge_spec.js | 175 +++++++++++++++++- .../modules/prebidServerBidAdapter_spec.js | 70 ++++++- test/spec/unit/core/adapterManager_spec.js | 58 ------ test/spec/unit/core/bidderFactory_spec.js | 38 +++- 12 files changed, 385 insertions(+), 117 deletions(-) diff --git a/libraries/ortbConverter/converter.js b/libraries/ortbConverter/converter.js index 2057af8b6d3..c367aec268a 100644 --- a/libraries/ortbConverter/converter.js +++ b/libraries/ortbConverter/converter.js @@ -90,12 +90,13 @@ export function ortbConverter({ req: Object.assign({bidRequests}, defaultContext, context), imp: {} } + ctx.req.impContext = ctx.imp; const imps = bidRequests.map(bidRequest => { const impContext = Object.assign({bidderRequest, reqContext: ctx.req}, defaultContext, context); const result = buildImp(bidRequest, impContext); if (result != null) { if (result.hasOwnProperty('id')) { - impContext.bidRequest = bidRequest; + Object.assign(impContext, {bidRequest, imp: result}); ctx.imp[result.id] = impContext; return result; } @@ -116,7 +117,7 @@ export function ortbConverter({ throw new Error('ortbRequest passed to `fromORTB` must be the same object returned by `toORTB`') } function augmentContext(ctx, extraParams = {}) { - return Object.assign({ortbRequest: request}, extraParams, ctx); + return Object.assign(ctx, {ortbRequest: request}, extraParams, ctx); } const impsById = Object.fromEntries((request.imp || []).map(imp => [imp.id, imp])); const bidResponses = (response.seatbid || []).flatMap(seatbid => diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index 0f7092baf03..f29ce7508d5 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -5,6 +5,7 @@ import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; import { getGptSlotForAdUnitCode, logInfo, logWarn } from '../src/utils.js'; +import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; const MODULE = 'fledgeForGpt' @@ -21,22 +22,20 @@ export function init(cfg) { getHook('addComponentAuction').before(addComponentAuctionHook); isEnabled = true; } - logInfo(MODULE, `isEnabled`, cfg); + logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} fledge)`, cfg); } else { if (isEnabled) { getHook('addComponentAuction').getHooks({hook: addComponentAuctionHook}).remove(); isEnabled = false; } - logInfo(MODULE, `isDisabled`, cfg); + logInfo(`${MODULE} disabled`, cfg); } } -export function addComponentAuctionHook(next, bidRequest, componentAuctionConfig) { +export function addComponentAuctionHook(next, adUnitCode, componentAuctionConfig) { const seller = componentAuctionConfig.seller; - const adUnitCode = bidRequest.adUnitCode; const gptSlot = getGptSlotForAdUnitCode(adUnitCode); if (gptSlot && gptSlot.setConfig) { - delete componentAuctionConfig.bidId; gptSlot.setConfig({ componentAuction: [{ configKey: seller, @@ -48,5 +47,54 @@ export function addComponentAuctionHook(next, bidRequest, componentAuctionConfig logWarn(MODULE, `unable to register component auction config for: ${adUnitCode} x ${seller}.`); } - next(bidRequest, componentAuctionConfig); + next(adUnitCode, componentAuctionConfig); } + +function isFledgeSupported() { + return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator +} + +export function markForFledge(next, bidderRequests) { + if (isFledgeSupported()) { + bidderRequests.forEach((req) => { + req.fledgeEnabled = config.runWithBidder(req.bidderCode, () => config.getConfig('fledgeEnabled')) + }) + } + next(bidderRequests); +} +getHook('makeBidRequests').after(markForFledge); + +export function setImpExtAe(imp, bidRequest, context) { + if (!context.bidderRequest.fledgeEnabled) { + delete imp.ext?.ae; + } +} +registerOrtbProcessor({type: IMP, name: 'impExtAe', fn: setImpExtAe}); + +// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up +// fledge response processing in two steps: first aggregate all the auction configs by their imp... + +export function parseExtPrebidFledge(response, ortbResponse, context) { + (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []).forEach((cfg) => { + const impCtx = context.impContext[cfg.impid]; + if (!impCtx?.imp?.ext?.ae) { + logWarn('Received fledge auction configuration for an impression that was not in the request or did not ask for it', cfg, impCtx?.imp); + } else { + impCtx.fledgeConfigs = impCtx.fledgeConfigs || []; + impCtx.fledgeConfigs.push(cfg); + } + }) +} +registerOrtbProcessor({type: RESPONSE, name: 'extPrebidFledge', fn: parseExtPrebidFledge, dialects: [PBS]}); + +// ...then, make them available in the adapter's response. This is the client side version, for which the +// interpretResponse api is {fledgeAuctionConfigs: [{bidId, config}]} + +export function setResponseFledgeConfigs(response, ortbResponse, context) { + const configs = Object.values(context.impContext) + .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({bidId: impCtx.bidRequest.bidId, config: cfg.config}))); + if (configs.length > 0) { + response.fledgeAuctionConfigs = configs; + } +} +registerOrtbProcessor({type: RESPONSE, name: 'fledgeAuctionConfigs', priority: -1, fn: setResponseFledgeConfigs, dialects: [PBS]}) diff --git a/modules/openxOrtbBidAdapter.js b/modules/openxOrtbBidAdapter.js index 200d2cf0fed..e550423094f 100644 --- a/modules/openxOrtbBidAdapter.js +++ b/modules/openxOrtbBidAdapter.js @@ -32,10 +32,6 @@ const converter = ortbConverter({ if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') { imp.video.placement = imp.video.placement || 4; } - if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) { - // TODO: we may want to standardize this and move fledge logic to ortbConverter - delete imp.ext.ae; - } mergeDeep(imp, { tagid: bidRequest.params.unit, ext: { @@ -106,10 +102,12 @@ const converter = ortbConverter({ let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); if (fledgeAuctionConfigs) { fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return Object.assign({ + return { bidId, - auctionSignals: {} - }, cfg); + config: Object.assign({ + auctionSignals: {}, + }, cfg) + } }); return { bids: response.bids, diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index c5f082f5355..ffb02204ed0 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -20,7 +20,7 @@ import { import CONSTANTS from '../../src/constants.json'; import adapterManager from '../../src/adapterManager.js'; import {config} from '../../src/config.js'; -import {isValid} from '../../src/adapters/bidderFactory.js'; +import {addComponentAuction, isValid} from '../../src/adapters/bidderFactory.js'; import * as events from '../../src/events.js'; import {includes} from '../../src/polyfill.js'; import {S2S_VENDORS} from './config.js'; @@ -496,6 +496,9 @@ export function PrebidServer() { addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.INVALID); } } + }, + onFledge: ({adUnitCode, config}) => { + addComponentAuction(adUnitCode, config); } }) } @@ -521,7 +524,7 @@ export function PrebidServer() { * @param onError {function(String, {})} invoked on HTTP failure - with status message and XHR error * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse */ -export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}) { +export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid, onFledge}) { let { gdprConsent } = getConsentData(bidRequests); const adUnits = deepClone(s2sBidRequest.ad_units); @@ -545,8 +548,11 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let result; try { result = JSON.parse(response); - const bids = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request).bids); + const {bids, fledgeAuctionConfigs} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); bids.forEach(onBid); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs.forEach(onFledge); + } } catch (error) { logError(error); } diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index be034b3cfbe..820c34c66fd 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -221,6 +221,13 @@ const PBS_CONVERTER = ortbConverter({ serverSideStats(orig, response, ortbResponse, context) { // override to process each request context.actualBidderRequests.forEach(req => orig(response, ortbResponse, {...context, bidderRequest: req, bidRequests: req.bids})); + }, + fledgeAuctionConfigs(orig, response, ortbResponse, context) { + const configs = Object.values(context.impContext) + .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({adUnitCode: impCtx.adUnit.code, config: cfg.config}))); + if (configs.length > 0) { + response.fledgeAuctionConfigs = configs; + } } } }, @@ -254,11 +261,14 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste ...adUnit, adUnitCode: adUnit.code, ...getDefinedParams(actualBidRequests.values().next().value || {}, ['userId', 'userIdAsEids', 'schain']), - pbsData: {impId, actualBidRequests, adUnit} + pbsData: {impId, actualBidRequests, adUnit}, }); }); - const proxyBidderRequest = Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))) + const proxyBidderRequest = { + ...Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))), + fledgeEnabled: bidderRequests.some(req => req.fledgeEnabled) + } return PBS_CONVERTER.toORTB({ bidderRequest: proxyBidderRequest, diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index c01ce5e7db7..5c3d430aadf 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -171,10 +171,12 @@ export const spec = { if (fledgeAuctionConfigs) { fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return Object.assign({ + return { bidId, - auctionSignals: {} - }, cfg); + config: Object.assign({ + auctionSignals: {} + }, cfg) + } }); logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { diff --git a/src/adapterManager.js b/src/adapterManager.js index f2f0a6d6432..850488af8ae 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -338,13 +338,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a } }); - bidRequests.forEach(bidRequest => { - config.runWithBidder(bidRequest.bidderCode, () => { - const fledgeEnabledFromConfig = config.getConfig('fledgeEnabled'); - bidRequest['fledgeEnabled'] = navigator.runAdAuction && fledgeEnabledFromConfig - }); - }); - return bidRequests; }, 'makeBidRequests'); diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 1b38e1e652b..d6a43e409e3 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -1,20 +1,32 @@ import Adapter from '../adapter.js'; import adapterManager from '../adapterManager.js'; -import { config } from '../config.js'; -import { createBid } from '../bidfactory.js'; -import { userSync } from '../userSync.js'; -import { nativeBidIsValid } from '../native.js'; -import { isValidVideoBid } from '../video.js'; +import {config} from '../config.js'; +import {createBid} from '../bidfactory.js'; +import {userSync} from '../userSync.js'; +import {nativeBidIsValid} from '../native.js'; +import {isValidVideoBid} from '../video.js'; import CONSTANTS from '../constants.json'; import * as events from '../events.js'; import {includes} from '../polyfill.js'; -import { ajax } from '../ajax.js'; -import { logWarn, logInfo, logError, parseQueryStringParameters, delayExecution, parseSizesInput, flatten, uniques, timestamp, deepAccess, isArray, isPlainObject } from '../utils.js'; -import { ADPOD } from '../mediaTypes.js'; -import { getHook, hook } from '../hook.js'; -import { getCoreStorageManager } from '../storageManager.js'; +import {ajax} from '../ajax.js'; +import { + deepAccess, + delayExecution, + flatten, + isArray, + isPlainObject, + logError, + logWarn, + parseQueryStringParameters, + parseSizesInput, + timestamp, + uniques +} from '../utils.js'; +import {ADPOD} from '../mediaTypes.js'; +import {getHook, hook} from '../hook.js'; +import {getCoreStorageManager} from '../storageManager.js'; import {auctionManager} from '../auctionManager.js'; -import { bidderSettings } from '../bidderSettings.js'; +import {bidderSettings} from '../bidderSettings.js'; import {useMetrics} from '../utils/perfMetrics.js'; export const storage = getCoreStorageManager('bidderFactory'); @@ -247,7 +259,9 @@ export function newBidder(spec) { fledgeAuctionConfigs.forEach((fledgeAuctionConfig) => { const bidRequest = bidRequestMap[fledgeAuctionConfig.bidId]; if (bidRequest) { - addComponentAuction(bidRequest, fledgeAuctionConfig); + addComponentAuction(bidRequest.adUnitCode, fledgeAuctionConfig.config); + } else { + logWarn('Received fledge auction configuration for an unknown bidId', fledgeAuctionConfig); } }); }, @@ -467,9 +481,8 @@ export const registerSyncInner = hook('async', function(spec, responses, gdprCon } }, 'registerSyncs') -export const addComponentAuction = hook('sync', (_bidRequest, fledgeAuctionConfig) => { - logInfo(`bidderFactory.addComponentAuction`, fledgeAuctionConfig); -}, 'addComponentAuction') +export const addComponentAuction = hook('sync', (adUnitCode, fledgeAuctionConfig) => { +}, 'addComponentAuction'); export function preloadBidderMappingFile(fn, adUnits) { if (FEATURES.VIDEO) { diff --git a/test/spec/modules/fledge_spec.js b/test/spec/modules/fledge_spec.js index 2094ab42438..a81ff05596e 100644 --- a/test/spec/modules/fledge_spec.js +++ b/test/spec/modules/fledge_spec.js @@ -2,6 +2,13 @@ import { expect } from 'chai'; import * as fledge from 'modules/fledgeForGpt.js'; +import {config} from '../../../src/config.js'; +import adapterManager from '../../../src/adapterManager.js'; +import * as utils from '../../../src/utils.js'; +import {hook} from '../../../src/hook.js'; +import 'modules/appnexusBidAdapter.js'; +import 'modules/rubiconBidAdapter.js'; +import {parseExtPrebidFledge, setImpExtAe, setResponseFledgeConfigs} from 'modules/fledgeForGpt.js'; const CODE = 'sampleBidder'; const AD_UNIT_CODE = 'mock/placement'; @@ -29,9 +36,173 @@ describe('fledgeForGpt module', function() { nextFnSpy = sinon.spy(); }); - it('should call next() when a proper bidrequest and fledgeAuctionConfig are provided', function() { - fledge.addComponentAuctionHook(nextFnSpy, bidRequest, fledgeAuctionConfig); + it('should call next() when a proper adUnitCode and fledgeAuctionConfig are provided', function() { + fledge.addComponentAuctionHook(nextFnSpy, bidRequest.adUnitCode, fledgeAuctionConfig); expect(nextFnSpy.called).to.be.true; }); }); }); + +describe('fledgeEnabled', function () { + const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])) + + before(function () { + // navigator.runAdAuction & co may not exist, so we can't stub it normally with + // sinon.stub(navigator, 'runAdAuction') or something + Object.keys(navProps).forEach(p => { navigator[p] = sinon.stub() }); + hook.ready(); + }); + + after(function() { + Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + }) + + afterEach(function () { + config.resetConfig(); + }); + + it('should set fledgeEnabled correctly per bidder', function () { + config.setConfig({bidderSequence: 'fixed'}) + config.setBidderConfig({ + bidders: ['appnexus'], + config: { + fledgeEnabled: true, + } + }); + + const adUnits = [{ + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + }, + }, + 'bids': [ + { + 'bidder': 'appnexus', + }, + { + 'bidder': 'rubicon', + }, + ] + }]; + + const bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].fledgeEnabled).to.be.true; + + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].fledgeEnabled).to.be.undefined; + }); +}); + +describe('ortb processors for fledge', () => { + describe('imp.ext.ae', () => { + it('should be removed if fledge is not enabled', () => { + const imp = {ext: {ae: 1}}; + setImpExtAe(imp, {}, {bidderRequest: {}}); + expect(imp.ext.ae).to.not.exist; + }) + it('should be left intact if fledge is enabled', () => { + const imp = {ext: {ae: false}}; + setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); + expect(imp.ext.ae).to.equal(false); + }); + }); + describe('parseExtPrebidFledge', () => { + function packageConfigs(configs) { + return { + ext: { + prebid: { + fledge: { + auctionconfigs: configs + } + } + } + } + } + + function generateImpCtx(fledgeFlags) { + return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); + } + + function generateCfg(impid, ...ids) { + return ids.map((id) => ({impid, config: {id}})); + } + + function extractResult(ctx) { + return Object.fromEntries( + Object.entries(ctx) + .map(([impid, ctx]) => [impid, ctx.fledgeConfigs?.map(cfg => cfg.config.id)]) + .filter(([_, val]) => val != null) + ); + } + + it('should collect fledge configs by imp', () => { + const ctx = { + impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) + }; + const resp = packageConfigs( + generateCfg('e1', 1, 2, 3) + .concat(generateCfg('e2', 4) + .concat(generateCfg('d1', 5, 6))) + ); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({ + e1: [1, 2, 3], + e2: [4], + }); + }); + it('should not choke if fledge config references unknown imp', () => { + const ctx = {impContext: generateImpCtx({i: 1})}; + const resp = packageConfigs(generateCfg('unknown', 1)); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({}); + }); + }); + describe('setResponseFledgeConfigs', () => { + it('should set fledgeAuctionConfigs paired with their corresponding bid id', () => { + const ctx = { + impContext: { + 1: { + bidRequest: {bidId: 'bid1'}, + fledgeConfigs: [{config: {id: 1}}, {config: {id: 2}}] + }, + 2: { + bidRequest: {bidId: 'bid2'}, + fledgeConfigs: [{config: {id: 3}}] + }, + 3: { + bidRequest: {bidId: 'bid3'} + } + } + }; + const resp = {}; + setResponseFledgeConfigs(resp, {}, ctx); + expect(resp.fledgeAuctionConfigs).to.eql([ + {bidId: 'bid1', config: {id: 1}}, + {bidId: 'bid1', config: {id: 2}}, + {bidId: 'bid2', config: {id: 3}}, + ]); + }); + it('should not set fledgeAuctionConfigs if none exist', () => { + const resp = {}; + setResponseFledgeConfigs(resp, {}, { + impContext: { + 1: { + fledgeConfigs: [] + }, + 2: {} + } + }); + expect(resp).to.eql({}); + }); + }); +}); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index be2c6bf67f0..9516f0402c1 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -8,7 +8,7 @@ import { } from 'modules/prebidServerBidAdapter/index.js'; import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import {deepAccess, deepClone} from 'src/utils.js'; +import {deepAccess, deepClone, mergeDeep} from 'src/utils.js'; import {ajax} from 'src/ajax.js'; import {config} from 'src/config.js'; import * as events from 'src/events.js'; @@ -25,13 +25,15 @@ import 'modules/priceFloors.js'; import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/fledgeForGpt.js'; import {hook} from '../../../src/hook.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; -import {registerBidder} from 'src/adapters/bidderFactory.js'; +import {addComponentAuction, registerBidder} from 'src/adapters/bidderFactory.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {syncAddFPDEnrichments, syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {deepSetValue} from '../../../src/utils.js'; let CONFIG = { accountId: '1', @@ -3296,6 +3298,70 @@ describe('S2S Adapter', function () { }); }); }); + describe('when the response contains ext.prebid.fledge', () => { + let fledgeStub, request, bidderRequests; + + function fledgeHook(next, ...args) { + fledgeStub(...args); + } + + before(() => { + addComponentAuction.before(fledgeHook); + }); + + after(() => { + addComponentAuction.getHooks({hook: fledgeHook}).remove(); + }) + + beforeEach(function () { + fledgeStub = sinon.stub(); + config.setConfig({CONFIG}); + request = deepClone(REQUEST); + request.ad_units.forEach(au => deepSetValue(au, 'ortb2Imp.ext.ae', 1)); + bidderRequests = deepClone(BID_REQUESTS); + bidderRequests.forEach(req => req.fledgeEnabled = true); + }); + + const AU = 'div-gpt-ad-1460505748561-0'; + const FLEDGE_RESP = { + ext: { + prebid: { + fledge: { + auctionconfigs: [ + { + impid: AU, + config: { + id: 1 + } + }, + { + impid: AU, + config: { + id: 2 + } + } + ] + } + } + } + } + + it('calls addComponentAuction alongside addBidResponse', function () { + adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(mergeDeep({}, RESPONSE_OPENRTB, FLEDGE_RESP))); + expect(addBidResponse.called).to.be.true; + sinon.assert.calledWith(fledgeStub, AU, {id: 1}); + sinon.assert.calledWith(fledgeStub, AU, {id: 2}); + }); + + it('calls addComponentAuction when there is no bid in the response', () => { + adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); + expect(addBidResponse.called).to.be.false; + sinon.assert.calledWith(fledgeStub, AU, {id: 1}); + sinon.assert.calledWith(fledgeStub, AU, {id: 2}); + }) + }); }); describe('bid won events', function () { diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 7d169fae3d3..58334595d71 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1966,64 +1966,6 @@ describe('adapterManager tests', function () { }); }); - describe('fledgeEnabled', function () { - const origRunAdAuction = navigator?.runAdAuction; - before(function () { - // navigator.runAdAuction doesn't exist, so we can't stub it normally with - // sinon.stub(navigator, 'runAdAuction') or something - navigator.runAdAuction = sinon.stub(); - }); - - after(function() { - navigator.runAdAuction = origRunAdAuction; - }) - - afterEach(function () { - config.resetConfig(); - }); - - it('should set fledgeEnabled correctly per bidder', function () { - config.setConfig({bidderSequence: 'fixed'}) - config.setBidderConfig({ - bidders: ['appnexus'], - config: { - fledgeEnabled: true, - } - }); - - const adUnits = [{ - 'code': '/19968336/header-bid-tag1', - 'mediaTypes': { - 'banner': { - 'sizes': [[728, 90]] - }, - }, - 'bids': [ - { - 'bidder': 'appnexus', - }, - { - 'bidder': 'rubicon', - }, - ] - }]; - - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[1].fledgeEnabled).to.be.undefined; - }); - }); - describe('sizeMapping', function () { let sandbox; beforeEach(function () { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 35c7cb0b971..c0e48089b52 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,4 +1,11 @@ -import {newBidder, registerBidder, preloadBidderMappingFile, storage, isValid} from 'src/adapters/bidderFactory.js'; +import { + newBidder, + registerBidder, + preloadBidderMappingFile, + storage, + isValid, + addComponentAuction +} from 'src/adapters/bidderFactory.js'; import adapterManager from 'src/adapterManager.js'; import * as ajax from 'src/ajax.js'; import { expect } from 'chai'; @@ -1212,16 +1219,27 @@ describe('validate bid response: ', function () { }; const fledgeAuctionConfig = { bidId: '1', + config: { + foo: 'bar' + } } describe('when response has FLEDGE auction config', function() { - let logInfoSpy; + let fledgeStub; - beforeEach(function () { - logInfoSpy = sinon.spy(utils, 'logInfo'); + function fledgeHook(next, ...args) { + fledgeStub(...args); + } + + before(() => { + addComponentAuction.before(fledgeHook); }); - afterEach(function () { - logInfoSpy.restore(); + after(() => { + addComponentAuction.getHooks({hook: fledgeHook}).remove(); + }) + + beforeEach(function () { + fledgeStub = sinon.stub(); }); it('should unwrap bids', function() { @@ -1243,8 +1261,8 @@ describe('validate bid response: ', function () { }); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(logInfoSpy.calledOnce).to.equal(true); - expect(logInfoSpy.firstCall.args[1]).to.equal(fledgeAuctionConfig); + expect(fledgeStub.calledOnce).to.equal(true); + sinon.assert.calledWith(fledgeStub, 'mock/placement', fledgeAuctionConfig.config); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); }) @@ -1257,8 +1275,8 @@ describe('validate bid response: ', function () { }); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(logInfoSpy.calledOnce).to.equal(true); - expect(logInfoSpy.firstCall.args[1]).to.equal(fledgeAuctionConfig); + expect(fledgeStub.calledOnce).to.be.true; + sinon.assert.calledWith(fledgeStub, 'mock/placement', fledgeAuctionConfig.config); expect(addBidResponseStub.calledOnce).to.equal(false); }) }) From 9c0becf2f2a73fbb4a6e9f062e087b383344a7a9 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Thu, 30 Mar 2023 13:12:17 -0600 Subject: [PATCH 254/375] remove timeout on synchronous test (#9738) --- test/spec/modules/realvuAnalyticsAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js index 221efc2d374..e51a4e2e3a2 100644 --- a/test/spec/modules/realvuAnalyticsAdapter_spec.js +++ b/test/spec/modules/realvuAnalyticsAdapter_spec.js @@ -50,7 +50,6 @@ describe('RealVu', function() { describe('Analytics Adapter.', function () { it('enableAnalytics', function () { - this.timeout(3500) const config = { options: { partnerId: '1Y', From 4087bb09f77b1511e000bc553b4165911e7e091d Mon Sep 17 00:00:00 2001 From: danielpfei <122355648+danielpfei@users.noreply.github.com> Date: Fri, 31 Mar 2023 14:03:42 +0200 Subject: [PATCH 255/375] AdsInteractive Bid Adapter: new bid adapter (#9586) * AdsInteractive adapter first commit * minor changes for the best bidding * add new test params * add meta obj, simplify condition * simplify condition * beutify conditions * add usersync, gdpr, fix interpretResp --------- Co-authored-by: pfeifer.daniel83 --- modules/adsinteractiveBidAdapter.js | 144 ++++++++++++ modules/adsinteractiveBidAdapter.md | 31 +++ .../modules/adsinteractiveBidAdapter_spec.js | 207 ++++++++++++++++++ 3 files changed, 382 insertions(+) create mode 100644 modules/adsinteractiveBidAdapter.js create mode 100644 modules/adsinteractiveBidAdapter.md create mode 100644 test/spec/modules/adsinteractiveBidAdapter_spec.js diff --git a/modules/adsinteractiveBidAdapter.js b/modules/adsinteractiveBidAdapter.js new file mode 100644 index 00000000000..304b8bcade0 --- /dev/null +++ b/modules/adsinteractiveBidAdapter.js @@ -0,0 +1,144 @@ +import { + deepAccess, +} from '../src/utils.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const ADSINTERACTIVE_CODE = 'adsinteractive'; +const USER_SYNC_URL_IMAGE = 'https://pb.adsinteractive.com/img'; +const USER_SYNC_URL_IFRAME = 'https://pb.adsinteractive.com/sync'; + +export const spec = { + code: ADSINTERACTIVE_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => { + return ( + !!bid.params.adUnit && !!bid.bidId && bid.bidder === 'adsinteractive' + ); + }, + + buildRequests: (bidRequests, bidderRequest) => { + return bidRequests.map((bid) => { + var gdprConsent; + if (bidderRequest && bidderRequest.gdprConsent) { + gdprConsent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies, + }; + + if ( + bidderRequest.gdprConsent.addtlConsent && + bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1 + ) { + let ac = bidderRequest.gdprConsent.addtlConsent; + let acStr = ac.substring(ac.indexOf('~') + 1); + gdprConsent.addtl_consent = acStr + .split('.') + .map((id) => parseInt(id, 10)); + } + } + + let url = 'https://pb.adsinteractive.com/prebid'; + const data = { + id: bid.bidId, + at: 1, + source: { fd: 0 }, + gdprConsent: gdprConsent, + site: { + page: bid.ortb2.site.page, + keywords: bid.ortb2.site.keywords, + domain: bid.ortb2.site.domain, + publisher: { + domain: bid.ortb2.site.domain, + }, + ext: { + amp: Number(bidderRequest.refererInfo.isAmp), + }, + }, + regs: bid.ortb2.regs, + device: bid.ortb2.device, + user: bid.ortb2.user, + imp: [ + { + id: bid.params.adUnit, + banner: { + format: bid.sizes.map((size) => ({ + w: size[0], + h: size[1], + })), + }, + ext: { + bidder: { + adUnit: bid.params.adUnit, + }, + }, + }, + ], + tmax: bidderRequest.timeout, + }; + const options = { + withCredentials: true, + }; + return { + method: 'POST', + url, + data, + options, + }; + }); + }, + + interpretResponse: (serverResponse, bidRequest) => { + let answer = []; + if (serverResponse && serverResponse.body && serverResponse.body.seatbid) { + serverResponse.body.seatbid.forEach((seatbid) => { + if (seatbid.bid.length) { + answer = [ + ...answer, + ...seatbid.bid + .filter((bid) => bid.price > 0) + .map((adsinteractiveBid) => { + const bid = { + id: adsinteractiveBid.id, + requestId: bidRequest.data.id, + cpm: adsinteractiveBid.price, + netRevenue: true, + ttl: 1000, + ad: adsinteractiveBid.adm, + meta: {advertiserDomains: adsinteractiveBid && adsinteractiveBid.adomain ? adsinteractiveBid.adomain : []}, + width: adsinteractiveBid.w, + height: adsinteractiveBid.h, + currency: serverResponse.body.cur || 'USD', + creativeId: adsinteractiveBid.crid || 0, + }; + return bid; + }), + ]; + } + }); + } + return answer; + }, + getUserSyncs: (syncOptions, serverResponse, gdprConsent, uspConsent) => { + if (syncOptions.iframeEnabled) { + const auid = serverResponse.filter(resp => deepAccess(resp, 'body.ext.auid')) + .map(resp => resp.body.ext.auid); + return [ + { + type: 'iframe', + url: USER_SYNC_URL_IFRAME + '?consent=' + gdprConsent.consentString + '&auid=' + auid, + }, + ]; + } else { + return [ + { + type: 'image', + url: USER_SYNC_URL_IMAGE, + }, + ]; + } + }, +}; +registerBidder(spec); diff --git a/modules/adsinteractiveBidAdapter.md b/modules/adsinteractiveBidAdapter.md new file mode 100644 index 00000000000..81afcd18200 --- /dev/null +++ b/modules/adsinteractiveBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +Module Name: AdsInteractive Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: it@adsinteractive.com + +# Description + +You can use this adapter to get a bid from adsinteractive.com. + +About us : https://www.adsinteractive.com + + +# Test Parameters +```javascript + var adUnits = [ + { + sizes: [[300, 250]], + bids: [ + { + bidder: "adsinteractive", + params: { + adUnit: "example_adunit_1" + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/adsinteractiveBidAdapter_spec.js b/test/spec/modules/adsinteractiveBidAdapter_spec.js new file mode 100644 index 00000000000..d0f90bd71de --- /dev/null +++ b/test/spec/modules/adsinteractiveBidAdapter_spec.js @@ -0,0 +1,207 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adsinteractiveBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('adsinteractiveBidAdapter', function () { + let bid = { + ortb2: { + site: { + page: 'http://test.com', + domain: 'test.com', + publisher: { + domain: 'test.com', + }, + }, + }, + bidder: 'adsinteractive', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: { + adUnit: 'example_adunit_1', + }, + } + + const bidderRequest = { + refererInfo: { + isAmp: 0 + } } + + describe('build requests', () => { + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests([ + { + ortb2: { + site: { + page: 'http://test.com', + domain: 'test.com', + publisher: { + domain: 'test.com', + }, + }, + }, + bidder: 'adsinteractive', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: { + adUnit: 'example_adunit_1', + }, + }, + ], bidderRequest); + expect(request[0].method).to.equal('POST'); + }); + it('sends bid request to adsinteractive endpoint', function () { + const request = spec.buildRequests([ + { + ortb2: { + site: { + page: 'http://test.com', + domain: 'test.com', + publisher: { + domain: 'test.com', + }, + }, + }, + bidder: 'adsinteractive', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: { + adUnit: 'example_adunit_1', + }, + }, + ], bidderRequest); + expect(request[0].url).to.equal('https://pb.adsinteractive.com/prebid'); + }); + }); + + describe('inherited functions', () => { + const adapter = newBidder(spec); + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when necessary information is found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return false when necessary information is not found', function () { + // empty bid + expect(spec.isBidRequestValid({ bidId: '', params: {} })).to.be.false; + + // empty bidId + bid.bidId = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + + // empty adUnit + bid.bidId = '32469kja92389'; + bid.params.adUnit = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('returns false when bidder not set to "adsinteractive"', function () { + const invalidBid = { + bidder: 'newyork', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: { + adUnit: 'example_adunit_1', + }, + }; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('returns false when adUnit is not set in params', function () { + const invalidBid = { + bidder: 'adsinteractive', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: {}, + }; + + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + describe('interpretResponse', function () { + let serverResponse; + let bidRequest = { data: { id: 'adsinteractiverequest-9320' } }; + + beforeEach(function () { + serverResponse = { + body: { + id: '239823rhaldf822', + seatbid: [ + { + bid: [ + { + id: 'fae50ca1-3f69-4b34-bf6d-b2eb0ae3376b', + impid: 'example_adunit_1', + price: 0.49, + netRevenue: true, + ttl: 1000, + meta: {advertiserDomains: []}, + adm: '', + crid: '932048jda99cr', + h: 250, + w: 300, + }, + ], + seat: 'adsinteractive', + }, + ], + cur: 'USD', + }, + }; + }); + + it('validate_response_params', function () { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse[0].id).to.be.equal( + 'fae50ca1-3f69-4b34-bf6d-b2eb0ae3376b' + ); + expect(newResponse[0].requestId).to.be.equal( + 'adsinteractiverequest-9320' + ); + expect(newResponse[0].cpm).to.be.equal(0.49); + expect(newResponse[0].width).to.be.equal(300); + expect(newResponse[0].height).to.be.equal(250); + expect(newResponse[0].currency).to.be.equal('USD'); + expect(newResponse[0].ad).to.be.equal( + '' + ); + }); + + it('should correctly reorder the server response', function () { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + id: 'fae50ca1-3f69-4b34-bf6d-b2eb0ae3376b', + requestId: 'adsinteractiverequest-9320', + cpm: 0.49, + netRevenue: true, + ttl: 1000, + width: 300, + height: 250, + meta: {advertiserDomains: []}, + creativeId: '932048jda99cr', + currency: 'USD', + ad: '', + }); + }); + + it('should not add responses if the cpm is 0 or null', function () { + serverResponse.body.seatbid[0].bid[0].price = 0; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]); + + serverResponse.body.seatbid[0].bid[0].price = null; + response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]); + }); + it('should add responses if the cpm is valid', function () { + serverResponse.body.seatbid[0].bid[0].price = 0.5; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.not.deep.equal([]); + }); + }); +}); From c283ef875123558cc0cb4eccbfc4741201919cbc Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 31 Mar 2023 08:06:45 -0700 Subject: [PATCH 256/375] Build system: fix build error on Windows (#9743) --- plugins/pbjsGlobals.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/pbjsGlobals.js b/plugins/pbjsGlobals.js index 4c8685db840..6d1eeb0c57d 100644 --- a/plugins/pbjsGlobals.js +++ b/plugins/pbjsGlobals.js @@ -42,9 +42,11 @@ module.exports = function(api, options) { ]; const PREBID_ROOT = path.resolve(__dirname, '..'); + // on Windows, require paths are not filesystem paths + const SEP_PAT = new RegExp(path.sep.replace(/\\/g, '\\\\'), 'g') function relPath(from, toRelToProjectRoot) { - return path.relative(path.dirname(from), path.join(PREBID_ROOT, toRelToProjectRoot)); + return path.relative(path.dirname(from), path.join(PREBID_ROOT, toRelToProjectRoot)).replace(SEP_PAT, '/'); } function getModuleName(filename) { From 43089c70a8209d9dccd1c418f510709e6de0c933 Mon Sep 17 00:00:00 2001 From: onlsol <48312668+onlsol@users.noreply.github.com> Date: Sat, 1 Apr 2023 01:26:14 +0200 Subject: [PATCH 257/375] DSPx bid adapter: bidder enhancement (#9674) * Dspx: improve floorprice support, dynamic mediatype handling and shared id support; add video context/renderer support and schain * kick of tests * Dspx: improve floorprice support, dynamic mediatype handling and shared id support; add video context/renderer support and schain --------- Co-authored-by: Alexander Co-authored-by: Chris Huie --- modules/dspxBidAdapter.js | 238 +++++++++++++++++++++-- test/spec/modules/dspxBidAdapter_spec.js | 197 +++++++++++++++++-- 2 files changed, 407 insertions(+), 28 deletions(-) diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index 8850eb282b5..7606bc86d73 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -1,12 +1,17 @@ -import { deepAccess } from '../src/utils.js'; +import {deepAccess, getBidIdParameter, logError, logMessage, logWarn, isFn} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {includes} from '../src/polyfill.js'; const BIDDER_CODE = 'dspx'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; const GVLID = 602; +const VIDEO_ORTB_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', + 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', + 'api', 'companiontype', 'ext']; export const spec = { code: BIDDER_CODE, @@ -17,24 +22,34 @@ export const spec = { return !!(bid.params.placement); }, buildRequests: function(validBidRequests, bidderRequest) { + let payload = {}; return validBidRequests.map(bidRequest => { const params = bidRequest.params; - const placementId = params.placement; const rnd = Math.floor(Math.random() * 99999999999); const referrer = bidderRequest.refererInfo.page; const bidId = bidRequest.bidId; - const isDev = params.devMode || false; const pbcode = bidRequest.adUnitCode || false; // div id const auctionId = bidRequest.auctionId || false; + const isDev = params.devMode || false; let endpoint = isDev ? ENDPOINT_URL_DEV : ENDPOINT_URL; + let placementId = params.placement; + + // dev config + if (isDev && params.dev) { + endpoint = params.dev.endpoint || endpoint; + placementId = params.dev.placement || placementId; + if (params.dev.pfilter !== undefined) { + params.pfilter = params.dev.pfilter; + } + } let mediaTypesInfo = getMediaTypesInfo(bidRequest); let type = isBannerRequest(bidRequest) ? BANNER : VIDEO; let sizes = mediaTypesInfo[type]; - let payload = { + payload = { _f: 'auto', alternative: 'prebid_js', inventory_item_id: placementId, @@ -47,10 +62,6 @@ export const spec = { pbver: '$prebid.version$' }; - if (mediaTypesInfo[VIDEO] !== undefined && params.vastFormat !== undefined) { - payload.vf = params.vastFormat; - } - if (params.pfilter !== undefined) { payload.pfilter = params.pfilter; } @@ -79,11 +90,49 @@ export const spec = { payload.prebidDevMode = 1; } - if (bidRequest.userId && bidRequest.userId.netId) { - payload.did_netid = bidRequest.userId.netId; + // fill userId params + if (bidRequest.userId) { + if (bidRequest.userId.netId) { + payload.did_netid = bidRequest.userId.netId; + } + if (bidRequest.userId.id5id) { + payload.did_id5 = bidRequest.userId.id5id.uid || '0'; + if (bidRequest.userId.id5id.ext.linkType !== undefined) { + payload.did_id5_linktype = bidRequest.userId.id5id.ext.linkType; + } + } + let uId2 = deepAccess(bidRequest, 'userId.uid2.id'); + if (uId2) { + payload.did_uid2 = uId2; + } + let sharedId = deepAccess(bidRequest, 'userId.sharedid.id'); + if (sharedId) { + payload.did_sharedid = sharedId; + } + let pubcId = deepAccess(bidRequest, 'userId.pubcid'); + if (pubcId) { + payload.did_pubcid = pubcId; + } + let crumbsPubcid = deepAccess(bidRequest, 'crumbs.pubcid'); + if (crumbsPubcid) { + payload.did_cpubcid = crumbsPubcid; + } + } + + if (bidRequest.schain) { + payload.schain = bidRequest.schain; } - if (bidRequest.userId && bidRequest.userId.uid2) { - payload.did_uid2 = bidRequest.userId.uid2; + + if (payload.pfilter === undefined || !payload.pfilter.floorprice) { + let bidFloor = getBidFloor(bidRequest); + if (bidFloor > 0) { + if (payload.pfilter !== undefined) { + payload.pfilter.floorprice = bidFloor; + } else { + payload.pfilter = { 'floorprice': bidFloor }; + } + // payload.bidFloor = bidFloor; + } } if (auctionId) { @@ -94,6 +143,17 @@ export const spec = { } payload.media_types = convertMediaInfoForRequest(mediaTypesInfo); + if (mediaTypesInfo[VIDEO] !== undefined) { + payload.vctx = getVideoContext(bidRequest); + if (params.vastFormat !== undefined) { + payload.vf = params.vastFormat; + } + payload.vpl = {}; + let videoParams = deepAccess(bidRequest, 'mediaTypes.video'); + Object.keys(videoParams) + .filter(key => includes(VIDEO_ORTB_PARAMS, key)) + .forEach(key => payload.vpl[key] = videoParams[key]); + } return { method: 'GET', @@ -103,6 +163,8 @@ export const spec = { }); }, interpretResponse: function(serverResponse, bidRequest) { + logMessage('DSPx: serverResponse', serverResponse); + logMessage('DSPx: bidRequest', bidRequest); const bidResponses = []; const response = serverResponse.body; const crid = response.crid || 0; @@ -126,13 +188,33 @@ export const spec = { advertiserDomains: response.adomain || [] } }; + + if (response.vastUrl) { + bidResponse.vastUrl = response.vastUrl; + bidResponse.mediaType = 'video'; + } if (response.vastXml) { bidResponse.vastXml = response.vastXml; bidResponse.mediaType = 'video'; - } else { + } + if (response.renderer) { + bidResponse.renderer = newRenderer(bidRequest, response); + } + + if (response.videoCacheKey) { + bidResponse.videoCacheKey = response.videoCacheKey; + } + + if (response.adTag) { bidResponse.ad = response.adTag; } + if (response.bid_appendix) { + Object.keys(response.bid_appendix).forEach(fieldName => { + bidResponse[fieldName] = response.bid_appendix[fieldName]; + }); + } + bidResponses.push(bidResponse); } return bidResponses; @@ -217,12 +299,22 @@ function isVideoRequest(bid) { * Get video sizes * * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} True if it's a video bid + * @returns {object} */ function getVideoSizes(bid) { return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); } +/** + * Get video context + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} + */ +function getVideoContext(bid) { + return deepAccess(bid, 'mediaTypes.video.context') || 'unknown'; +} + /** * Get banner sizes * @@ -296,4 +388,120 @@ function getMediaTypesInfo(bid) { return mediaTypesInfo; } +/** + * Get Bid Floor + * @param bid + * @returns {number|*} + */ +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +/** + * Create a new renderer + * + * @param bidRequest + * @param response + * @returns {Renderer} + */ +function newRenderer(bidRequest, response) { + logMessage('DSPx: newRenderer', bidRequest, response); + const renderer = Renderer.install({ + id: response.renderer.id || response.bid_id, + url: (bidRequest.params && bidRequest.params.rendererUrl) || response.renderer.url, + config: response.renderer.options || deepAccess(bidRequest, 'renderer.options'), + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +/** + * Outstream Render Function + * + * @param bid + */ +function outstreamRender(bid) { + logMessage('DSPx: outstreamRender bid:', bid); + const embedCode = createOutstreamEmbedCode(bid); + try { + const inIframe = getBidIdParameter('iframe', bid.renderer.config); + if (inIframe && window.document.getElementById(inIframe).nodeName === 'IFRAME') { + const iframe = window.document.getElementById(inIframe); + let framedoc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); + framedoc.body.appendChild(embedCode); + if (typeof window.dspxRender === 'function') { + window.dspxRender(bid); + } else { + logError('[dspx][renderer] Error: dspxRender function is not found'); + } + return; + } + + const slot = getBidIdParameter('slot', bid.renderer.config) || bid.adUnitCode; + if (slot && window.document.getElementById(slot)) { + window.document.getElementById(slot).appendChild(embedCode); + if (typeof window.dspxRender === 'function') { + window.dspxRender(bid); + } else { + logError('[dspx][renderer] Error: dspxRender function is not found'); + } + } else if (slot) { + logError('[dspx][renderer] Error: slot not found'); + } + } catch (err) { + logError('[dspx][renderer] Error:' + err.message) + } +} + +/** + * create Outstream Embed Code Node + * + * @param bid + * @returns {DocumentFragment} + */ +function createOutstreamEmbedCode(bid) { + const fragment = window.document.createDocumentFragment(); + let div = window.document.createElement('div'); + div.innerHTML = deepAccess(bid, 'renderer.config.code', ""); + fragment.appendChild(div); + + // run scripts + var scripts = div.getElementsByTagName('script'); + var scriptsClone = []; + for (var idx = 0; idx < scripts.length; idx++) { + scriptsClone.push(scripts[idx]); + } + for (var i = 0; i < scriptsClone.length; i++) { + var currentScript = scriptsClone[i]; + var s = document.createElement('script'); + for (var j = 0; j < currentScript.attributes.length; j++) { + var a = currentScript.attributes[j]; + s.setAttribute(a.name, a.value); + } + s.appendChild(document.createTextNode(currentScript.innerHTML)); + currentScript.parentNode.replaceChild(s, currentScript); + } + + return fragment; +} + registerBidder(spec); diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index 2869385d7e7..841fc087613 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/dspxBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { deepClone } from '../../../src/utils'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; @@ -65,7 +66,34 @@ describe('dspxAdapter', function () { 'adUnitCode': 'testDiv1', 'userId': { 'netId': '123', - 'uid2': '456' + 'uid2': {'id': '456'}, + 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61', + 'id5id': { + 'uid': 'ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x', + 'ext': { + 'linkType': 2 + } + }, + 'sharedid': { + 'id': '01EXPPGZ9C8NKG1MTXVHV98505', + 'third': '01EXPPGZ9C8NKG1MTXVHV98505' + } + }, + 'crumbs': { + 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61' + }, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'example.com', + 'sid': '0', + 'hp': 1, + 'rid': 'bidrequestid', + 'domain': 'example.com' + } + ] } }, { @@ -111,7 +139,10 @@ describe('dspxAdapter', function () { 'mediaTypes': { 'video': { 'playerSize': [640, 480], - 'context': 'instream' + 'context': 'instream', + 'protocols': [1, 2], + 'playbackmethod': [2], + 'skip': 1 }, 'banner': { 'sizes': [ @@ -135,14 +166,53 @@ describe('dspxAdapter', function () { 'mediaTypes': { 'video': { 'playerSize': [640, 480], - 'context': 'instream' + 'context': 'instream', + 'protocols': [1, 2], + 'playbackmethod': [2], + 'skip': 1, + 'renderer': { + url: 'example.com/videoRenderer.js', + render: function (bid) { alert('test'); } + } } }, 'bidId': '30b31c1838de1e41', 'bidderRequestId': '22edbae2733bf67', 'auctionId': '1d1a030790a478', 'adUnitCode': 'testDiv4' - } + }, + { + 'bidder': 'dspx', + 'params': { + 'placement': '101', + 'devMode': true, + 'dev': { + 'endpoint': 'http://localhost', + 'placement': '107', + 'pfilter': {'test': 1} + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + 'mimes': ['video/mp4'], + 'protocols': [1, 2], + 'playbackmethod': [2], + 'skip': 1 + }, + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + + 'bidId': '30b31c1838de1e4', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv3' + }, ]; @@ -163,7 +233,7 @@ describe('dspxAdapter', 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=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&did_netid=123&did_uid2=456&auctionId=1d1a030790a475&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&did_netid=123&did_id5=ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x&did_id5_linktype=2&did_uid2=456&did_sharedid=01EXPPGZ9C8NKG1MTXVHV98505&did_pubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&did_cpubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&schain%5Bver%5D=1.0&schain%5Bcomplete%5D=1&schain%5Bnodes%5D%5B0%5D%5Basi%5D=example.com&schain%5Bnodes%5D%5B0%5D%5Bsid%5D=0&schain%5Bnodes%5D%5B0%5D%5Bhp%5D=1&schain%5Bnodes%5D%5B0%5D%5Brid%5D=bidrequestid&schain%5Bnodes%5D%5B0%5D%5Bdomain%5D=example.com&auctionId=1d1a030790a475&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); }); var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; @@ -193,14 +263,61 @@ describe('dspxAdapter', function () { expect(request4.method).to.equal('GET'); expect(request4.url).to.equal(ENDPOINT_URL_DEV); let data = request4.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); - expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250&vctx=instream&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); }); var request5 = spec.buildRequests([bidRequests[4]], bidderRequestWithoutGdpr)[0]; it('sends bid video request to our endpoint via GET', function () { expect(request5.method).to.equal('GET'); let data = request5.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); - expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&vf=vast4&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv4&media_types%5Bvideo%5D=640x480'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv4&media_types%5Bvideo%5D=640x480&vctx=instream&vf=vast4&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); + }); + + var request6 = spec.buildRequests([bidRequests[5]], bidderRequestWithoutGdpr)[0]; + it('sends bid request without gdprConsent to our DEV endpoint with overriden DEV params via GET', function () { + expect(request6.method).to.equal('GET'); + expect(request6.url).to.equal('http://localhost'); + let data = request6.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=107&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&pfilter%5Btest%5D=1&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250&vctx=instream&vpl%5Bmimes%5D%5B0%5D=video%2Fmp4&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); + }); + + // bidfloor tests + const getFloorResponse = {currency: 'EUR', floor: 5}; + let testBidRequest = deepClone(bidRequests[1]); + let floorRequest = spec.buildRequests([testBidRequest], bidderRequestWithoutGdpr)[0]; + + // 1. getBidFloor not exist AND bidfloor not exist - no floorprice in request + it('bidfloor is not exists in request', function () { + expect(floorRequest.data).to.not.contain('floorprice'); + }); + + // 2. getBidFloor not exist AND pfilter.floorprice exist - use pfilter.floorprice property + it('bidfloor is equal 0.5', function () { + testBidRequest = deepClone(bidRequests[0]); + testBidRequest.params.pfilter = { + 'floorprice': 0.5 + }; + floorRequest = spec.buildRequests([testBidRequest], bidderRequestWithoutGdpr)[0]; + expect(floorRequest.data).to.contain('floorprice%5D=0.5'); + }); + + // 3. getBidFloor exist AND pfilter.floorprice not exist - use getFloor method + it('bidfloor is equal 5', function () { + testBidRequest = deepClone(bidRequests[1]); + testBidRequest.getFloor = () => getFloorResponse; + floorRequest = spec.buildRequests([testBidRequest], bidderRequestWithoutGdpr)[0]; + expect(floorRequest.data).to.contain('floorprice%5D=5'); + }); + + // 4. getBidFloor exist AND pfilter.floorprice exist -> use getFloor method + it('bidfloor is equal 0.35', function () { + testBidRequest = deepClone(bidRequests[0]); + testBidRequest.getFloor = () => getFloorResponse; + testBidRequest.params.pfilter = { + 'floorprice': 0.35 + }; + floorRequest = spec.buildRequests([testBidRequest], bidderRequestWithoutGdpr)[0]; + expect(floorRequest.data).to.contain('floorprice%5D=0.35'); }); }); @@ -212,7 +329,7 @@ describe('dspxAdapter', function () { 'width': '300', 'height': '250', 'type': 'sspHTML', - 'tag': '', + 'adTag': '', 'requestId': '220ed41385952a', 'currency': 'EUR', 'ttl': 60, @@ -233,7 +350,25 @@ describe('dspxAdapter', function () { 'currency': 'EUR', 'ttl': 60, 'netRevenue': true, - 'zone': '6682' + 'zone': '6682', + 'renderer': {id: 1, url: '//player.example.com', options: {}} + } + }; + let serverVideoResponseVastUrl = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'requestId': '220ed41385952a', + 'type': 'vast2', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682', + 'vastUrl': 'https://local/vasturl1', + 'videoCacheKey': 'cache_123', + 'bid_appendix': {'someField': 'someValue'} } }; @@ -246,10 +381,10 @@ describe('dspxAdapter', function () { dealId: '', currency: 'EUR', netRevenue: true, - ttl: 300, + ttl: 60, type: 'sspHTML', ad: '', - meta: {advertiserDomains: ['bdomain']} + meta: {advertiserDomains: ['bdomain']}, }, { requestId: '23beaa6af6cdde', cpm: 0.5, @@ -263,7 +398,24 @@ describe('dspxAdapter', function () { type: 'vast2', vastXml: '{"reason":7001,"status":"accepted"}', mediaType: 'video', - meta: {advertiserDomains: []} + meta: {advertiserDomains: []}, + renderer: {} + }, { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 60, + type: 'vast2', + vastUrl: 'https://local/vasturl1', + videoCacheKey: 'cache_123', + mediaType: 'video', + meta: {advertiserDomains: []}, + someField: 'someValue' }]; it('should get the correct bid response by display ad', function () { @@ -287,7 +439,7 @@ describe('dspxAdapter', function () { 'mediaTypes': { 'video': { 'playerSize': [640, 480], - 'context': 'instream' + 'context': 'outstream' } }, 'data': { @@ -299,6 +451,25 @@ describe('dspxAdapter', function () { expect(result[0].meta.advertiserDomains.length).to.equal(0); }); + it('should get the correct dspx video bid response by display ad (vastUrl)', function () { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverVideoResponseVastUrl, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[2])); + expect(result[0].meta.advertiserDomains.length).to.equal(0); + }); + it('handles empty bid response', function () { let response = { body: {} From 8270c7d3aeea43cf59f57a31621068c06f94337b Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 31 Mar 2023 16:30:01 -0700 Subject: [PATCH 258/375] Core: do not allow bidders to run syncs more than once (#9695) --- src/adapters/bidderFactory.js | 1 + src/userSync.js | 11 +++++++---- test/spec/userSync_spec.js | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index d6a43e409e3..9357041c505 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -477,6 +477,7 @@ export const registerSyncInner = hook('async', function(spec, responses, gdprCon syncs.forEach((sync) => { userSync.registerSync(sync.type, spec.code, sync.url) }); + userSync.bidderDone(spec.code); } } }, 'registerSyncs') diff --git a/src/userSync.js b/src/userSync.js index dec8f650930..ed3cbb5d5f6 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -109,10 +109,7 @@ export function newUserSync(userSyncDependencies) { // Randomize the order of the pixels before firing // This is to avoid giving any bidder who has registered multiple syncs // any preferential treatment and balancing them out - shuffle(queue).forEach((sync) => { - fn(sync); - hasFiredBidder.add(sync[0]); - }); + shuffle(queue).forEach(fn); } /** @@ -212,6 +209,12 @@ export function newUserSync(userSyncDependencies) { numAdapterBids = incrementAdapterBids(numAdapterBids, bidder); }; + /** + * Mark a bidder as done with its user syncs - no more will be accepted from them in this session. + * @param {string} bidderCode + */ + publicApi.bidderDone = hasFiredBidder.add.bind(hasFiredBidder); + /** * @function shouldBidderBeBlocked * @summary Check filterSettings logic to determine if the bidder should be prevented from registering their userSync tracker diff --git a/test/spec/userSync_spec.js b/test/spec/userSync_spec.js index 910ffe7b2d6..6d0953f68ac 100644 --- a/test/spec/userSync_spec.js +++ b/test/spec/userSync_spec.js @@ -110,9 +110,10 @@ describe('user sync', function () { expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe'); }); - it('should only trigger syncs once per page per bidder', function () { + it('should stop triggering user syncs after bidderDone', function () { const userSync = newTestUserSync({ pixelEnabled: true }); userSync.registerSync('image', 'testBidder', 'http://example.com/1'); + userSync.bidderDone('testBidder'); userSync.syncUsers(); userSync.registerSync('image', 'testBidder', 'http://example.com/2'); userSync.registerSync('image', 'testBidder2', 'http://example.com/3'); From 0a665d712b7c0560ae55651443da290d3554916d Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Sun, 2 Apr 2023 22:33:23 +0200 Subject: [PATCH 259/375] TheMediaGrid: update gpid logic (#9748) --- modules/gridBidAdapter.js | 18 ++++++++++++------ test/spec/modules/gridBidAdapter_spec.js | 14 +++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 31bb350aaa6..7da2c778c43 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -145,12 +145,18 @@ export const spec = { if (ortb2Imp.instl) { impObj.instl = ortb2Imp.instl; } - if (ortb2Imp.ext && ortb2Imp.ext.data) { - impObj.ext.data = ortb2Imp.ext.data; - if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { - impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); - } else { - impObj.ext.gpid = ortb2Imp.ext.data.pbadslot && ortb2Imp.ext.data.pbadslot.toString(); + + if (ortb2Imp.ext) { + if (ortb2Imp.ext.data) { + impObj.ext.data = ortb2Imp.ext.data; + if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { + impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); + } else if (ortb2Imp.ext.data.pbadslot) { + impObj.ext.gpid = ortb2Imp.ext.data.pbadslot.toString(); + } + } + if (ortb2Imp.ext.gpid) { + impObj.ext.gpid = ortb2Imp.ext.gpid.toString(); } } } diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 7ff8094a80c..1cafdf6134f 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -873,14 +873,17 @@ describe('TheMediaGrid Adapter', function () { } }, { ext: { + gpid: '/222222/slot', data: { adserver: { name: 'ad_server_name', - adslot: '/222222/slot' - }, - pbadslot: '/222222/slot' + } } } + }, { + ext: { + gpid: '/333333/slot' + } }]; const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); @@ -896,10 +899,11 @@ describe('TheMediaGrid Adapter', function () { expect(payload.imp[1].ext).to.deep.equal({ divid: bidRequests[1].adUnitCode, data: ortb2Imp[1].ext.data, - gpid: ortb2Imp[1].ext.data.adserver.adslot + gpid: ortb2Imp[1].ext.gpid }); expect(payload.imp[2].ext).to.deep.equal({ - divid: bidRequests[2].adUnitCode + divid: bidRequests[2].adUnitCode, + gpid: ortb2Imp[2].ext.gpid }); }); From 68796582e86db7cf8aa28398c7b20609d8ce52f4 Mon Sep 17 00:00:00 2001 From: Philip Watson Date: Tue, 4 Apr 2023 01:00:32 +1200 Subject: [PATCH 260/375] StroeerCore Bid Adapter: handle gdpr applies correctly (#9740) --- modules/stroeerCoreBidAdapter.js | 5 +-- .../modules/stroeerCoreBidAdapter_spec.js | 31 ++++++++----------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index 722cffab16d..e579b1f94de 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -68,9 +68,10 @@ export const spec = { const gdprConsent = bidderRequest.gdprConsent; - if (gdprConsent && gdprConsent.consentString != null && gdprConsent.gdprApplies != null) { + if (gdprConsent) { basePayload.gdpr = { - consent: bidderRequest.gdprConsent.consentString, applies: bidderRequest.gdprConsent.gdprApplies + consent: gdprConsent.consentString, + applies: gdprConsent.gdprApplies }; } diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index 6489d8ece7e..c2806317ee6 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -636,10 +636,13 @@ describe('stroeerCore bid adapter', function () { } }); - const gdprSamples = [{consentString: 'RG9ua2V5IEtvbmc=', gdprApplies: true}, { - consentString: 'UGluZyBQb25n', - gdprApplies: false - }]; + const gdprSamples = [ + {consentString: 'RG9ua2V5IEtvbmc=', gdprApplies: true}, + {consentString: 'UGluZyBQb25n', gdprApplies: false}, + {consentString: undefined, gdprApplies: true}, + {consentString: undefined, gdprApplies: false}, + {consentString: undefined, gdprApplies: undefined}, + ]; gdprSamples.forEach((sample) => { it(`should add GDPR info ${JSON.stringify(sample)} when provided`, () => { const bidReq = buildBidderRequest(); @@ -653,22 +656,14 @@ describe('stroeerCore bid adapter', function () { }); }); - const skippableGdprSamples = [{consentString: null, gdprApplies: true}, // - {consentString: 'UGluZyBQb25n', gdprApplies: null}, // - {consentString: null, gdprApplies: null}, // - {consentString: undefined, gdprApplies: true}, // - {consentString: 'UGluZyBQb25n', gdprApplies: undefined}, // - {consentString: undefined, gdprApplies: undefined}]; - skippableGdprSamples.forEach((sample) => { - it(`should not add GDPR info ${JSON.stringify(sample)} when one or more values are missing`, () => { - const bidReq = buildBidderRequest(); - bidReq.gdprConsent = sample; + it(`should not add GDPR info when not provided`, () => { + const bidReq = buildBidderRequest(); - const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + delete bidReq.gdprConsent; - const actualGdpr = serverRequestInfo.data.gdpr; - assert.isUndefined(actualGdpr); - }); + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + assert.notProperty(serverRequestInfo.data, 'gdpr'); }); it('should be able to build without third party user id data', () => { From a239e68848d92714eea10a937385132b00ed68f7 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Mon, 3 Apr 2023 08:56:03 -0600 Subject: [PATCH 261/375] Dspx Bid Adapter : fix Linting Error (#9750) --- modules/dspxBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index 7606bc86d73..3bfb660cd7e 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -481,7 +481,7 @@ function outstreamRender(bid) { function createOutstreamEmbedCode(bid) { const fragment = window.document.createDocumentFragment(); let div = window.document.createElement('div'); - div.innerHTML = deepAccess(bid, 'renderer.config.code', ""); + div.innerHTML = deepAccess(bid, 'renderer.config.code', ''); fragment.appendChild(div); // run scripts From aff38f76214316382f5be0fdbc73cf5386b20517 Mon Sep 17 00:00:00 2001 From: beglobal2022 <120370005+beglobal2022@users.noreply.github.com> Date: Mon, 3 Apr 2023 21:16:09 +0530 Subject: [PATCH 262/375] BEdigitech Bid Adapter: New Bid Adapter (#9603) * Added bedigital adatper * Added test cases for bedigital adapter * changed email id * Added params in payload as suggestio in review --------- Co-authored-by: yogesh.ingale1 --- modules/bedigitechBidAdapter.js | 67 ++++++++ modules/bedigitechBidAdapter.md | 32 ++++ .../spec/modules/bedigitechBidAdapter_spec.js | 145 ++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 modules/bedigitechBidAdapter.js create mode 100644 modules/bedigitechBidAdapter.md create mode 100644 test/spec/modules/bedigitechBidAdapter_spec.js diff --git a/modules/bedigitechBidAdapter.js b/modules/bedigitechBidAdapter.js new file mode 100644 index 00000000000..6bfe95e85d0 --- /dev/null +++ b/modules/bedigitechBidAdapter.js @@ -0,0 +1,67 @@ +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { _each, isArray } from '../src/utils.js'; + +const BEDIGITECH_CODE = 'bedigitech'; +const BEDIGITECH_ENDPOINT = 'https://bedigitalhb.s3.amazonaws.com/hb.js'; +const BEDIGITECH_REQUEST_METHOD = 'GET'; +const BEDIGITECH_CURRENCY = 'USD'; + +function interpretResponse(placementResponse, bidRequest, bids) { + const bid = { + id: placementResponse.id, + requestId: placementResponse.requestId, + cpm: placementResponse.cpm, + ad: decodeURIComponent(placementResponse.ad), + width: placementResponse.width || 0, + height: placementResponse.height || 0, + currency: placementResponse.currency || BEDIGITECH_CURRENCY, + ttl: placementResponse.ttl || 300, + creativeId: placementResponse.creativeId || 0, + meta: { + mediaType: BANNER, + }, + }; + bids.push(bid); +} + +export const spec = { + code: BEDIGITECH_CODE, + supportedMediaTypes: [BANNER, NATIVE], + isBidRequestValid: bid => { + return !!bid.params.placementId && !!bid.bidId && bid.bidder === 'bedigitech' + }, + + buildRequests: (bidRequests, bidderRequest) => { + return bidRequests.map(bid => { + let url = BEDIGITECH_ENDPOINT; + const data = {'pid': bid.params.placementId}; + + return { + method: BEDIGITECH_REQUEST_METHOD, + url, + data, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + }, + }; + }); + }, + + interpretResponse: function(serverResponse, bidRequest) { + let bids = []; + + if (isArray(serverResponse.body)) { + _each(serverResponse.body, function(placementResponse) { + interpretResponse(placementResponse, bidRequest, bids); + }); + } + + return bids; + }, + +}; + +registerBidder(spec); diff --git a/modules/bedigitechBidAdapter.md b/modules/bedigitechBidAdapter.md new file mode 100644 index 00000000000..35cc0751187 --- /dev/null +++ b/modules/bedigitechBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: Bedigitech Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: yogesh@thebeglobal.com + +# Description + +You can use this adapter to get a bid from bedigitech.com. + +About us : https://www.bedigitech.com/ + + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'div-bedigitech-example', + sizes: [[300, 250]], + bids: [ + { + bidder: "bedigitech", + params: { + placementId: "1234" + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/bedigitechBidAdapter_spec.js b/test/spec/modules/bedigitechBidAdapter_spec.js new file mode 100644 index 00000000000..4bd335b7bbd --- /dev/null +++ b/test/spec/modules/bedigitechBidAdapter_spec.js @@ -0,0 +1,145 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bedigitechBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import {BANNER} from 'src/mediaTypes.js'; + +describe('BedigitechAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + bidder: 'bedigitech', + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: 1234, + }, + }; + + 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 () { + const bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'masterId': 0 + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + bidder: 'bedigitech', + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: 1234, + }, + }]; + + it('sends bid request to url via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://bedigitalhb.s3.amazonaws.com/hb.js'); + }); + + it('should attach pid to url', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.pid).to.equal(1234); + }); + }); + + describe('interpretResponse', function () { + const response = { + 'body': [ + { + 'id': 'bedigitechMyidfdfdf', + 'requestId': 'gshgfshdfgdfsd', + 'cpm': '5.0', + 'ad': '%3C!--%20Creative%20--%3E', + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'ttl': 300, + 'creativeId': '0af345b42983cc4bc0' + } + ], + 'headers': { + 'get': function() {} + } + }; + + const bidRequest = { + bidder: 'bedigitech', + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: 1234, + }, + }; + + it('should get correct bid response', function () { + const expectedResponse = [ + { + 'id': 'bedigitechMyidfdfdf', + 'requestId': 'gshgfshdfgdfsd', + 'cpm': '5.0', + 'ad': '%3C!--%20Creative%20--%3E', + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'ttl': 300, + 'creativeId': '0af345b42983cc4bc0', + 'meta': { + 'mediaType': BANNER, + }, + } + ]; + + const result = spec.interpretResponse(response, bidRequest); + expect(result).to.have.lengthOf(1); + let resultKeys = Object.keys(result[0]); + expect(resultKeys.sort()).to.deep.equal(Object.keys(expectedResponse[0]).sort()); + resultKeys.forEach(function(k) { + if (k === 'ad') { + expect(result[0][k]).to.match(/$/); + } else if (k === 'meta') { + expect(result[0][k]).to.deep.equal(expectedResponse[0][k]); + } else { + expect(result[0][k]).to.equal(expectedResponse[0][k]); + } + }); + }); + }); +}); From c16c3897c79fa3fde23bdc32d799c3cabad75273 Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Mon, 3 Apr 2023 12:42:24 -0400 Subject: [PATCH 263/375] bug in AMX bid adapter: assumed filterSettings is not null (#9737) restore package-lock.json --- modules/amxBidAdapter.js | 2 ++ test/spec/modules/amxBidAdapter_spec.js | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 2a3716589b8..65c935db5f5 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -194,6 +194,8 @@ function resolveSize(bid, request, bidId) { } function isSyncEnabled(syncConfigP, syncType) { + if (syncConfigP == null) return false; + const syncConfig = syncConfigP[syncType]; if (syncConfig == null) { return false; diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index da31b221096..6f69e57d8bc 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -362,7 +362,10 @@ describe('AmxBidAdapter', () => { const base = { d: 2300, l: 2, e: true }; - const tests = [[{ + const tests = [[ + undefined, + { ...base, t: 0 } + ], [{ image: { bidders: '*', filter: 'include' From cc99b2144ab9e67bf84c898d17f0f28590288e96 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 3 Apr 2023 16:34:00 -0400 Subject: [PATCH 264/375] Update secureCreatives.js (#9739) --- src/secureCreatives.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 82d331988fc..c719bc191f2 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -73,7 +73,7 @@ function handleRenderRequest(reply, data, adObject) { if (adObject == null) { emitAdRenderFail({ reason: constants.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, - message: `Cannot find ad '${data.adId}' for cross-origin render request`, + message: `Cannot find ad for cross-origin render request: '${data.adId}'`, id: data.adId }); return; @@ -111,7 +111,7 @@ function handleNativeRequest(reply, data, adObject) { // adId: '%%PATTERN:hb_adid%%' // }), '*'); if (adObject == null) { - logError(`Cannot find ad '${data.adId}' for x-origin event request`); + logError(`Cannot find ad for x-origin event request: '${data.adId}'`); return; } From 1f028f3fa269992337a8fa9b4fc748d6bbee3520 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 4 Apr 2023 05:36:23 -0700 Subject: [PATCH 265/375] SharedID: wait for ID from pixel on first page load (#9756) --- modules/sharedIdSystem.js | 18 ++++++----- modules/userId/index.js | 4 +-- test/spec/modules/userId_spec.js | 53 ++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 7562b472047..860c9d7483d 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -38,12 +38,15 @@ function readValue(name, type) { } } -function getIdCallback(pubcid, pixelCallback) { - return function (callback) { - if (typeof pixelCallback === 'function') { - pixelCallback(); +function getIdCallback(pubcid, pixelUrl) { + return function (callback, getStoredId) { + if (pixelUrl) { + queuePixelCallback(pixelUrl, pubcid, () => { + callback(getStoredId() || pubcid); + })(); + } else { + callback(pubcid); } - callback(pubcid); } } @@ -58,7 +61,7 @@ function queuePixelCallback(pixelUrl, id = '', callback) { const targetUrl = buildUrl(urlInfo); return function () { - triggerPixel(targetUrl); + triggerPixel(targetUrl, callback); }; } @@ -125,8 +128,7 @@ export const sharedIdSystemSubmodule = { if (!newId) newId = (create && hasDeviceAccess()) ? generateUUID() : undefined; } - const pixelCallback = queuePixelCallback(pixelUrl, newId); - return {id: newId, callback: getIdCallback(newId, pixelCallback)}; + return {id: newId, callback: getIdCallback(newId, pixelUrl)}; }, /** * performs action to extend an id. There are generally two ways to extend the expiration time diff --git a/modules/userId/index.js b/modules/userId/index.js index 4c8d4c0be38..d4a8679f0ca 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -413,7 +413,7 @@ function processSubmoduleCallbacks(submodules, cb) { moduleDone(); } try { - submodule.callback(callbackCompleted); + submodule.callback(callbackCompleted, getStoredValue.bind(null, submodule.config?.storage)); } catch (e) { logError(`Error in userID module '${submodule.submodule.name}':`, e); moduleDone(); @@ -982,7 +982,7 @@ function updateSubmodules() { submodule: i, config: submoduleConfig, callback: undefined, - idObj: undefined + idObj: undefined, } : null; }).filter(submodule => submodule !== null) .forEach((sm) => submodules.push(sm)); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 33e2e2ea264..f092486c587 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -53,6 +53,7 @@ import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import {getPPID} from '../../../src/adserver.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {getCoreStorageManager} from '../../../src/storageManager.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -426,6 +427,58 @@ describe('User ID', function () { }); }); + describe('submodule callback', () => { + const TEST_KEY = 'testKey'; + + function setVal(val) { + if (val) { + coreStorage.setDataInLocalStorage(TEST_KEY, val); + coreStorage.setDataInLocalStorage(TEST_KEY + '_exp', ''); + } else { + coreStorage.removeDataFromLocalStorage(TEST_KEY); + coreStorage.removeDataFromLocalStorage(TEST_KEY + '_exp'); + } + } + afterEach(() => { + setVal(null); + }) + + it('should be able to re-read ID changes', (done) => { + setVal(null); + init(config); + setSubmoduleRegistry([{ + name: 'mockId', + getId: function (_1, _2, storedId) { + expect(storedId).to.not.exist; + setVal('laterValue'); + return { + callback(_, readId) { + expect(readId()).to.eql('laterValue'); + done(); + } + } + }, + decode(d) { + return d + } + }]); + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [ + { + name: 'mockId', + storage: { + type: 'html5', + name: TEST_KEY + } + } + ] + } + }); + }); + }); + it('should set PPID when the source needs to call out to the network', () => { let adUnits = [getAdUnitMock()]; init(config); From bfa9a50e78f0626dde2160cdbfd1c88604ba6032 Mon Sep 17 00:00:00 2001 From: Nisar Thadathil Date: Tue, 4 Apr 2023 18:35:51 +0530 Subject: [PATCH 266/375] bugfix in vidoomy adapter: schain serialized (#9759) --- modules/vidoomyBidAdapter.js | 30 ++++++++++++++++++- test/spec/modules/vidoomyBidAdapter_spec.js | 33 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index 146ea6dd4bd..08c7d1b31ac 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -46,6 +46,34 @@ const isBidRequestValid = bid => { return true; }; +/** + * Schain Object needed encodes URI Component with exlamation mark + * @param {String} str + * @returns {String} + */ +function encodeURIComponentWithExlamation(str) { + return encodeURIComponent(str).replace(/!/g, '%21'); +} + +/** + * Serializes the supply chain object based on IAB standards + * @see https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md + * @param {Object} schainObj supply chain object + * @returns {string} serialized supply chain value + */ +function serializeSupplyChainObj(schainObj) { + if (!schainObj || !schainObj.nodes) { + return ''; + } + const nodeProps = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + const serializedNodes = schainObj.nodes.map(node => + nodeProps.map(prop => encodeURIComponentWithExlamation(node[prop] || '')).join(',') + ).join('!'); + + const serializedSchain = `${schainObj.ver},${schainObj.complete}!${serializedNodes}`; + return serializedSchain; +} + const isBidResponseValid = bid => { if (!bid || !bid.requestId || !bid.cpm || !bid.ttl || !bid.currency) { return false; @@ -91,7 +119,7 @@ const buildRequests = (validBidRequests, bidderRequest) => { dt: /Mobi/.test(navigator.userAgent) ? 2 : 1, pid: bid.params.pid, requestId: bid.bidId, - schain: bid.schain || '', + schain: serializeSupplyChainObj(bid.schain) || '', bidfloor, d: getDomainWithoutSubdomain(hostname), // 'vidoomy.com', // TODO: does the fallback make sense here? diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 61b7f2fad7d..8a3e61ca43a 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -74,6 +74,33 @@ describe('vidoomyBidAdapter', function() { 'sizes': [[300, 250], [200, 100]] } }, + 'schain': { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'exchange1.com', + 'sid': '1234!abcd', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher, Inc.', + 'domain': 'publisher.com' + }, + { + 'asi': 'exchange2.com', + 'sid': 'abcd', + 'hp': 1 + }, + { + 'asi': 'exchange2.com', + 'sid': 'abcd', + 'hp': 1, + 'rid': 'bid-request-2', + 'name': 'intermediary', + 'domain': 'intermediary.com' + } + ] + } }, { 'bidder': 'vidoomy', @@ -128,6 +155,12 @@ describe('vidoomyBidAdapter', function() { expect('' + request[1].data.id).to.equal('456456'); expect('' + request[1].data.pid).to.equal('456456'); }); + + it('should send schain parameter in serialized form', function () { + const serializedForm = '1.0,1!exchange1.com,1234%21abcd,1,bid-request-1,publisher%2C%20Inc.,publisher.com!exchange2.com,abcd,1,,,!exchange2.com,abcd,1,bid-request-2,intermediary,intermediary.com' + expect(request[0].data).to.include.any.keys('schain'); + expect(request[0].data.schain).to.eq(serializedForm); + }); }); describe('interpretResponse', function () { From 51612e70cd1fe40309eae2f1fd5336d390bd1d11 Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Tue, 4 Apr 2023 08:16:20 -0500 Subject: [PATCH 267/375] Add ext.tid to data when creating slot params (#9745) --- modules/rubiconBidAdapter.js | 1 + test/spec/modules/rubiconBidAdapter_spec.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 11d60e77ece..36e077aeab8 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -465,6 +465,7 @@ export const spec = { 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, 'x_source.tid': bidRequest.transactionId, + 'x_imp.ext.tid': bidRequest.transactionId, 'l_pb_bid_id': bidRequest.bidId, 'p_screen_res': _getScreenResolution(), 'tk_user_key': params.userId, diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index d535dc84411..e3ebf15619d 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -600,7 +600,7 @@ describe('the rubicon adapter', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'l_pb_bid_id', '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', 'x_imp.ext.tid', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; request.data.split('&').forEach((item, i) => { expect(item.split('=')[0]).to.equal(referenceOrdering[i]); @@ -619,6 +619,7 @@ describe('the rubicon adapter', function () { 'rand': '0.1', 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', From 945b1e1c9690bddd8cabe6cd3bd57ca1535cfb51 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 4 Apr 2023 11:34:02 -0400 Subject: [PATCH 268/375] 33across Id System: Stop generating error for all cookied users (#9760) * Update 33acrossIdSystem.js * Update 33acrossIdSystem.js * Update 33acrossIdSystem_spec.js --- modules/33acrossIdSystem.js | 7 +++++-- test/spec/modules/33acrossIdSystem_spec.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 5e07fe9523d..be81b26b110 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -17,8 +17,11 @@ const CALLER_NAME = 'pbjs'; function getEnvelope(response) { if (!response.succeeded) { - logError(`${MODULE_NAME}: Unsuccessful response`); - + if (response.error == 'Cookied User') { + logMessage(`${MODULE_NAME}: Unsuccessful response`.concat(' ', response.error)); + } else { + logError(`${MODULE_NAME}: Unsuccessful response`.concat(' ', response.error)); + } return; } diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 765b320f925..5070d2b8845 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -282,7 +282,7 @@ describe('33acrossIdSystem', () => { error: 'foo' })); - expect(logErrorSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: Unsuccessful response`)).to.be.true; + expect(logErrorSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: Unsuccessful response foo`)).to.be.true; logErrorSpy.restore(); }); From 4167bae3e07c9c6e2300e4ad94ed723b871beb6d Mon Sep 17 00:00:00 2001 From: Justin Quinn <42960493+Justin-Quinn51@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:40:24 -0400 Subject: [PATCH 269/375] Multiple Adapters : removing cross-module imports (#9617) * Removed 'CreateEidsArray' import statements, as well as references to it within corresponding functions. Added references to 'userIdAsEids' property as an alternative * Revert "Removed 'CreateEidsArray' import statements, as well as references to it within corresponding functions. Added references to 'userIdAsEids' property as an alternative" This reverts commit f654a5b75f70485e98d8e630d6302f258baa7624. * passed all tests * passed tests with bluebillywigAdapter * Impactify bid adapter passed tests * Mediakeys bid adapter passing tests * Sharethrough bid adapter passed tests * Connectad bid adapter passed tests * Added tests back to expectedEids object, and adjusted userIdAsEids array to reflect changes * Yieldmo bid adapter passed tests * Smartadserver bid adapter passed tests * Removed unnecessary reassignment of bidUserId to eids * Removed unnecessary reassignment of bidUserId to eids * Improveddigital bid adapter passed tests * Yieldmo bid adapter passed tests * Sovrn bid adapter passed tests * Ttd bid adapter passed tests * Removed reference to userIdAsEids to ensure tdid property is not undefined * Addressed issue where eids could return null or undefined --- modules/adagioBidAdapter.js | 5 +- modules/adyoulikeBidAdapter.js | 5 +- modules/bluebillywigBidAdapter.js | 6 +- modules/connectadBidAdapter.js | 6 +- modules/impactifyBidAdapter.js | 6 +- modules/mediakeysBidAdapter.js | 5 +- modules/sharethroughBidAdapter.js | 3 +- modules/smartadserverBidAdapter.js | 5 +- modules/sovrnBidAdapter.js | 5 +- modules/ttdBidAdapter.js | 5 +- modules/yieldmoBidAdapter.js | 5 +- test/spec/modules/adagioBidAdapter_spec.js | 28 ++--- test/spec/modules/adyoulikeBidAdapter_spec.js | 24 ++-- .../modules/bluebillywigBidAdapter_spec.js | 4 +- test/spec/modules/connectadBidAdapter_spec.js | 14 ++- .../modules/improvedigitalBidAdapter_spec.js | 13 +- test/spec/modules/mediakeysBidAdapter_spec.js | 28 ++--- .../modules/sharethroughBidAdapter_spec.js | 118 ++++++++++++++---- .../modules/smartadserverBidAdapter_spec.js | 86 +++++++++++-- test/spec/modules/sovrnBidAdapter_spec.js | 31 ++++- test/spec/modules/ttdBidAdapter_spec.js | 9 +- test/spec/modules/yieldmoBidAdapter_spec.js | 20 ++- 22 files changed, 295 insertions(+), 136 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 49f3fdc6e52..3d3c1df0e45 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -28,7 +28,6 @@ import {loadExternalScript} from '../src/adloader.js'; import {verify} from 'criteo-direct-rsa-validate/build/verify.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo, parseDomain} from '../src/refererDetection.js'; -import {createEidsArray} from './userId/eids.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {OUTSTREAM} from '../src/video.js'; @@ -401,8 +400,8 @@ function _getSchain(bidRequest) { } function _getEids(bidRequest) { - if (deepAccess(bidRequest, 'userId')) { - return createEidsArray(bidRequest.userId); + if (deepAccess(bidRequest, 'userIdAsEids')) { + return bidRequest.userIdAsEids; } } diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index e6cdc7698bf..376d108e980 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,6 +1,5 @@ import {buildUrl, deepAccess, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {createEidsArray} from './userId/eids.js'; import {find} from '../src/polyfill.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -122,8 +121,8 @@ export const spec = { payload.ortb2 = bidderRequest.ortb2; } - if (deepAccess(bidderRequest, 'userId')) { - payload.userId = createEidsArray(bidderRequest.userId); + if (deepAccess(bidderRequest, 'userIdAsEids')) { + payload.userId = bidderRequest.userIdAsEids; } payload.pbjs_version = '$prebid.version$'; diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index 27e310177f6..9022ca120af 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -4,7 +4,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; -import {createEidsArray} from './userId/eids.js'; const DEV_MODE = window.location.search.match(/bbpbs_debug=true/); @@ -52,10 +51,9 @@ const BB_HELPERS = { else if (Array.isArray(adServerCur) && adServerCur.length) request.cur = [adServerCur[0]]; }, addUserIds: function(request, validBidRequests) { - const bidUserId = deepAccess(validBidRequests, '0.userId'); - const eids = createEidsArray(bidUserId); + const eids = deepAccess(validBidRequests, '0.userIdAsEids'); - if (eids.length) { + if (eids != null && eids.length) { deepSetValue(request, 'user.ext.eids', eids); } }, diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index 5185308eab0..d53e3b28ab5 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -2,8 +2,6 @@ import { deepSetValue, convertTypes, tryAppendQueryString, logWarn } from '../sr import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import {config} from '../src/config.js'; -import {createEidsArray} from './userId/eids.js'; - const BIDDER_CODE = 'connectad'; const BIDDER_CODE_ALIAS = 'connectadrealtime'; const ENDPOINT_URL = 'https://i.connectad.io/api/v2'; @@ -73,8 +71,8 @@ export const spec = { } // EIDS Support - if (validBidRequests[0].userId) { - deepSetValue(data, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); + if (validBidRequests[0].userIdAsEids) { + deepSetValue(data, 'user.ext.eids', validBidRequests[0].userIdAsEids); } validBidRequests.map(bid => { diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index 04f34bdc7d9..a44e1d4dd3f 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -2,7 +2,6 @@ import { deepAccess, deepSetValue, generateUUID } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { ajax } from '../src/ajax.js'; -import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'impactify'; const BIDDER_ALIAS = ['imp']; @@ -63,9 +62,8 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { if (schain) request.source.ext = { schain: schain }; // Set eids - let bidUserId = deepAccess(validBidRequests, '0.userId'); - let eids = createEidsArray(bidUserId); - if (eids.length) { + let eids = deepAccess(validBidRequests, '0.userIdAsEids'); + if (eids && eids.length) { deepSetValue(request, 'user.ext.eids', eids); } diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index 60ced650329..de86bfa248f 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -20,7 +20,6 @@ import { import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {createEidsArray} from './userId/eids.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const AUCTION_TYPE = 1; @@ -638,8 +637,8 @@ export const spec = { deepSetValue(payload, 'regs.coppa', 1); } - if (deepAccess(validBidRequests[0], 'userId')) { - deepSetValue(payload, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); + if (deepAccess(validBidRequests[0], 'userIdAsEids')) { + deepSetValue(payload, 'user.ext.eids', validBidRequests[0].userIdAsEids); } // Assign payload.site from refererinfo diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 0c8b84fd0c5..07dc065a419 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -2,7 +2,6 @@ import { deepAccess, generateUUID, inIframe } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { createEidsArray } from './userId/eids.js'; const VERSION = '4.3.0'; const BIDDER_CODE = 'sharethrough'; @@ -66,7 +65,7 @@ export const sharethroughAdapterSpec = { req.user = nullish(firstPartyData.user, {}); if (!req.user.ext) req.user.ext = {}; - req.user.ext.eids = createEidsArray(deepAccess(bidRequests[0], 'userId')) || []; + req.user.ext.eids = bidRequests[0].userIdAsEids || []; if (bidderRequest.gdprConsent) { const gdprApplies = bidderRequest.gdprConsent.gdprApplies === true; diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index c07b2abe933..fd2d6e16463 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,7 +1,6 @@ import { deepAccess, deepClone, logError, isFn, isPlainObject } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; -import { createEidsArray } from './userId/eids.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'smartadserver'; @@ -177,8 +176,8 @@ export const spec = { } } - if (bid && bid.userId) { - payload.eids = createEidsArray(bid.userId); + if (bid && bid.userIdAsEids) { + payload.eids = bid.userIdAsEids; } if (bidderRequest && bidderRequest.uspConsent) { diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index ab8abb7e2b4..9982c9afa45 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -15,7 +15,6 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js' -import {createEidsArray} from './userId/eids.js'; const ORTB_VIDEO_PARAMS = { 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), @@ -90,8 +89,8 @@ export const spec = { let criteoId; _each(bidReqs, function (bid) { - if (!eids && bid.userId) { - eids = createEidsArray(bid.userId) + if (!eids && bid.userIdAsEids) { + eids = bid.userIdAsEids; eids.forEach(function (id) { if (id.uids && id.uids[0]) { if (id.source === 'criteo.com') { diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 3bf20ed43ae..db166577d82 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -1,6 +1,5 @@ import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; -import { createEidsArray } from './userId/eids.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -125,8 +124,8 @@ function getUser(bidderRequest) { user.buyeruid = bidderRequest.bids[0].userId.tdid; } - var eids = createEidsArray(utils.deepAccess(bidderRequest, 'bids.0.userId')) - if (eids.length) { + var eids = utils.deepAccess(bidderRequest, 'bids.0.userIdAsEids') + if (eids && eids.length) { utils.deepSetValue(user, 'ext.eids', eids); } diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index b4b916a1048..526e1911a06 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -17,7 +17,6 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {Renderer} from '../src/Renderer.js'; import {find, includes} from '../src/polyfill.js'; -import {createEidsArray} from './userId/eids.js'; const BIDDER_CODE = 'yieldmo'; const GVLID = 173; @@ -652,8 +651,8 @@ function shortcutProperty(extraCharacters, target, propertyName) { * @return array of eids objects */ function getEids(bidRequest) { - if (deepAccess(bidRequest, 'userId')) { - return createEidsArray(bidRequest.userId) || []; + if (deepAccess(bidRequest, 'userIdAsEids')) { + return bidRequest.userIdAsEids || []; } }; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 36e3f04b2a7..759b16a81bb 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -684,32 +684,26 @@ describe('Adagio bid adapter', () => { }); describe('with userID modules', function() { - const userId = { - pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', - unsuported: '666' - }; + const userIdAsEids = [{ + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' + } + ] + }]; it('should send "user.eids" in the request for Prebid.js supported modules only', function() { const bid01 = new BidRequestBuilder({ - userId + userIdAsEids }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); - const expected = [{ - source: 'pubcid.org', - uids: [ - { - atype: 1, - id: '01EAJWWNEPN3CYMM5N8M5VXY22' - } - ] - }]; - - expect(requests[0].data.user.eids).to.have.lengthOf(1); - expect(requests[0].data.user.eids).to.deep.equal(expected); + expect(requests[0].data.user.eids).to.deep.equal(userIdAsEids); }); it('should send an empty "user.eids" array in the request if userId module is unsupported', function() { diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index 24cede20352..c69d31d1bbd 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -672,10 +672,18 @@ describe('Adyoulike Adapter', function () { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - 'userId': { - pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', - unsuported: '666' - } + 'userIdAsEids': + [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' + } + ] + } + ] }; bidderRequest.bids = bidRequestWithSinglePlacement; @@ -684,13 +692,7 @@ describe('Adyoulike Adapter', function () { const payload = JSON.parse(request.data); expect(payload.userId).to.exist; - expect(payload.userId).to.deep.equal([{ - 'source': 'pubcid.org', - 'uids': [{ - 'atype': 1, - 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' - }] - }]); + expect(payload.userId).to.deep.equal(bidderRequest.userIdAsEids); }); it('sends bid request to endpoint with single placement', function () { diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index 0a9c6b30c94..0826acc7f29 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -535,10 +535,8 @@ describe('BlueBillywigAdapter', () => { }); it('should set user ids when present', () => { - const userId = { tdid: 123 }; - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].userId = { criteoId: 'sample-userid' }; + newBaseValidBidRequests[0].userIdAsEids = [ {} ]; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); diff --git a/test/spec/modules/connectadBidAdapter_spec.js b/test/spec/modules/connectadBidAdapter_spec.js index 7a70c4bacdb..d8dfcb0ce98 100644 --- a/test/spec/modules/connectadBidAdapter_spec.js +++ b/test/spec/modules/connectadBidAdapter_spec.js @@ -46,9 +46,17 @@ describe('ConnectAd Adapter', function () { auctionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', bidderRequestId: '1c56ad30b9b8ca8', transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', - userId: { - tdid: '123456' - } + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': '123456' + } + ] + } + ] }]; bidderRequest = { diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 3b951c6e045..1c3bd3197d0 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -14,7 +14,6 @@ import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; -import {createEidsArray} from '../../../modules/userId/eids.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; @@ -579,7 +578,15 @@ describe('Improve Digital Adapter Tests', function () { }); it('should add eids', function () { - const userId = { id5id: { uid: '1111' } }; + const userIdAsEids = [ + { + source: 'id5-sync.com', + uids: [{ + atype: 1, + id: '1111' + }] + } + ]; const expectedUserObject = { ext: { eids: [{ source: 'id5-sync.com', uids: [{ @@ -588,7 +595,7 @@ describe('Improve Digital Adapter Tests', function () { }] }]}}; const bidRequest = Object.assign({}, simpleBidRequest); - bidRequest.userIdAsEids = createEidsArray(userId); + bidRequest.userIdAsEids = userIdAsEids; const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; const payload = JSON.parse(request.data); expect(payload.user).to.deep.equal(expectedUserObject); diff --git a/test/spec/modules/mediakeysBidAdapter_spec.js b/test/spec/modules/mediakeysBidAdapter_spec.js index 393b6ac6764..75c7f42cf58 100644 --- a/test/spec/modules/mediakeysBidAdapter_spec.js +++ b/test/spec/modules/mediakeysBidAdapter_spec.js @@ -601,33 +601,29 @@ describe('mediakeysBidAdapter', function () { }); describe('should support userId modules', function() { - const userId = { - pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', - unsuported: '666' - }; + const userIdAsEids = [{ + source: 'pubcid.org', + uids: [ + { + atype: 1, + id: '01EAJWWNEPN3CYMM5N8M5VXY22' + } + ] + }]; it('should send "user.eids" in the request for Prebid.js supported modules only', function() { const bidCopy = utils.deepClone(bid); - bidCopy.userId = userId; + bidCopy.userIdAsEids = userIdAsEids; const bidderRequestCopy = utils.deepClone(bidderRequest); - bidderRequestCopy.bids[0].userId = userId; + bidderRequestCopy.bids[0].userIdAsEids = userIdAsEids; const bidRequests = [utils.deepClone(bidCopy)]; const request = spec.buildRequests(bidRequests, bidderRequestCopy); const data = request.data; - const expected = [{ - source: 'pubcid.org', - uids: [ - { - atype: 1, - id: '01EAJWWNEPN3CYMM5N8M5VXY22' - } - ] - }]; + const expected = userIdAsEids; expect(data.user.ext).to.exist; - expect(data.user.ext.eids).to.have.lengthOf(1); expect(data.user.ext.eids).to.deep.equal(expected); }); }); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 56e10a74240..f635791aeed 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -93,31 +93,107 @@ describe('sharethrough adapter spec', function () { }, }, }, - userId: { - tdid: 'fake-tdid', - pubcid: 'fake-pubcid', - idl_env: 'fake-identity-link', - id5id: { - uid: 'fake-id5id', - ext: { - linkType: 2, - }, + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-pubcid' + }, + ] + }, + { + 'source': 'liveramp.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-identity-link' + } + ] + }, + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-id5id' + } + ] }, - lipb: { - lipbid: 'fake-lipbid', + { + 'source': 'adserver.org', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-tdid' + } + ] }, - criteoId: 'fake-criteo', - britepoolid: 'fake-britepool', - intentIqId: 'fake-intentiq', - lotamePanoramaId: 'fake-lotame', - parrableId: { - eid: 'fake-parrable', + { + 'source': 'criteo.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-criteo' + } + ] }, - netId: 'fake-netid', - sharedid: { - id: 'fake-sharedid', + { + 'source': 'britepool.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-britepool' + } + ] }, - }, + { + 'source': 'liveintent.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-lipbid' + } + ] + }, + { + 'source': 'intentiq.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-intentiq' + } + ] + }, + { + 'source': 'crwdcntrl.net', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-lotame' + } + ] + }, + { + 'source': 'parrable.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-parrable' + } + ] + }, + { + 'source': 'netid.de', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-netid' + } + ] + } + ], crumbs: { pubcid: 'fake-pubcid-in-crumbs-obj', }, diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 97250ad0ebc..504ff978e9e 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -57,18 +57,80 @@ describe('Smart bid adapter tests', function () { }, requestId: 'efgh5678', transactionId: 'zsfgzzg', - userId: { - britepoolid: '1111', - criteoId: '1111', - digitrustid: { data: { id: 'DTID', keyv: 4, privacy: { optout: false }, producer: 'ABC', version: 2 } }, - id5id: { uid: '1111' }, - idl_env: '1111', - lipbid: '1111', - parrableid: 'eidVersion.encryptionKeyReference.encryptedValue', - pubcid: '1111', - tdid: '1111', - netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - } + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'britepoolid', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'id5id', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'idl_env', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'lipbid', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'parrableid', + 'uids': [ + { + 'atype': 1, + 'id': 'eidVersion.encryptionKeyReference.encryptedValue' + } + ] + }, + { + 'source': 'tdid', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'netId', + 'uids': [ + { + 'atype': 1, + 'id': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg' + } + ] + } + ] }]; // Default params without optional ones diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 02a3d86b29a..0fedd4b9f49 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -270,14 +270,33 @@ describe('sovrnBidAdapter', function() { expect(data.source.ext.schain.nodes.length).to.equal(1) }) - it('should add eds to the bid request', function() { + it('should add eids to the bid request', function() { const criteoIdRequest = { ...baseBidRequest, - userId: { - criteoId: 'A_CRITEO_ID', - tdid: 'SOMESORTOFID', - } - } + userIdAsEids: [ + { + source: 'criteo.com', + uids: [ + { + atype: 1, + id: 'A_CRITEO_ID' + } + ] + }, + { + source: 'adserver.org', + uids: [ + { + atype: 1, + ext: { + rtiPartner: 'TDID' + }, + id: 'SOMESORTOFID' + } + ] + } + ] + }; const criteoIdRequests = [criteoIdRequest, baseBidRequest] const ext = JSON.parse(spec.buildRequests(criteoIdRequests, baseBidderRequest).data).user.ext const firstEID = ext.eids[0] diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index ebaed2502f8..f9adecabddf 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -489,13 +489,7 @@ describe('ttdBidAdapter', function () { const TDID = '00000000-0000-0000-0000-000000000000'; const UID2 = '99999999-9999-9999-9999-999999999999'; let clonedBannerRequests = deepClone(baseBannerBidRequests); - clonedBannerRequests[0].userId = { - tdid: TDID, - uid2: { - id: UID2 - } - }; - const expectedEids = [ + clonedBannerRequests[0].userIdAsEids = [ { source: 'adserver.org', uids: [ @@ -518,6 +512,7 @@ describe('ttdBidAdapter', function () { ] } ]; + const expectedEids = clonedBannerRequests[0].userIdAsEids; const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; expect(requestBody.user.ext.eids).to.deep.equal(expectedEids); diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 4123422ce6e..d6bf15ff9d4 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -371,7 +371,15 @@ describe('YieldmoAdapter', function () { it('should add eids to the banner bid request', function () { const params = { - userId: {pubcid: 'fake_pubcid'}, + userIdAsEids: [{ + source: 'pubcid.org', + uids: [ + { + id: 'fake_pubcid', + atype: 1, + } + ] + }], fakeUserIdAsEids: [{ source: 'pubcid.org', uids: [{ @@ -532,7 +540,15 @@ describe('YieldmoAdapter', function () { it('should add eids to the video bid request', function () { const params = { - userId: {pubcid: 'fake_pubcid'}, + userIdAsEids: [{ + source: 'pubcid.org', + uids: [ + { + id: 'fake_pubcid', + atype: 1, + } + ] + }], fakeUserIdAsEids: [{ source: 'pubcid.org', uids: [{ From 76399e27cfff8719ba0508eddd2c9161abb3abc2 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 4 Apr 2023 09:55:19 -0700 Subject: [PATCH 270/375] Build system: do not allow cross-module imports (#8293) * eslint validate-imports plugin: do not allow cross-module imports * Update onetag * fix conflicts * update imports * refactor audiencerun & jixie --------- Co-authored-by: Chris Huie Co-authored-by: caseywhitmire <60086994+caseywhitmire@users.noreply.github.com> --- modules/audiencerunBidAdapter.js | 19 ++-- modules/jixieBidAdapter.js | 9 +- plugins/eslint/validateImports.js | 27 +++-- .../modules/audiencerunBidAdapter_spec.js | 19 ++-- test/spec/modules/jixieBidAdapter_spec.js | 102 ++++++++---------- 5 files changed, 75 insertions(+), 101 deletions(-) diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index 754a48ede75..efd88aa6f58 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -1,18 +1,17 @@ import { + _each, deepAccess, - isFn, - logError, - getValue, + formatQS, getBidIdParameter, - _each, + getValue, isArray, + isFn, + logError, triggerPixel, - formatQS, } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { createEidsArray } from './userId/eids.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; const BIDDER_CODE = 'audiencerun'; const BASE_URL = 'https://d.audiencerun.com'; @@ -135,7 +134,7 @@ export const spec = { payload.uspConsent = deepAccess(bidderRequest, 'uspConsent'); payload.schain = deepAccess(bidRequests, '0.schain'); - payload.userId = deepAccess(bidRequests, '0.userId') ? createEidsArray(bidRequests[0].userId) : []; + payload.userId = deepAccess(bidRequests, '0.userIdAsEids') || [] if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr = { diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 5eeab197f3a..586fce84804 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -6,7 +6,6 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {Renderer} from '../src/Renderer.js'; -import {createEidsArray} from './userId/eids.js'; const BIDDER_CODE = 'jixie'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -190,12 +189,10 @@ export const spec = { let miscDims = internal.getMiscDims(); let schain = deepAccess(validBidRequests[0], 'schain'); + let eids1 = validBidRequests[0].userIdAsEids // all available user ids are sent to our backend in the standard array layout: - if (validBidRequests[0].userId) { - let eids1 = createEidsArray(validBidRequests[0].userId); - if (eids1.length) { - eids = eids1; - } + if (eids1 && eids1.length) { + eids = eids1; } // we want to send this blob of info to our backend: let pg = config.getConfig('priceGranularity'); diff --git a/plugins/eslint/validateImports.js b/plugins/eslint/validateImports.js index 37a87fffb50..324b75c4ce7 100644 --- a/plugins/eslint/validateImports.js +++ b/plugins/eslint/validateImports.js @@ -1,11 +1,17 @@ -let path = require('path'); -let _ = require('lodash'); -let resolveFrom = require('resolve-from'); +const path = require('path'); +const _ = require('lodash'); +const resolveFrom = require('resolve-from'); +const MODULES_PATH = path.resolve(__dirname, '../../modules'); + +function isInDirectory(filename, dir) { + const rel = path.relative(dir, filename); + return rel && !rel.startsWith('..') && !path.isAbsolute(rel); +} function flagErrors(context, node, importPath) { let absFileDir = path.dirname(context.getFilename()); - let absImportPath = path.resolve(absFileDir, importPath); + let absImportPath = importPath.startsWith('.') ? path.resolve(absFileDir, importPath) : require.resolve(importPath); try { resolveFrom(absFileDir, importPath); @@ -20,16 +26,9 @@ function flagErrors(context, node, importPath) { ) { context.report(node, `import "${importPath}" not in import whitelist`); } else { - let absModulePath = path.resolve(__dirname, '../../modules'); - - // don't allow import of any files directly within modules folder or index.js files within modules' sub-folders - if ( - path.dirname(absImportPath) === absModulePath || ( - absImportPath.startsWith(absModulePath) && - path.basename(absImportPath) === 'index.js' - ) - ) { - context.report(node, `import "${importPath}" cannot require module entry point`); + // do not allow cross-module imports + if (isInDirectory(absImportPath, MODULES_PATH) && (!isInDirectory(absImportPath, absFileDir) || absFileDir === MODULES_PATH)) { + context.report(node, `import "${importPath}": importing from modules is not allowed`); } // don't allow extension-less local imports diff --git a/test/spec/modules/audiencerunBidAdapter_spec.js b/test/spec/modules/audiencerunBidAdapter_spec.js index 57de8bdb0df..5c736345068 100644 --- a/test/spec/modules/audiencerunBidAdapter_spec.js +++ b/test/spec/modules/audiencerunBidAdapter_spec.js @@ -218,16 +218,7 @@ describe('AudienceRun bid adapter tests', function () { it('should add userid eids information to the request', function () { const bid = Object.assign({}, bidRequest); - bid.userId = { - pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', - unsuported: '666', - } - - const request = spec.buildRequests([bid]); - const payload = JSON.parse(request.data); - - expect(payload.userId).to.exist; - expect(payload.userId).to.deep.equal([ + bid.userIdAsEids = [ { source: 'pubcid.org', uids: [ @@ -237,7 +228,13 @@ describe('AudienceRun bid adapter tests', function () { }, ], }, - ]); + ]; + + const request = spec.buildRequests([bid]); + const payload = JSON.parse(request.data); + + expect(payload.userId).to.exist; + expect(payload.userId).to.deep.equal(bid.userIdAsEids); }); it('should add schain object if available', function() { diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index c354de3c3ac..5bf2a3b6fc9 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -309,71 +309,53 @@ describe('jixie Adapter', function () { it('should populate eids when supported userIds are available', function () { const oneSpecialBidReq = Object.assign({}, bidRequests_[0], { - userId: { - tdid: '11111111-2222-3333-4444-555555555555', - uid2: { id: 'AbCdEfGhIjKlMnO9qdQBW7qtMw8f1WTUvtkHe6u+fqLfhbtsqrJ697Z6YoI3IB9klGUv1wvlFIbwH7ELDlqQBGtj8AC1v7fMJ/Q45E7W90dts7UQLTDMLNmtHBRDXVb0Fpas4Vh3yN1jGVQNhzXC/RpGIVtZE8dCxcjfa7VfcTNcvxxxxx==' }, - pubProvidedId: [{ - source: 'puburl1.com', - uids: [{ - id: 'pubid1', - atype: 1, - ext: { - stype: 'ppuid' - } - }] - }, { - source: 'puburl2.com', - uids: [{ - id: 'pubid2' - }] - }] - } - }); - const request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); - const payload = JSON.parse(request.data); - expect(payload.eids).to.deep.include({ - 'source': 'adserver.org', - 'uids': [ + userIdAsEids: [ { - 'id': '11111111-2222-3333-4444-555555555555', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - } - ] - }); - expect(payload.eids).to.deep.include({ - 'source': 'uidapi.com', - 'uids': [ + 'source': 'adserver.org', + 'uids': [ + { + 'id': '11111111-2222-3333-4444-555555555555', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + }, { - 'id': 'AbCdEfGhIjKlMnO9qdQBW7qtMw8f1WTUvtkHe6u+fqLfhbtsqrJ697Z6YoI3IB9klGUv1wvlFIbwH7ELDlqQBGtj8AC1v7fMJ/Q45E7W90dts7UQLTDMLNmtHBRDXVb0Fpas4Vh3yN1jGVQNhzXC/RpGIVtZE8dCxcjfa7VfcTNcvxxxxx==', - 'atype': 3 - } - ] - }); - - expect(payload.eids).to.deep.include({ - 'source': 'puburl1.com', - 'uids': [ + 'source': 'uidapi.com', + 'uids': [ + { + 'id': 'AbCdEfGhIjKlMnO9qdQBW7qtMw8f1WTUvtkHe6u+fqLfhbtsqrJ697Z6YoI3IB9klGUv1wvlFIbwH7ELDlqQBGtj8AC1v7fMJ/Q45E7W90dts7UQLTDMLNmtHBRDXVb0Fpas4Vh3yN1jGVQNhzXC/RpGIVtZE8dCxcjfa7VfcTNcvxxxxx==', + 'atype': 3 + } + ] + }, { - 'id': 'pubid1', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - } - ] - }); - - expect(payload.eids).to.deep.include({ - 'source': 'puburl2.com', - 'uids': [ + 'source': 'puburl1.com', + 'uids': [ + { + 'id': 'pubid1', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + }, { - 'id': 'pubid2' - } - ] + 'source': 'puburl2.com', + 'uids': [ + { + 'id': 'pubid2' + } + ] + }, + ], }); + const request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + const payload = JSON.parse(request.data); + expect(payload.eids).to.eql(oneSpecialBidReq.userIdAsEids); }); });// describe From e6497b43d1e2b6c85ac1a95bdb50df9f51c4c668 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 4 Apr 2023 10:10:33 -0700 Subject: [PATCH 271/375] Core: cache `rerefererInfo` as long as location does not change (#9670) --- src/refererDetection.js | 60 +++++++++++++++++++----------- test/spec/refererDetection_spec.js | 57 +++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/refererDetection.js b/src/refererDetection.js index e0cb15522cc..93ebf085dd5 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -51,6 +51,26 @@ export function parseDomain(url, {noLeadingWww = false, noPort = false} = {}) { return url; } +/** + * This function returns canonical URL which refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage + * + * @param {Object} doc document + * @returns {string|null} + */ +function getCanonicalUrl(doc) { + try { + const element = doc.querySelector("link[rel='canonical']"); + + if (element !== null) { + return element.href; + } + } catch (e) { + // Ignore error + } + + return null; +} + /** * @param {Window} win Window * @returns {Function} @@ -75,26 +95,6 @@ export function detectReferer(win) { } } - /** - * This function returns canonical URL which refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage - * - * @param {Object} doc document - * @returns {string|null} - */ - function getCanonicalUrl(doc) { - try { - const element = doc.querySelector("link[rel='canonical']"); - - if (element !== null) { - return element.href; - } - } catch (e) { - // Ignore error - } - - return null; - } - // TODO: the meaning of "reachedTop" seems to be intentionally ambiguous - best to leave them out of // the typedef for now. (for example, unit tests enforce that "reachedTop" should be false in some situations where we // happily provide a location for the top). @@ -260,7 +260,25 @@ export function detectReferer(win) { return refererInfo; } +// cache result of fn (= referer info) as long as: +// - we are the top window +// - canonical URL tag and window location have not changed +export function cacheWithLocation(fn, win = window) { + if (win.top !== win) return fn; + let canonical, href, value; + return function () { + const newCanonical = getCanonicalUrl(win.document); + const newHref = win.location.href; + if (canonical !== newCanonical || newHref !== href) { + canonical = newCanonical; + href = newHref; + value = fn(); + } + return value; + } +} + /** * @type {function(): refererInfo} */ -export const getRefererInfo = detectReferer(window); +export const getRefererInfo = cacheWithLocation(detectReferer(window)); diff --git a/test/spec/refererDetection_spec.js b/test/spec/refererDetection_spec.js index a0ffc3eddbe..800222892e6 100644 --- a/test/spec/refererDetection_spec.js +++ b/test/spec/refererDetection_spec.js @@ -1,4 +1,4 @@ -import {detectReferer, ensureProtocol, parseDomain} from 'src/refererDetection.js'; +import {cacheWithLocation, detectReferer, ensureProtocol, parseDomain} from 'src/refererDetection.js'; import {config} from 'src/config.js'; import {expect} from 'chai'; @@ -493,3 +493,58 @@ describe('parseDomain', () => { }) }) }); + +describe('cacheWithLocation', () => { + let fn, win, cached; + const RESULT = 'result'; + beforeEach(() => { + fn = sinon.stub().callsFake(() => RESULT); + win = { + location: { + }, + document: { + querySelector: sinon.stub() + } + } + }); + + describe('when window is not on top', () => { + beforeEach(() => { + win.top = {}; + cached = cacheWithLocation(fn, win); + }) + + it('should not cache', () => { + win.top = {}; + cached(); + expect(cached()).to.eql(RESULT); + expect(fn.callCount).to.eql(2); + }); + }) + + describe('when window is on top', () => { + beforeEach(() => { + win.top = win; + cached = cacheWithLocation(fn, win); + }) + + it('should not cache when canonical URL changes', () => { + let canonical = 'foo'; + win.document.querySelector.callsFake(() => ({href: canonical})); + cached(); + expect(cached()).to.eql(RESULT); + canonical = 'bar'; + expect(cached()).to.eql(RESULT); + expect(fn.callCount).to.eql(2); + }); + + it('should not cache when location changes', () => { + win.location.href = 'foo'; + cached(); + expect(cached()).to.eql(RESULT); + win.location.href = 'bar'; + expect(cached()).to.eql(RESULT); + expect(fn.callCount).to.eql(2); + }) + }); +}) From 50c93ff5290044ed75bd5a1daacf74febe5bc07f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Tue, 4 Apr 2023 19:17:16 +0200 Subject: [PATCH 272/375] Greenbids Analytics Adapter: create new analytics adapter for Greenbids (#9690) * Add Greenbids Analytics module * fix doc * rework payload --- modules/greenbidsAnalyticsAdapter.js | 181 ++++++++ modules/greenbidsAnalyticsAdapter.md | 23 + .../modules/greenbidsAnalyticsAdapter_spec.js | 418 ++++++++++++++++++ 3 files changed, 622 insertions(+) create mode 100644 modules/greenbidsAnalyticsAdapter.js create mode 100644 modules/greenbidsAnalyticsAdapter.md create mode 100644 test/spec/modules/greenbidsAnalyticsAdapter_spec.js diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js new file mode 100644 index 00000000000..38ed14dd295 --- /dev/null +++ b/modules/greenbidsAnalyticsAdapter.js @@ -0,0 +1,181 @@ +import {ajax} from '../src/ajax.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager.js'; +import {deepClone, logError, logInfo} from '../src/utils.js'; + +const analyticsType = 'endpoint'; + +export const ANALYTICS_VERSION = '1.0.0'; + +const ANALYTICS_SERVER = 'https://europe-west2-greenbids-357713.cloudfunctions.net/publisher-analytics-endpoint'; + +const { + EVENTS: { + AUCTION_END, + BID_TIMEOUT, + } +} = CONSTANTS; + +export const BIDDER_STATUS = { + BID: 'bid', + NO_BID: 'noBid', + TIMEOUT: 'timeout' +}; + +const analyticsOptions = {}; + +export const parseBidderCode = function (bid) { + let bidderCode = bid.bidderCode || bid.bidder; + return bidderCode.toLowerCase(); +}; + +export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER, analyticsType}), { + + cachedAuctions: {}, + + initConfig(config) { + /** + * Required option: pbuid + * @type {boolean} + */ + analyticsOptions.options = deepClone(config.options); + if (typeof config.options.pbuid !== 'string' || config.options.pbuid.length < 1) { + logError('"options.pbuid" is required.'); + return false; + } + analyticsOptions.sampled = true; + if (typeof config.options.sampling === 'number') { + analyticsOptions.sampled = Math.random() < parseFloat(config.options.sampling); + } + + analyticsOptions.pbuid = config.options.pbuid + analyticsOptions.server = ANALYTICS_SERVER; + return true; + }, + sendEventMessage(endPoint, data) { + logInfo(`AJAX: ${endPoint}: ` + JSON.stringify(data)); + + ajax(`${analyticsOptions.server}${endPoint}`, null, JSON.stringify(data), { + contentType: 'application/json' + }); + }, + createCommonMessage(auctionId) { + return { + version: ANALYTICS_VERSION, + auctionId: auctionId, + referrer: window.location.href, + sampling: analyticsOptions.options.sampling, + prebid: '$prebid.version$', + pbuid: analyticsOptions.pbuid, + adUnits: [], + }; + }, + serializeBidResponse(bid, status) { + return { + bidder: bid.bidder, + isTimeout: (status === BIDDER_STATUS.TIMEOUT), + hasBid: (status === BIDDER_STATUS.BID), + }; + }, + addBidResponseToMessage(message, bid, status) { + const adUnitCode = bid.adUnitCode.toLowerCase(); + const adUnitIndex = message.adUnits.findIndex((adUnit) => { + return adUnit.code === adUnitCode; + }); + if (adUnitIndex === -1) { + logError('Trying to add to non registered adunit'); + return; + } + const bidderIndex = message.adUnits[adUnitIndex].bidders.findIndex((bidder) => { + return bidder.bidder === bid.bidder; + }); + if (bidderIndex === -1) { + message.adUnits[adUnitIndex].bidders.push(this.serializeBidResponse(bid, status)); + } else { + if (status === BIDDER_STATUS.BID) { + message.adUnits[adUnitIndex].bidders[bidderIndex].hasBid = true; + } else if (status === BIDDER_STATUS.TIMEOUT) { + message.adUnits[adUnitIndex].bidders[bidderIndex].isTimeout = true; + } + } + }, + createBidMessage(auctionEndArgs, timeoutBids) { + logInfo(auctionEndArgs) + const {auctionId, timestamp, auctionEnd, adUnits, bidsReceived, noBids} = auctionEndArgs; + const message = this.createCommonMessage(auctionId); + + message.auctionElapsed = (auctionEnd - timestamp); + + adUnits.forEach((adUnit) => { + const adUnitCode = adUnit.code.toLowerCase(); + message.adUnits.push({ + code: adUnitCode, + mediaTypes: { + ...(adUnit.mediaTypes.banner !== undefined) && {banner: adUnit.mediaTypes.banner}, + ...(adUnit.mediaTypes.video !== undefined) && {video: adUnit.mediaTypes.video}, + ...(adUnit.mediaTypes.native !== undefined) && {native: adUnit.mediaTypes.native} + }, + bidders: [], + }); + }); + + // We enrich noBid then bids, then timeouts, because in case of a timeout, one response from a bidder + // Can be in the 3 arrays, and we want that case reflected in the call + noBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.NO_BID)); + + bidsReceived.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.BID)); + + timeoutBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.TIMEOUT)); + + return message; + }, + getCachedAuction(auctionId) { + this.cachedAuctions[auctionId] = this.cachedAuctions[auctionId] || { + timeoutBids: [], + }; + return this.cachedAuctions[auctionId]; + }, + handleAuctionEnd(auctionEndArgs) { + const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId); + this.sendEventMessage('/', + this.createBidMessage(auctionEndArgs, cachedAuction.timeoutBids) + ); + }, + handleBidTimeout(timeoutBids) { + timeoutBids.forEach((bid) => { + const cachedAuction = this.getCachedAuction(bid.auctionId); + cachedAuction.timeoutBids.push(bid); + }); + }, + track({eventType, args}) { + if (analyticsOptions.sampled) { + switch (eventType) { + case BID_TIMEOUT: + this.handleBidTimeout(args); + break; + case AUCTION_END: + this.handleAuctionEnd(args); + break; + } + } + }, + getAnalyticsOptions() { + return analyticsOptions; + }, +}); + +greenbidsAnalyticsAdapter.originEnableAnalytics = greenbidsAnalyticsAdapter.enableAnalytics; + +greenbidsAnalyticsAdapter.enableAnalytics = function(config) { + this.initConfig(config); + logInfo('loading greenbids analytics'); + greenbidsAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: greenbidsAnalyticsAdapter, + code: 'greenbids' +}); + +export default greenbidsAnalyticsAdapter; diff --git a/modules/greenbidsAnalyticsAdapter.md b/modules/greenbidsAnalyticsAdapter.md new file mode 100644 index 00000000000..46e3af2c5e2 --- /dev/null +++ b/modules/greenbidsAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# Overview + +``` +Module Name: Greenbids Analytics Adapter +Module Type: Analytics Adapter +Maintainer: jb@greenbids.ai +``` + +# Description + +Analytics adapter for Greenbids + +# Test Parameters + +``` +{ + provider: 'greenbids', + options: { + pbuid: "PBUID_FROM_GREENBIDS" + sampling: 1.0 + } +} +``` diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b3cbf4c47c2 --- /dev/null +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -0,0 +1,418 @@ +import { + greenbidsAnalyticsAdapter, parseBidderCode, + ANALYTICS_VERSION, BIDDER_STATUS +} from 'modules/greenbidsAnalyticsAdapter.js'; + +import {expect} from 'chai'; +import sinon from 'sinon'; + +const events = require('src/events'); +const constants = require('src/constants.json'); + +const pbuid = 'pbuid-AA778D8A796AEA7A0843E2BBEB677766'; +const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e'; + +describe('Greenbids Prebid AnalyticsAdapter Testing', function () { + describe('event tracking and message cache manager', function () { + beforeEach(function () { + const configOptions = { + pbuid: pbuid, + sampling: 0, + }; + + greenbidsAnalyticsAdapter.enableAnalytics({ + provider: 'greenbidsAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + greenbidsAnalyticsAdapter.disableAnalytics(); + }); + + describe('#parseBidderCode()', function() { + it('should get lower case bidder code from bidderCode field value', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'greenbids', + bidderCode: 'GREENBIDS', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseBidderCode(receivedBids[0]); + expect(result).to.equal('greenbids'); + }); + it('should get lower case bidder code from bidder field value as bidderCode field is missing', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'greenbids', + bidderCode: '', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseBidderCode(receivedBids[0]); + expect(result).to.equal('greenbids'); + }); + }); + + describe('#getCachedAuction()', function() { + const existing = {timeoutBids: [{}]}; + greenbidsAnalyticsAdapter.cachedAuctions['test_auction_id'] = existing; + + it('should get the existing cached object if it exists', function() { + const result = greenbidsAnalyticsAdapter.getCachedAuction('test_auction_id'); + + expect(result).to.equal(existing); + }); + + it('should create a new object and store it in the cache on cache miss', function() { + const result = greenbidsAnalyticsAdapter.getCachedAuction('no_such_id'); + + expect(result).to.deep.include({ + timeoutBids: [], + }); + }); + }); + + describe('when formatting JSON payload sent to backend', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit-1', + bidder: 'greenbids', + bidderCode: 'greenbids', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + { + auctionId: auctionId, + adUnitCode: 'adunit-1', + bidder: 'greenbidsx', + bidderCode: 'greenbidsx', + requestId: 'b2c3d4e5', + timeToRespond: 100, + cpm: 0.08, + currency: 'USD', + ad: 'fake ad2' + }, + { + auctionId: auctionId, + adUnitCode: 'adunit-2', + bidder: 'greenbids', + bidderCode: 'greenbids', + requestId: 'c3d4e5f6', + timeToRespond: 120, + cpm: 0.09, + currency: 'USD', + ad: 'fake ad3' + }, + ]; + const noBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit-2', + bidder: 'greenbids', + bidderCode: 'greenbids', + bidId: 'a1b2c3d4', + } + ]; + const timeoutBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit-2', + bidder: 'greenbids', + bidderCode: 'greenbids', + bidId: '00123d4c', + } + ]; + function assertHavingRequiredMessageFields(message) { + expect(message).to.include({ + version: ANALYTICS_VERSION, + auctionId: auctionId, + pbuid: pbuid, + referrer: window.location.href, + sampling: 0, + prebid: '$prebid.version$', + }); + } + + describe('#createCommonMessage', function() { + it('should correctly serialize some common fields', function() { + const message = greenbidsAnalyticsAdapter.createCommonMessage(auctionId); + + assertHavingRequiredMessageFields(message); + }); + }); + + describe('#serializeBidResponse', function() { + it('should handle BID properly with timeout false and hasBid true', function() { + const result = greenbidsAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID); + + expect(result).to.include({ + bidder: 'greenbids', + isTimeout: false, + hasBid: true, + }); + }); + + it('should handle NO_BID properly and set hasBid to false', function() { + const result = greenbidsAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.NO_BID); + + expect(result).to.include({ + bidder: 'greenbids', + isTimeout: false, + hasBid: false, + }); + }); + + it('should handle TIMEOUT properly and set isTimeout to true', function() { + const result = greenbidsAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.TIMEOUT); + + expect(result).to.include({ + bidder: 'greenbids', + isTimeout: true, + hasBid: false, + }); + }); + }); + + describe('#addBidResponseToMessage()', function() { + it('should add a bid response in the output message, grouped by adunit_id and bidder', function() { + const message = { + adUnits: [ + { + code: 'adunit-2', + bidders: [] + } + ] + }; + greenbidsAnalyticsAdapter.addBidResponseToMessage(message, noBids[0], BIDDER_STATUS.NO_BID); + + expect(message.adUnits[0]).to.deep.include({ + code: 'adunit-2', + bidders: [ + { + bidder: 'greenbids', + isTimeout: false, + hasBid: false, + } + ] + }); + }); + }); + + describe('#createBidMessage()', function() { + it('should format auction message sent to the backend', function() { + const args = { + auctionId: auctionId, + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + adUnitCodes: ['adunit-1', 'adunit-2'], + adUnits: [ + { + code: 'adunit-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + } + }, + { + code: 'adunit-2', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'instream', + mimes: ['video/mp4'], + playerSize: [[640, 480]], + skip: 1, + protocols: [1, 2, 3, 4] + }, + } + }, + ], + bidsReceived: receivedBids, + noBids: noBids + }; + + const result = greenbidsAnalyticsAdapter.createBidMessage(args, timeoutBids); + + assertHavingRequiredMessageFields(result); + expect(result).to.deep.include({ + auctionElapsed: 100, + adUnits: [ + { + code: 'adunit-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidders: [ + { + bidder: 'greenbids', + isTimeout: false, + hasBid: true + }, + { + bidder: 'greenbidsx', + isTimeout: false, + hasBid: true + } + ] + }, + { + code: 'adunit-2', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'instream', + mimes: ['video/mp4'], + playerSize: [[640, 480]], + skip: 1, + protocols: [1, 2, 3, 4] + } + }, + bidders: [ + { + bidder: 'greenbids', + isTimeout: true, + hasBid: true + } + ] + } + ], + }); + }); + }); + + describe('#handleBidTimeout()', function() { + it('should cached the timeout bid as BID_TIMEOUT event was triggered', function() { + greenbidsAnalyticsAdapter.cachedAuctions['test_timeout_auction_id'] = { 'timeoutBids': [] }; + const args = [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }]; + + greenbidsAnalyticsAdapter.handleBidTimeout(args); + const result = greenbidsAnalyticsAdapter.getCachedAuction('test_timeout_auction_id'); + expect(result).to.deep.include({ + timeoutBids: [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }] + }); + }); + }); + }); + }); + + describe('greenbids Analytics Adapter track handler ', function () { + const configOptions = { + pbuid: pbuid, + sampling: 1, + }; + + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + greenbidsAnalyticsAdapter.enableAnalytics({ + provider: 'greenbidsAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + greenbidsAnalyticsAdapter.disableAnalytics(); + events.getEvents.restore(); + }); + + it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { + sinon.spy(greenbidsAnalyticsAdapter, 'handleBidTimeout'); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + sinon.assert.callCount(greenbidsAnalyticsAdapter.handleBidTimeout, 1); + greenbidsAnalyticsAdapter.handleBidTimeout.restore(); + }); + + it('should call handleAuctionEnd as AUCTION_END trigger event', function() { + sinon.spy(greenbidsAnalyticsAdapter, 'handleAuctionEnd'); + events.emit(constants.EVENTS.AUCTION_END, {}); + sinon.assert.callCount(greenbidsAnalyticsAdapter.handleAuctionEnd, 1); + greenbidsAnalyticsAdapter.handleAuctionEnd.restore(); + }); + }); + + describe('enableAnalytics and config parser', function () { + const configOptions = { + pbuid: pbuid, + sampling: 0, + }; + + beforeEach(function () { + greenbidsAnalyticsAdapter.enableAnalytics({ + provider: 'greenbidsAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + greenbidsAnalyticsAdapter.disableAnalytics(); + }); + + it('should parse config correctly with optional values', function () { + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().options).to.deep.equal(configOptions); + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().pbuid).to.equal(configOptions.pbuid); + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().sampled).to.equal(false); + }); + + it('should not enable Analytics when pbuid is missing', function () { + const configOptions = { + options: { + } + }; + const validConfig = greenbidsAnalyticsAdapter.initConfig(configOptions); + expect(validConfig).to.equal(false); + }); + it('should fall back to default value when sampling factor is not number', function () { + const configOptions = { + options: { + pbuid: pbuid, + sampling: 'string', + } + }; + greenbidsAnalyticsAdapter.enableAnalytics({ + provider: 'greenbidsAnalytics', + options: configOptions + }); + + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().sampled).to.equal(false); + }); + }); +}); From c0eadc0eb560859f440b74f3fa63e83ae3260299 Mon Sep 17 00:00:00 2001 From: pkowalski-id5 <112544015+pkowalski-id5@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:55:19 +0200 Subject: [PATCH 273/375] LocalStorage availability logging (#9755) --- modules/id5IdSystem.js | 3 +- test/spec/modules/id5IdSystem_spec.js | 45 ++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 488df984913..c26ba3662e1 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -263,7 +263,8 @@ class IdFetchFlow { 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$', - 'storage': this.submoduleConfig.storage + 'storage': this.submoduleConfig.storage, + 'localStorage': storage.localStorageIsEnabled() ? 1 : 0 }; // pass in optional data, but only if populated diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 392c11f3529..51954f76356 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -65,6 +65,10 @@ describe('ID5 ID System', function () { } } + const HEADERS_CONTENT_TYPE_JSON = { + 'Content-Type': 'application/json' + } + function getId5FetchConfig(storageName = ID5_STORAGE_NAME, storageType = 'html5') { return { name: ID5_MODULE_NAME, @@ -148,7 +152,7 @@ describe('ID5 ID System', function () { } respondWithConfigAndExpectNext(configRequest, config = ID5_API_CONFIG) { - configRequest.respond(200, {'Content-Type': 'application/json'}, JSON.stringify(config)); + configRequest.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(config)); return this.expectNextRequest() } @@ -249,7 +253,7 @@ describe('ID5 ID System', function () { }); describe('Xhr Requests from getId()', function () { - const responseHeader = {'Content-Type': 'application/json'}; + const responseHeader = HEADERS_CONTENT_TYPE_JSON beforeEach(function () { }); @@ -719,6 +723,40 @@ describe('ID5 ID System', function () { }) }); + describe('Local storage', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'localStorageIsEnabled'); + }); + afterEach(() => { + sandbox.restore(); + }); + [ + [true, 1], + [false, 0] + ].forEach(function ([isEnabled, expectedValue]) { + it(`should check localStorage availability and log in request. Available=${isEnabled}`, () => { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let config = getId5FetchConfig(); + let submoduleResponse = callSubmoduleGetId(config, undefined, undefined); + storage.localStorageIsEnabled.callsFake(() => isEnabled) + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.localStorage).is.eq(expectedValue); + + fetchRequest.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + .then(submoduleResponse => { + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + }); + }) + }) + }); + describe('Request Bids Hook', function () { let adUnits; let sandbox; @@ -842,8 +880,7 @@ describe('ID5 ID System', function () { expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); expect(requestBody.nbPage).is.eq(2); expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); - const responseHeader = {'Content-Type': 'application/json'}; - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + request.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); return new Promise(function (resolve) { (function waitForCondition() { From 8c8a2d7d2aa9202ef0823d27ad7f5a079ac3c284 Mon Sep 17 00:00:00 2001 From: Hiroaki Kubota Date: Wed, 5 Apr 2023 02:57:06 +0900 Subject: [PATCH 274/375] Craft Bid Adapter : update for imuIdSystem (#9757) * update craftBidAdapter for imuIdSystem * update craftBidAdapter for develop --- modules/craftBidAdapter.js | 53 +++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 519c4dd9b6f..8ffdabcb597 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -33,9 +33,9 @@ export const spec = { buildRequests: function(bidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); - + const bidRequest = bidRequests[0]; const tags = bidRequests.map(bidToTag); - const schain = bidRequests[0].schain; + const schain = bidRequest.schain; const payload = { tags: [...tags], ua: navigator.userAgent, @@ -44,26 +44,31 @@ export const spec = { }, schain: schain }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - } - if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; - } - if (bidderRequest && bidderRequest.refererInfo) { - let refererinfo = { - // TODO: this collects everything it finds, except for the canonical URL - rd_ref: bidderRequest.refererInfo.topmostLocation, - rd_top: bidderRequest.refererInfo.reachedTop, - rd_ifs: bidderRequest.refererInfo.numIframes, - }; - if (bidderRequest.refererInfo.stack) { - refererinfo.rd_stk = bidderRequest.refererInfo.stack.join(','); + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + if (bidderRequest.refererInfo) { + let refererinfo = { + // TODO: this collects everything it finds, except for the canonical URL + rd_ref: bidderRequest.refererInfo.topmostLocation, + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + }; + if (bidderRequest.refererInfo.stack) { + refererinfo.rd_stk = bidderRequest.refererInfo.stack.join(','); + } + payload.referrer_detection = refererinfo; + } + if (bidRequest.userId) { + payload.userId = bidRequest.userId } - payload.referrer_detection = refererinfo; } const request = formatRequest(payload, bidderRequest); return request; @@ -106,7 +111,7 @@ export const spec = { params = convertTypes({ 'sitekey': 'string', 'placementId': 'string', - 'keywords': transformBidderParamKeywords + 'keywords': transformBidderParamKeywords, }, params); if (isOpenRtb) { if (isPopulatedArray(params.keywords)) { @@ -148,11 +153,11 @@ function formatRequest(payload, bidderRequest) { withCredentials: false }; } - + const baseUrl = payload.tags[0].url || URL_BASE; const payloadString = JSON.stringify(payload); return { method: 'POST', - url: `${URL_BASE}/${payload.tags[0].sitekey}`, + url: `${baseUrl}/${payload.tags[0].sitekey}`, data: payloadString, bidderRequest, options From 3cb413f05adcf49a70e6bc53cab878e271b4dc46 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Thu, 6 Apr 2023 13:32:38 +0200 Subject: [PATCH 275/375] ZetaGlobalSsp Bid Adapter: provide tagid from params (#9764) * ZetaGlobalSsp: provide tagid from params * fix test --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspBidAdapter.js | 3 +++ test/spec/modules/zeta_global_sspBidAdapter_spec.js | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 2f392ccdbd3..531384b9f27 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -77,6 +77,9 @@ export const spec = { id: request.bidId, secure: secure }; + if (params.tagid) { + impData.tagid = params.tagid; + } if (request.mediaTypes) { for (const mediaType in request.mediaTypes) { switch (mediaType) { diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 4e18f49c849..c3678427a9a 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -51,6 +51,7 @@ describe('Zeta Ssp Bid Adapter', function () { }, sid: 'publisherId', shortname: 'test_shortname', + tagid: 'test_tag_id', site: { page: 'testPage' }, @@ -396,4 +397,11 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.source.ext.schain).to.eql(schain); }); + + it('Test tagid provided', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].tagid).to.eql(params.tagid); + }); }); From 640a4ea2b43edeae1083ebe730934af4e5ea285b Mon Sep 17 00:00:00 2001 From: AndreaC <67786179+darkstarac@users.noreply.github.com> Date: Thu, 6 Apr 2023 14:40:18 +0200 Subject: [PATCH 276/375] AIDEM Bid Adapter: extended app/site parameters on win notice (#9765) * 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 * Extended win notice --------- Co-authored-by: Giovanni Sollazzo Co-authored-by: darkstar --- modules/aidemBidAdapter.js | 5 ++ test/spec/modules/aidemBidAdapter_spec.js | 80 ++++++++++++++++++++--- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index e4d5c618b77..2f8732a8ec4 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -152,6 +152,7 @@ function getPageUrl(bidderRequest) { function buildWinNotice(bid) { const params = bid.params[0]; + const app = deepAccess(bid, 'meta.ext.app') return { publisherId: params.publisherId, siteId: params.siteId, @@ -167,6 +168,9 @@ function buildWinNotice(bid) { ttl: bid.ttl, requestTimestamp: bid.requestTimestamp, responseTimestamp: bid.responseTimestamp, + mediatype: bid.mediaType, + environment: app ? 'app' : 'web', + ...app }; } @@ -348,6 +352,7 @@ function getPrebidResponseBidObject(openRTBResponseBidObject) { function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) { logInfo('AIDEM Bid Adapter meta', openRTBResponseBidObject); deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', deepAccess(openRTBResponseBidObject, 'meta.advertiserDomains')); + deepSetValue(prebidResponseBidObject, 'meta.ext', deepAccess(openRTBResponseBidObject, 'meta.ext')); if (openRTBResponseBidObject.cat && Array.isArray(openRTBResponseBidObject.cat)) { const primaryCatId = openRTBResponseBidObject.cat.shift(); deepSetValue(prebidResponseBidObject, 'meta.primaryCatId', primaryCatId); diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index f58e49eb364..6a875feb2a9 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -276,7 +276,7 @@ const SERVER_RESPONSE_VIDEO = { }, } -const WIN_NOTICE = { +const WIN_NOTICE_WEB = { 'adId': '3a20ee5dc78c1e', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'creativeId': '24277955', @@ -297,9 +297,71 @@ const WIN_NOTICE = { 'USD' ], 'mediaType': 'banner', - 'advertiserDomains': [ - 'abc.com' + 'meta': { + 'advertiserDomains': [ + 'cloudflare.com' + ], + 'ext': {} + }, + 'size': '300x250', + 'params': [ + { + 'placementId': '13144370', + 'siteId': '23434', + 'publisherId': '7689670753' + } + ], + 'width': 300, + 'height': 250, + 'status': 'rendered', + 'transactionId': 'ce089116-4251-45c3-bdbb-3a03cb13816b', + 'ttl': 300, + 'requestTimestamp': 1666796241007, + 'responseTimestamp': 1666796241021, + metrics: { + getMetrics() { + return { + + } + } + } +} + +const WIN_NOTICE_APP = { + 'adId': '3a20ee5dc78c1e', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'creativeId': '24277955', + 'cpm': 1, + 'netRevenue': false, + 'adserverTargeting': { + 'hb_bidder': 'aidem', + 'hb_adid': '3a20ee5dc78c1e', + 'hb_pb': '1.00', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + + 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', + 'currency': [ + 'USD' ], + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': [ + 'cloudflare.com' + ], + 'ext': { + 'app': { + 'app_bundle': '{{APP_BUNDLE}}', + 'app_id': '{{APP_ID}}', + 'app_name': '{{APP_NAME}}', + 'app_store_url': '{{APP_STORE_URL}}', + 'inventory_source': '{{INVENTORY_SOURCE}}' + } + } + }, 'size': '300x250', 'params': [ { @@ -549,11 +611,13 @@ describe('Aidem adapter', () => { expect(spec.onBidWon).to.exist.and.to.be.a('function') }); - it(`should send a valid bid won notice`, function () { - spec.onBidWon(WIN_NOTICE); - // server.respondWith('POST', WIN_EVENT_URL, [ - // 400, {'Content-Type': 'application/json'}, ) - // ]); + it(`should send a valid bid won notice from web environment`, function () { + spec.onBidWon(WIN_NOTICE_WEB); + expect(server.requests.length).to.equal(1); + }); + + it(`should send a valid bid won notice from app environment`, function () { + spec.onBidWon(WIN_NOTICE_APP); expect(server.requests.length).to.equal(1); }); }); From 3c8d5c75959929078f8710cfef9e14eca443992c Mon Sep 17 00:00:00 2001 From: Alexandru Date: Thu, 6 Apr 2023 17:00:20 +0300 Subject: [PATCH 277/375] BrightcomSSP, Brightcom: remove options overide from buildRequests (#9753) --- modules/brightcomBidAdapter.js | 1 - modules/brightcomSSPBidAdapter.js | 1 - 2 files changed, 2 deletions(-) diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index 15b4175b59a..6c7223a562c 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -100,7 +100,6 @@ function buildRequests(bidReqs, bidderRequest) { method: 'POST', url: URL, data: JSON.stringify(brightcomBidReq), - options: {contentType: 'text/plain', withCredentials: false} }; } catch (e) { logError(e, {bidReqs, bidderRequest}); diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js index b7a9aa3fdc9..8ace326e3b0 100644 --- a/modules/brightcomSSPBidAdapter.js +++ b/modules/brightcomSSPBidAdapter.js @@ -116,7 +116,6 @@ function buildRequests(bidReqs, bidderRequest) { method: 'POST', url: URL, data: JSON.stringify(payload), - options: {contentType: 'text/plain', withCredentials: false} }; } catch (e) { logError(e, {bidReqs, bidderRequest}); From 0b80f443b8087b9e3f7394af4721e30175813db4 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 6 Apr 2023 10:37:34 -0400 Subject: [PATCH 278/375] TTD Bid Adapter: add support for video.plcmt and imp.rwdd (#9762) * Update ttdBidAdapter.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter.js * Update ttdBidAdapter_spec.js --- modules/ttdBidAdapter.js | 11 +++++++++-- test/spec/modules/ttdBidAdapter_spec.js | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index db166577d82..b9e209f7cd7 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -164,15 +164,18 @@ function getImpression(bidRequest) { const gpid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); const tid = utils.deepAccess(bidRequest, 'ortb2Imp.ext.tid'); + const rwdd = utils.deepAccess(bidRequest, 'ortb2Imp.rwdd'); if (gpid || tid) { impression.ext = {} if (gpid) { impression.ext.gpid = gpid } if (tid) { impression.ext.tid = tid } } - + if (rwdd) { + impression.rwdd = rwdd; + } const tagid = gpid || bidRequest.params.placementId; if (tagid) { - impression.tagid = tagid + impression.tagid = tagid; } const mediaTypesVideo = utils.deepAccess(bidRequest, 'mediaTypes.video'); @@ -251,6 +254,7 @@ function video(bid) { const api = utils.deepAccess(bid, 'mediaTypes.video.api'); const mimes = utils.deepAccess(bid, 'mediaTypes.video.mimes'); const placement = utils.deepAccess(bid, 'mediaTypes.video.placement'); + const plcmt = utils.deepAccess(bid, 'mediaTypes.video.plcmt'); const protocols = utils.deepAccess(bid, 'mediaTypes.video.protocols'); const playbackmethod = utils.deepAccess(bid, 'mediaTypes.video.playbackmethod'); const pos = utils.deepAccess(bid, 'mediaTypes.video.pos'); @@ -286,6 +290,9 @@ function video(bid) { if (playbackmethod) { video.playbackmethod = playbackmethod; } + if (plcmt) { + video.plcmt = plcmt; + } if (pos) { video.pos = pos; } diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index f9adecabddf..8c0a9db8fbd 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -282,6 +282,21 @@ describe('ttdBidAdapter', function () { expect(requestBody.imp[0].ext.gpid).to.equal(gpid); }); + it('sends rwdd in imp.rwdd if present', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + const gpid = '/1111/home#header'; + const rwdd = 1; + clonedBannerRequests[0].ortb2Imp = { + rwdd: rwdd, + ext: { + gpid: gpid + } + }; + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.imp[0].rwdd).to.be.not.null; + expect(requestBody.imp[0].rwdd).to.equal(1); + }); + it('sends auction id in source.tid', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.source).to.be.not.null; @@ -877,6 +892,14 @@ describe('ttdBidAdapter', function () { const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; expect(requestBody.imp[0].video.placement).to.equal(3); }); + + it('sets plcmt correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.plcmt = 3; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.plcmt).to.equal(3); + }); }); describe('interpretResponse-empty', function () { From 0beb9a8a8766457478813f85e58867362165aa58 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 6 Apr 2023 16:45:15 +0200 Subject: [PATCH 279/375] Criteo ID Module: Add error callback for pixel sync call (#9754) Co-authored-by: v.raybaud --- modules/criteoIdSystem.js | 3 ++ test/spec/modules/criteoIdSystem_spec.js | 38 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index 867e4315945..700fb566a82 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -102,6 +102,9 @@ function callSyncPixel(domain, pixel) { saveOnAllStorages(pixel.storageKeyName, jsonResponse[pixel.bundlePropertyName], domain); } } + }, + error: error => { + logError(`criteoIdSystem: unable to sync user id`, error); } }, undefined, diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 76d5222c8b2..508a22e8baa 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -185,6 +185,44 @@ describe('CriteoId module', function () { expect(setLocalStorageStub.calledWith('cto_pixel_test', 'ok')).to.be.true; }); + it('should call sync pixels and use error handler', function () { + const expirationTs = new Date(nowTimestamp + cookiesMaxAge).toString(); + + const result = criteoIdSubmodule.getId(); + result.callback((id) => { + }); + + const response = { + pixels: [ + { + pixelUrl: 'pixelUrlWithBundle', + writeBundleInStorage: true, + bundlePropertyName: 'abc', + storageKeyName: 'cto_pixel_test' + } + ] + }; + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(response) + ); + + server.requests[1].respond( + 500, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + abc: 'ok' + }) + ); + + expect(triggerPixelStub.called).to.be.false; + expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.com')).to.be.false; + expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.testdev.com')).to.be.false; + expect(setLocalStorageStub.calledWith('cto_pixel_test', 'ok')).to.be.false; + }); + gdprConsentTestCases.forEach(testCase => it('should call user sync url with the gdprConsent', function () { let callBackSpy = sinon.spy(); let result = criteoIdSubmodule.getId(undefined, testCase.consentData); From f24c2027e92fd76deac3e82e8dda821df6c9a7db Mon Sep 17 00:00:00 2001 From: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Date: Thu, 6 Apr 2023 21:40:53 +0530 Subject: [PATCH 280/375] PubMatic Bid Adapter: native 1.2 support (#9701) * Changes for native 1.2 support * Bug fixes and testacases updation * Changes to include ext and mimes properties of assets and added condition to read len OR length param value * Bug fixes * Bug fix and test cases fix * Test * Test --- modules/pubmaticAnalyticsAdapter.js | 13 +- modules/pubmaticBidAdapter.js | 414 +++++++++---------- test/spec/modules/pubmaticBidAdapter_spec.js | 77 +++- 3 files changed, 271 insertions(+), 233 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index a4a13c56a68..9e2a5b1cfeb 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -291,6 +291,17 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { }, []) } +function getSizesForAdUnit(adUnit) { + var bid = Object.values(adUnit.bids).filter((bid) => !!bid.bidResponse && bid.bidResponse.mediaType === 'native')[0]; + if (!!bid || (bid === undefined && adUnit.dimensions.length === 0)) { + return ['1x1']; + } else { + return adUnit.dimensions.map(function (e) { + return e[0] + 'x' + e[1]; + }) + } +} + function getAdUnitAdFormats(adUnit) { var af = adUnit ? Object.keys(adUnit.mediaTypes || {}).map(format => MEDIATYPE[format.toUpperCase()]) : []; return af; @@ -346,7 +357,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'sn': adUnitId, 'au': origAdUnit.adUnitId || adUnitId, 'mt': getAdUnitAdFormats(origAdUnit), - 'sz': adUnit.dimensions.map(e => e[0] + 'x' + e[1]), + 'sz': getSizesForAdUnit(adUnit, adUnitId), 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)), 'fskp': floorData ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, }; diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 5ca8a2c04b3..67f8f183783 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,10 +1,10 @@ -import { getBidRequest, logWarn, _each, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques } from '../src/utils.js'; +import { getBidRequest, logWarn, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques, isPlainObject, isInteger } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE, ADPOD } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { bidderSettings } from '../src/bidderSettings.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import CONSTANTS from '../src/constants.json'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; @@ -20,6 +20,7 @@ const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; + const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url 'gender': '', // User gender @@ -55,56 +56,11 @@ const VIDEO_CUSTOM_PARAMS = { 'skip': DATA_TYPES.NUMBER } -const NATIVE_ASSETS = { - 'TITLE': { ID: 1, KEY: 'title', TYPE: 0 }, - 'IMAGE': { ID: 2, KEY: 'image', TYPE: 0 }, - 'ICON': { ID: 3, KEY: 'icon', TYPE: 0 }, - 'SPONSOREDBY': { ID: 4, KEY: 'sponsoredBy', TYPE: 1 }, // please note that type of SPONSORED is also 1 - 'BODY': { ID: 5, KEY: 'body', TYPE: 2 }, // please note that type of DESC is also set to 2 - 'CLICKURL': { ID: 6, KEY: 'clickUrl', TYPE: 0 }, - 'VIDEO': { ID: 7, KEY: 'video', TYPE: 0 }, - 'EXT': { ID: 8, KEY: 'ext', TYPE: 0 }, - 'DATA': { ID: 9, KEY: 'data', TYPE: 0 }, - 'LOGO': { ID: 10, KEY: 'logo', TYPE: 0 }, - 'SPONSORED': { ID: 11, KEY: 'sponsored', TYPE: 1 }, // please note that type of SPONSOREDBY is also set to 1 - 'DESC': { ID: 12, KEY: 'data', TYPE: 2 }, // please note that type of BODY is also set to 2 - 'RATING': { ID: 13, KEY: 'rating', TYPE: 3 }, - 'LIKES': { ID: 14, KEY: 'likes', TYPE: 4 }, - 'DOWNLOADS': { ID: 15, KEY: 'downloads', TYPE: 5 }, - 'PRICE': { ID: 16, KEY: 'price', TYPE: 6 }, - 'SALEPRICE': { ID: 17, KEY: 'saleprice', TYPE: 7 }, - 'PHONE': { ID: 18, KEY: 'phone', TYPE: 8 }, - 'ADDRESS': { ID: 19, KEY: 'address', TYPE: 9 }, - 'DESC2': { ID: 20, KEY: 'desc2', TYPE: 10 }, - 'DISPLAYURL': { ID: 21, KEY: 'displayurl', TYPE: 11 }, - 'CTA': { ID: 22, KEY: 'cta', TYPE: 12 } -}; - const NATIVE_ASSET_IMAGE_TYPE = { 'ICON': 1, - 'LOGO': 2, 'IMAGE': 3 } -// check if title, image can be added with mandatory field default values -const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ - { - id: NATIVE_ASSETS.SPONSOREDBY.ID, - required: true, - data: { - type: 1 - } - }, - { - id: NATIVE_ASSETS.TITLE.ID, - required: true, - }, - { - id: NATIVE_ASSETS.IMAGE.ID, - required: true, - } -] - const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', @@ -175,16 +131,9 @@ const MEDIATYPE = [ let publisherId = 0; let isInvalidNativeRequest = false; -let NATIVE_ASSET_ID_TO_KEY_MAP = {}; -let NATIVE_ASSET_KEY_TO_ASSET_MAP = {}; let biddersList = ['pubmatic']; const allBiddersList = ['all']; -// loading NATIVE_ASSET_ID_TO_KEY_MAP -_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); -// loading NATIVE_ASSET_KEY_TO_ASSET_MAP -_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); - export function _getDomainFromURL(url) { let anchor = document.createElement('a'); anchor.href = url; @@ -355,133 +304,189 @@ function _checkParamDataType(key, value, datatype) { return UNDEFINED; } -function _commonNativeRequestObject(nativeAsset, params) { - var key = nativeAsset.KEY; - return { - id: nativeAsset.ID, - required: params[key].required ? 1 : 0, - data: { - type: nativeAsset.TYPE, - len: params[key].len, - ext: params[key].ext - } - }; -} +// TODO delete this code when removing native 1.1 support +const PREBID_NATIVE_DATA_KEYS_TO_ORTB = { + 'desc': 'desc', + 'desc2': 'desc2', + 'body': 'desc', + 'body2': 'desc2', + 'sponsoredBy': 'sponsored', + 'cta': 'ctatext', + 'rating': 'rating', + 'address': 'address', + 'downloads': 'downloads', + 'likes': 'likes', + 'phone': 'phone', + 'price': 'price', + 'salePrice': 'saleprice', + 'displayUrl': 'displayurl', + 'saleprice': 'saleprice', + 'displayurl': 'displayurl' +}; -function _createNativeRequest(params) { - var nativeRequestObject = { +const { NATIVE_IMAGE_TYPES, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS, NATIVE_ASSET_TYPES } = CONSTANTS; +const PREBID_NATIVE_DATA_KEY_VALUES = Object.values(PREBID_NATIVE_DATA_KEYS_TO_ORTB); + +// TODO remove this function when the support for 1.1 is removed +/** + * Copy of the function toOrtbNativeRequest from core native.js to handle the title len/length + * and ext and mimes parameters from legacy assets. + * @param {object} legacyNativeAssets + * @returns an OpenRTB format of the same bid request + */ +export function toOrtbNativeRequest(legacyNativeAssets) { + if (!legacyNativeAssets && !isPlainObject(legacyNativeAssets)) { + logWarn(`${LOG_WARN_PREFIX}: Native assets object is empty or not an object: ${legacyNativeAssets}`); + isInvalidNativeRequest = true; + return; + } + const ortb = { + ver: '1.2', assets: [] }; - for (var key in params) { - if (params.hasOwnProperty(key)) { - var assetObj = {}; - if (!(nativeRequestObject.assets && nativeRequestObject.assets.length > 0 && nativeRequestObject.assets.hasOwnProperty(key))) { - switch (key) { - case NATIVE_ASSETS.TITLE.KEY: - 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 = { - id: NATIVE_ASSETS.IMAGE.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.IMAGE, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), - wmin: params[key].wmin || params[key].minimumWidth || (params[key].minsizes ? params[key].minsizes[0] : UNDEFINED), - hmin: params[key].hmin || params[key].minimumHeight || (params[key].minsizes ? params[key].minsizes[1] : UNDEFINED), - mimes: params[key].mimes, - ext: params[key].ext, - } - }; - break; - case NATIVE_ASSETS.ICON.KEY: - assetObj = { - id: NATIVE_ASSETS.ICON.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.ICON, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), - } - }; - break; - case NATIVE_ASSETS.VIDEO.KEY: - assetObj = { - id: NATIVE_ASSETS.VIDEO.ID, - required: params[key].required ? 1 : 0, - video: { - minduration: params[key].minduration, - maxduration: params[key].maxduration, - protocols: params[key].protocols, - mimes: params[key].mimes, - ext: params[key].ext - } - }; - break; - case NATIVE_ASSETS.EXT.KEY: - assetObj = { - id: NATIVE_ASSETS.EXT.ID, - required: params[key].required ? 1 : 0, - }; - break; - case NATIVE_ASSETS.LOGO.KEY: - assetObj = { - id: NATIVE_ASSETS.LOGO.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.LOGO, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED) - } - }; - break; - case NATIVE_ASSETS.SPONSOREDBY.KEY: - case NATIVE_ASSETS.BODY.KEY: - case NATIVE_ASSETS.RATING.KEY: - case NATIVE_ASSETS.LIKES.KEY: - case NATIVE_ASSETS.DOWNLOADS.KEY: - case NATIVE_ASSETS.PRICE.KEY: - case NATIVE_ASSETS.SALEPRICE.KEY: - case NATIVE_ASSETS.PHONE.KEY: - case NATIVE_ASSETS.ADDRESS.KEY: - case NATIVE_ASSETS.DESC2.KEY: - case NATIVE_ASSETS.DISPLAYURL.KEY: - case NATIVE_ASSETS.CTA.KEY: - assetObj = _commonNativeRequestObject(NATIVE_ASSET_KEY_TO_ASSET_MAP[key], params); - break; + for (let key in legacyNativeAssets) { + // skip conversion for non-asset keys + if (NATIVE_KEYS_THAT_ARE_NOT_ASSETS.includes(key)) continue; + if (!NATIVE_KEYS.hasOwnProperty(key) && !PREBID_NATIVE_DATA_KEY_VALUES.includes(key)) { + logWarn(`${LOG_WARN_PREFIX}: Unrecognized native asset code: ${key}. Asset will be ignored.`); + continue; + } + + const asset = legacyNativeAssets[key]; + let required = 0; + if (asset.required && isBoolean(asset.required)) { + required = Number(asset.required); + } + const ortbAsset = { + id: ortb.assets.length, + required + }; + // data cases + if (key in PREBID_NATIVE_DATA_KEYS_TO_ORTB) { + ortbAsset.data = { + type: NATIVE_ASSET_TYPES[PREBID_NATIVE_DATA_KEYS_TO_ORTB[key]] + } + if (asset.len || asset.length) { + ortbAsset.data.len = asset.len || asset.length; + } + if (asset.ext) { + ortbAsset.data.ext = asset.ext; + } + // icon or image case + } else if (key === 'icon' || key === 'image') { + ortbAsset.img = { + type: key === 'icon' ? NATIVE_IMAGE_TYPES.ICON : NATIVE_IMAGE_TYPES.MAIN, + } + // if min_width and min_height are defined in aspect_ratio, they are preferred + if (asset.aspect_ratios) { + if (!isArray(asset.aspect_ratios)) { + logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios was passed, but it's not a an array: ${asset.aspect_ratios}`); + } else if (!asset.aspect_ratios.length) { + logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios was passed, but it's empty: ${asset.aspect_ratios}`); + } else { + const { min_width: minWidth, min_height: minHeight } = asset.aspect_ratios[0]; + if (!isInteger(minWidth) || !isInteger(minHeight)) { + logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios min_width or min_height are invalid: ${minWidth}, ${minHeight}`); + } else { + ortbAsset.img.wmin = minWidth; + ortbAsset.img.hmin = minHeight; + } + const aspectRatios = asset.aspect_ratios + .filter((ar) => ar.ratio_width && ar.ratio_height) + .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`); + if (aspectRatios.length > 0) { + ortbAsset.img.ext = { + aspectratios: aspectRatios + } + } } } + + ortbAsset.img.w = asset.w || asset.width; + ortbAsset.img.h = asset.h || asset.height; + ortbAsset.img.wmin = asset.wmin || asset.minimumWidth || (asset.minsizes ? asset.minsizes[0] : UNDEFINED); + ortbAsset.img.hmin = asset.hmin || asset.minimumHeight || (asset.minsizes ? asset.minsizes[1] : UNDEFINED); + + // if asset.sizes exist, by OpenRTB spec we should remove wmin and hmin + if (asset.sizes) { + if (asset.sizes.length !== 2 || !isInteger(asset.sizes[0]) || !isInteger(asset.sizes[1])) { + logWarn(`${LOG_WARN_PREFIX}: image.sizes was passed, but its value is not an array of integers: ${asset.sizes}`); + } else { + logInfo(`${LOG_WARN_PREFIX}: if asset.sizes exist, by OpenRTB spec we should remove wmin and hmin`); + ortbAsset.img.w = asset.sizes[0]; + ortbAsset.img.h = asset.sizes[1]; + delete ortbAsset.img.hmin; + delete ortbAsset.img.wmin; + } + } + asset.ext && (ortbAsset.img.ext = asset.ext); + asset.mimes && (ortbAsset.img.mimes = asset.mimes); + // title case + } else if (key === 'title') { + ortbAsset.title = { + // in openRTB, len is required for titles, while in legacy prebid was not. + // for this reason, if len is missing in legacy prebid, we're adding a default value of 140. + len: asset.len || asset.length || 140 + } + asset.ext && (ortbAsset.title.ext = asset.ext); + // all extensions to the native bid request are passed as is + } else if (key === 'ext') { + ortbAsset.ext = asset; + // in `ext` case, required field is not needed + delete ortbAsset.required; } - if (assetObj && assetObj.id) { - nativeRequestObject.assets[nativeRequestObject.assets.length] = assetObj; + ortb.assets.push(ortbAsset); + } + + if (ortb.assets.length < 1) { + logWarn(`${LOG_WARN_PREFIX}: Could not find any valid asset`); + isInvalidNativeRequest = true; + return; + } + + return ortb; +} +// TODO delete this code when removing native 1.1 support + +function _createNativeRequest(params) { + var nativeRequestObject; + + // TODO delete this code when removing native 1.1 support + if (!params.ortb) { // legacy assets definition found + nativeRequestObject = toOrtbNativeRequest(params); + } else { // ortb assets definition found + params = params.ortb; + // TODO delete this code when removing native 1.1 support + nativeRequestObject = { ver: '1.2', ...params, assets: [] }; + const { assets } = params; + + const isValidAsset = (asset) => asset.title || asset.img || asset.data || asset.video; + + if (assets.length < 1 || !assets.some(asset => isValidAsset(asset))) { + logWarn(`${LOG_WARN_PREFIX}: Native assets object is empty or contains some invalid object`); + isInvalidNativeRequest = true; + return nativeRequestObject; } - }; - // for native image adtype prebid has to have few required assests i.e. title,sponsoredBy, image - // if any of these are missing from the request then request will not be sent - var requiredAssetCount = NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.length; - var presentrequiredAssetCount = 0; - NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.forEach(ele => { - var lengthOfExistingAssets = nativeRequestObject.assets.length; - for (var i = 0; i < lengthOfExistingAssets; i++) { - if (ele.id == nativeRequestObject.assets[i].id) { - presentrequiredAssetCount++; - break; + assets.forEach(asset => { + var assetObj = asset; + if (assetObj.img) { + if (assetObj.img.type == NATIVE_ASSET_IMAGE_TYPE.IMAGE) { + assetObj.w = assetObj.w || assetObj.width || (assetObj.sizes ? assetObj.sizes[0] : UNDEFINED); + assetObj.h = assetObj.h || assetObj.height || (assetObj.sizes ? assetObj.sizes[1] : UNDEFINED); + assetObj.wmin = assetObj.wmin || assetObj.minimumWidth || (assetObj.minsizes ? assetObj.minsizes[0] : UNDEFINED); + assetObj.hmin = assetObj.hmin || assetObj.minimumHeight || (assetObj.minsizes ? assetObj.minsizes[1] : UNDEFINED); + } else if (assetObj.img.type == NATIVE_ASSET_IMAGE_TYPE.ICON) { + assetObj.w = assetObj.w || assetObj.width || (assetObj.sizes ? assetObj.sizes[0] : UNDEFINED); + assetObj.h = assetObj.h || assetObj.height || (assetObj.sizes ? assetObj.sizes[1] : UNDEFINED); + } + } + + if (assetObj && assetObj.id !== undefined && isValidAsset(assetObj)) { + nativeRequestObject.assets.push(assetObj); } } - }); - if (requiredAssetCount == presentrequiredAssetCount) { - isInvalidNativeRequest = false; - } else { - isInvalidNativeRequest = true; + ); } return nativeRequestObject; } @@ -624,7 +629,7 @@ function _addJWPlayerSegmentData(imp, bid) { ext && ext.key_val === undefined ? ext.key_val = jwPlayerData : ext.key_val += '|' + jwPlayerData; } -function _createImpressionObject(bid, conf) { +function _createImpressionObject(bid) { var impObj = {}; var bannerObj; var videoObj; @@ -657,11 +662,15 @@ function _createImpressionObject(bid, conf) { } break; case NATIVE: + // TODO uncomment below line when removing native 1.1 support + // nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeOrtbRequest)); + // TODO delete below line when removing native 1.1 support nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeParams)); if (!isInvalidNativeRequest) { impObj.native = nativeObj; } else { logWarn(LOG_WARN_PREFIX + 'Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); + isInvalidNativeRequest = false; } break; case VIDEO: @@ -817,7 +826,6 @@ function _checkMediaType(bid, newBid) { } function _parseNativeResponse(bid, newBid) { - newBid.native = {}; if (bid.hasOwnProperty('adm')) { var adm = ''; try { @@ -826,53 +834,15 @@ function _parseNativeResponse(bid, newBid) { logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); return; } - if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { - newBid.mediaType = NATIVE; - for (let i = 0, len = adm.native.assets.length; i < len; i++) { - switch (adm.native.assets[i].id) { - case NATIVE_ASSETS.TITLE.ID: - newBid.native.title = adm.native.assets[i].title && adm.native.assets[i].title.text; - break; - case NATIVE_ASSETS.IMAGE.ID: - newBid.native.image = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; - break; - case NATIVE_ASSETS.ICON.ID: - newBid.native.icon = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; - break; - case NATIVE_ASSETS.SPONSOREDBY.ID: - case NATIVE_ASSETS.BODY.ID: - case NATIVE_ASSETS.LIKES.ID: - case NATIVE_ASSETS.DOWNLOADS.ID: - case NATIVE_ASSETS.PRICE: - case NATIVE_ASSETS.SALEPRICE.ID: - case NATIVE_ASSETS.PHONE.ID: - case NATIVE_ASSETS.ADDRESS.ID: - case NATIVE_ASSETS.DESC2.ID: - case NATIVE_ASSETS.CTA.ID: - case NATIVE_ASSETS.RATING.ID: - case NATIVE_ASSETS.DISPLAYURL.ID: - newBid.native[NATIVE_ASSET_ID_TO_KEY_MAP[adm.native.assets[i].id]] = adm.native.assets[i].data && adm.native.assets[i].data.value; - break; - } - } - newBid.native.clickUrl = adm.native.link && adm.native.link.url; - newBid.native.clickTrackers = (adm.native.link && adm.native.link.clicktrackers) || []; - newBid.native.impressionTrackers = adm.native.imptrackers || []; - newBid.native.jstracker = adm.native.jstracker || []; - if (!newBid.width) { - newBid.width = DEFAULT_WIDTH; - } - if (!newBid.height) { - newBid.height = DEFAULT_HEIGHT; - } + newBid.native = { + ortb: { ...adm.native } + }; + newBid.mediaType = NATIVE; + if (!newBid.width) { + newBid.width = DEFAULT_WIDTH; + } + if (!newBid.height) { + newBid.height = DEFAULT_HEIGHT; } } } @@ -1075,7 +1045,7 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + // validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -1121,7 +1091,7 @@ export const spec = { if (bid.params.hasOwnProperty('acat') && isArray(bid.params.acat)) { allowedIabCategories = allowedIabCategories.concat(bid.params.acat); } - var impObj = _createImpressionObject(bid, conf); + var impObj = _createImpressionObject(bid); if (impObj) { payload.imp.push(impObj); } diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index ed74792e1cd..66bcf42bb0f 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -194,6 +194,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 1, title: {len: 140} }, + { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 2, required: 1, data: { type: 1 } } + ] + }, bidder: 'pubmatic', params: { publisherId: '5670', @@ -243,6 +251,22 @@ describe('PubMatic adapter', function () { desc2: {required: true, len: 10, ext: {'desc21': 'desc22'}}, displayurl: {required: true, len: 10, ext: {'displayurl1': 'displayurl2'}} }, + nativeOrtbRequest: { + 'ver': '1.2', + 'assets': [ + {'id': 0, 'required': 1, 'title': {'len': 80}}, + {'id': 1, 'required': 1, 'img': {'type': 1, 'w': 50, 'h': 50}}, + {'id': 2, 'required': 1, 'img': {'type': 3, 'w': 728, 'h': 90}}, + {'id': 3, 'required': 1, 'data': {'type': 1, 'len': 10}}, + {'id': 4, 'required': 1, 'data': {'type': 2, 'len': 10}}, + {'id': 5, 'required': 1, 'data': {'type': 3, 'len': 10}}, + {'id': 6, 'required': 1, 'data': {'type': 4, 'len': 10}}, + {'id': 7, 'required': 1, 'data': {'type': 5, 'len': 10}}, + {'id': 8, 'required': 1, 'data': {'type': 6, 'len': 10}}, + {'id': 9, 'required': 1, 'data': {'type': 8, 'len': 10}}, + {'id': 10, 'required': 1, 'data': {'type': 9, 'len': 10}} + ] + }, bidder: 'pubmatic', params: { publisherId: '5670', @@ -301,6 +325,14 @@ describe('PubMatic adapter', function () { image: { required: false, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 0, title: {len: 140} }, + { id: 1, required: 0, img: {type: 3, w: 300, h: 250} }, + { id: 2, required: 1, data: {type: 1} } + ] + }, bidder: 'pubmatic', params: { publisherId: '5670', @@ -389,6 +421,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + {id: 0, required: 1, title: {len: 140}}, + {id: 1, required: 1, img: {type: 3, w: 300, h: 250}}, + {id: 2, required: 1, data: {type: 1}} + ] + }, bidder: 'pubmatic', params: { publisherId: '301', @@ -442,6 +482,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 1, title: {len: 140} }, + { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 2, required: 1, data: { type: 1 } } + ] + }, bidder: 'pubmatic', params: { publisherId: '301', @@ -503,6 +551,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 1, title: {len: 80} }, + { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 2, required: 1, data: { type: 1 } } + ] + }, bidder: 'pubmatic', params: { publisherId: '301', @@ -603,7 +659,7 @@ describe('PubMatic adapter', function () { validnativeBidImpression = { 'native': { - 'request': '{"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":300,"h":250}},{"id":4,"required":1,"data":{"type":1}}]}' + 'request': '{"ver":"1.2","assets":[{"id":0,"required":1,"title":{"len":80}},{"id":1,"required":1,"img":{"type":3,"w":300,"h":250}},{"id":2,"required":1,"data":{"type":1}}]}' } } @@ -615,13 +671,13 @@ describe('PubMatic adapter', function () { validnativeBidImpressionWithRequiredParam = { 'native': { - 'request': '{"assets":[{"id":1,"required":0,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":300,"h":250}},{"id":4,"required":1,"data":{"type":1}}]}' + 'request': '{"ver":"1.2","assets":[{"id":0,"required":0,"title":{"len":80}},{"id":1,"required":0,"img":{"type":3,"w":300,"h":250}},{"id":2,"required":1,"data":{"type":1}}]}' } } validnativeBidImpressionWithAllParams = { native: { - 'request': '{"assets":[{"id":1,"required":1,"title":{"len":80,"ext":{"title1":"title2"}}},{"id":3,"required":1,"img":{"type":1,"w":50,"h":50}},{"id":2,"required":1,"img":{"type":3,"w":728,"h":90,"mimes":["image/png","image/gif"],"ext":{"image1":"image2"}}},{"id":4,"required":1,"data":{"type":1,"len":10,"ext":{"sponsor1":"sponsor2"}}},{"id":5,"required":1,"data":{"type":2,"len":10,"ext":{"body1":"body2"}}},{"id":13,"required":1,"data":{"type":3,"len":10,"ext":{"rating1":"rating2"}}},{"id":14,"required":1,"data":{"type":4,"len":10,"ext":{"likes1":"likes2"}}},{"id":15,"required":1,"data":{"type":5,"len":10,"ext":{"downloads1":"downloads2"}}},{"id":16,"required":1,"data":{"type":6,"len":10,"ext":{"price1":"price2"}}},{"id":17,"required":1,"data":{"type":7,"len":10,"ext":{"saleprice1":"saleprice2"}}},{"id":18,"required":1,"data":{"type":8,"len":10,"ext":{"phone1":"phone2"}}},{"id":19,"required":1,"data":{"type":9,"len":10,"ext":{"address1":"address2"}}},{"id":20,"required":1,"data":{"type":10,"len":10,"ext":{"desc21":"desc22"}}},{"id":21,"required":1,"data":{"type":11,"len":10,"ext":{"displayurl1":"displayurl2"}}}]}' + 'request': '{"ver":"1.2","assets":[{"id":0,"required":1,"title":{"len":80,"ext":{"title1":"title2"}}},{"id":1,"required":1,"img":{"type":1,"w":50,"h":50,"ext":{"icon1":"icon2"}}},{"id":2,"required":1,"img":{"type":3,"w":728,"h":90,"ext":{"image1":"image2"},"mimes":["image/png","image/gif"]}},{"id":3,"required":1,"data":{"type":1,"len":10,"ext":{"sponsor1":"sponsor2"}}},{"id":4,"required":1,"data":{"type":2,"len":10,"ext":{"body1":"body2"}}},{"id":5,"required":1,"data":{"type":3,"len":10,"ext":{"rating1":"rating2"}}},{"id":6,"required":1,"data":{"type":4,"len":10,"ext":{"likes1":"likes2"}}},{"id":7,"required":1,"data":{"type":5,"len":10,"ext":{"downloads1":"downloads2"}}},{"id":8,"required":1,"data":{"type":6,"len":10,"ext":{"price1":"price2"}}},{"id":9,"required":1,"data":{"type":7,"len":10,"ext":{"saleprice1":"saleprice2"}}},{"id":10,"required":1,"data":{"type":8,"len":10,"ext":{"phone1":"phone2"}}},{"id":11,"required":1,"data":{"type":9,"len":10,"ext":{"address1":"address2"}}},{"id":12,"required":1,"data":{"type":10,"len":10,"ext":{"desc21":"desc22"}}},{"id":13,"required":1,"data":{"type":11,"len":10,"ext":{"displayurl1":"displayurl2"}}}]}' } } @@ -3495,16 +3551,17 @@ describe('PubMatic adapter', function () { data.imp[0].id = '2a5571261281d4'; request.data = JSON.stringify(data); let response = spec.interpretResponse(nativeBidResponse, request); + let assets = response[0].native.ortb.assets; expect(response).to.be.an('array').with.length.above(0); expect(response[0].native).to.exist.and.to.be.an('object'); expect(response[0].mediaType).to.exist.and.to.equal('native'); - expect(response[0].native.title).to.exist.and.to.be.an('string'); - expect(response[0].native.image).to.exist.and.to.be.an('object'); - expect(response[0].native.image.url).to.exist.and.to.be.an('string'); - expect(response[0].native.image.height).to.exist; - expect(response[0].native.image.width).to.exist; - expect(response[0].native.sponsoredBy).to.exist.and.to.be.an('string'); - expect(response[0].native.clickUrl).to.exist.and.to.be.an('string'); + expect(assets).to.be.an('array').with.length.above(0); + expect(assets[0].title).to.exist.and.to.be.an('object'); + expect(assets[1].img).to.exist.and.to.be.an('object'); + expect(assets[1].img.url).to.exist.and.to.be.an('string'); + expect(assets[1].img.h).to.exist; + expect(assets[1].img.w).to.exist; + expect(assets[2].data).to.exist.and.to.be.an('object'); }); it('should check for valid banner mediaType in case of multiformat request', function() { From f0c5c27ddeb84948358037d81d3b3a26b6d06999 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 6 Apr 2023 13:50:49 -0400 Subject: [PATCH 281/375] Update undertoneBidAdapter.js (#9778) --- modules/undertoneBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index eda4b6f579c..28893d04b3f 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -76,6 +76,7 @@ function getBannerCoords(id) { export const spec = { code: BIDDER_CODE, + gvlid: 677, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { if (bid && bid.params && bid.params.publisherId) { From 4c1618c7a996c290e6965ca0cf8a12023fd71af4 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Thu, 6 Apr 2023 13:55:51 -0400 Subject: [PATCH 282/375] appnexus bid adapter - add gvlids to aliases (#9777) --- modules/appnexusBidAdapter.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index f354eb053b1..69afec3d1b3 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -102,13 +102,13 @@ export const spec = { aliases: [ { code: 'appnexusAst', gvlid: 32 }, { code: 'emxdigital', gvlid: 183 }, - { code: 'pagescience' }, - { code: 'defymedia' }, - { code: 'gourmetads' }, - { code: 'matomy' }, - { code: 'featureforward' }, - { code: 'oftmedia' }, - { code: 'adasta' }, + { code: 'pagescience', gvlid: 32 }, + { code: 'defymedia', gvlid: 32 }, + { code: 'gourmetads', gvlid: 32 }, + { code: 'matomy', gvlid: 32 }, + { code: 'featureforward', gvlid: 32 }, + { code: 'oftmedia', gvlid: 32 }, + { code: 'adasta', gvlid: 32 }, { code: 'beintoo', gvlid: 618 }, ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 7ea38f153500a24c1bb5dea83100f618496d059c Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 6 Apr 2023 13:56:20 -0400 Subject: [PATCH 283/375] Update gumgumBidAdapter.js (#9779) --- modules/gumgumBidAdapter.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index e9bdc955c1e..5203f190c09 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -6,13 +6,13 @@ import {getStorageManager} from '../src/storageManager.js'; import {includes} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'gumgum' +const BIDDER_CODE = 'gumgum'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); -const ALIAS_BIDDER_CODE = ['gg'] -const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp` +const ALIAS_BIDDER_CODE = ['gg']; +const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp`; const JCSI = { t: 0, rq: 8, pbv: '$prebid.version$' } -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO] -const TIME_TO_LIVE = 60 +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +const TIME_TO_LIVE = 60; const DELAY_REQUEST_TIME = 1800000; // setting to 30 mins let invalidRequestIds = {}; @@ -99,17 +99,6 @@ function getWrapperCode(wrapper, data) { return wrapper.replace('AD_JSON', window.btoa(JSON.stringify(data))) } -function _getDigiTrustQueryParams(userId) { - let digiTrustId = userId.digitrustid && userId.digitrustid.data; - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return {}; - } - return { - dt: digiTrustId.id - }; -} - /** * Serializes the supply chain object according to IAB standards * @see https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md @@ -416,7 +405,7 @@ function buildRequests(validBidRequests, bidderRequest) { sizes, url: BID_ENDPOINT, method: 'GET', - data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId)) + data: Object.assign(data, _getBrowserParams(topWindowUrl)) }); }); return bids; @@ -596,6 +585,7 @@ function getUserSyncs(syncOptions, serverResponses) { export const spec = { code: BIDDER_CODE, + gvlid: 61, aliases: ALIAS_BIDDER_CODE, isBidRequestValid, buildRequests, From 4f45f15bbd2e2319e93c9dca501256cf10a04449 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 6 Apr 2023 11:04:16 -0700 Subject: [PATCH 284/375] Multiple modules: decouple GVL IDs from storage access control (#9736) * Core: allow restriction of cookies / localStorage through `bidderSettings.*.storageAllowed` * Add test cases * Remove gvlid param from storage manager logic * Refactor every invocation of `getStorageManager` * GVL ID registry * Refactor gdprEnforcement gvlid lookup * fix lint * Remove empty file * Undo https://github.com/prebid/Prebid.js/pull/9728 for realVu * Fix typo --- modules/adagioBidAdapter.js | 2 +- modules/adkernelAdnAnalyticsAdapter.js | 6 +- modules/admixerIdSystem.js | 6 +- modules/adnuntiusBidAdapter.js | 2 +- modules/adqueryBidAdapter.js | 2 +- modules/adqueryIdSystem.js | 3 +- modules/adriverIdSystem.js | 5 +- modules/airgridRtdProvider.js | 6 +- modules/akamaiDapRtdProvider.js | 3 +- modules/amxBidAdapter.js | 18 +- modules/appnexusBidAdapter.js | 2 +- modules/apstreamBidAdapter.js | 2 +- modules/atsAnalyticsAdapter.js | 6 +- modules/blueconicRtdProvider.js | 3 +- modules/browsiRtdProvider.js | 6 +- modules/byDataAnalyticsAdapter.js | 9 +- modules/criteoBidAdapter.js | 2 +- modules/criteoIdSystem.js | 5 +- modules/czechAdIdSystem.js | 5 +- modules/dacIdSystem.js | 7 +- modules/deepintentDpesIdSystem.js | 5 +- modules/discoveryBidAdapter.js | 2 +- modules/fintezaAnalyticsAdapter.js | 8 +- modules/ftrackIdSystem.js | 12 +- modules/gdprEnforcement.js | 124 ++++---- modules/glimpseBidAdapter.js | 5 +- modules/gnetBidAdapter.js | 3 +- modules/gravitoIdSystem.js | 8 +- modules/gridBidAdapter.js | 2 +- modules/growthCodeAnalyticsAdapter.js | 5 +- modules/growthCodeIdSystem.js | 5 +- modules/hadronAnalyticsAdapter.js | 8 +- modules/hadronIdSystem.js | 4 +- modules/hadronRtdProvider.js | 6 +- modules/id5IdSystem.js | 3 +- modules/idWardRtdProvider.js | 3 +- modules/identityLinkIdSystem.js | 7 +- modules/idxIdSystem.js | 5 +- modules/imRtdProvider.js | 4 +- modules/imuIdSystem.js | 9 +- modules/insticatorBidAdapter.js | 2 +- modules/intentIqIdSystem.js | 5 +- modules/invibesBidAdapter.js | 2 +- modules/ixBidAdapter.js | 2 +- modules/kargoBidAdapter.js | 2 +- modules/kueezRtbBidAdapter.js | 2 +- modules/liveIntentIdSystem.js | 5 +- modules/lotamePanoramaIdSystem.js | 5 +- modules/magniteAnalyticsAdapter.js | 31 +- modules/mediafuseBidAdapter.js | 2 +- modules/mediagoBidAdapter.js | 2 +- modules/merkleIdSystem.js | 3 +- modules/mgidBidAdapter.js | 2 +- modules/mgidRtdProvider.js | 4 +- modules/minutemediaplusBidAdapter.js | 2 +- modules/mwOpenLinkIdSystem.js | 5 +- modules/naveggIdSystem.js | 5 +- modules/nexx360BidAdapter.js | 1 - modules/nobidBidAdapter.js | 2 +- modules/novatiqIdSystem.js | 9 +- modules/onetagBidAdapter.js | 2 +- modules/parrableIdSystem.js | 6 +- modules/permutiveRtdProvider.js | 3 +- modules/prebidmanagerAnalyticsAdapter.js | 5 +- modules/pubCommonId.js | 9 +- modules/publinkIdSystem.js | 3 +- modules/pubwiseAnalyticsAdapter.js | 8 +- modules/quantcastBidAdapter.js | 2 +- modules/quantcastIdSystem.js | 8 +- modules/realvuAnalyticsAdapter.js | 9 +- modules/roxotAnalyticsAdapter.js | 7 +- modules/rtdModule/index.js | 3 + modules/rubiconAnalyticsAdapter.js | 5 +- modules/sharedIdSystem.js | 3 +- modules/sigmoidAnalyticsAdapter.js | 6 +- modules/staqAnalyticsAdapter.js | 8 +- modules/taboolaBidAdapter.js | 2 +- modules/teadsBidAdapter.js | 2 +- modules/teadsIdSystem.js | 3 +- modules/tripleliftBidAdapter.js | 2 +- modules/trustpidSystem.js | 5 +- modules/uid2IdSystem.js | 5 +- modules/userId/index.js | 3 + modules/validationFpdModule/index.js | 5 +- modules/vidazooBidAdapter.js | 2 +- modules/weboramaRtdProvider.js | 4 +- modules/yuktamediaAnalyticsAdapter.js | 6 +- modules/zeotapIdPlusIdSystem.js | 5 +- src/activities/modules.js | 5 + src/adapterManager.js | 5 +- src/adapters/bidderFactory.js | 2 +- src/consentHandler.js | 42 +++ src/storageManager.js | 80 ++--- test/spec/modules/admixerIdSystem_spec.js | 3 - test/spec/modules/adnuntiusBidAdapter_spec.js | 12 +- test/spec/modules/atsAnalyticsAdapter_spec.js | 4 +- .../spec/modules/datablocksBidAdapter_spec.js | 1 - test/spec/modules/gdprEnforcement_spec.js | 298 ++++++++---------- test/spec/modules/growthCodeIdSystem_spec.js | 3 +- .../spec/modules/identityLinkIdSystem_spec.js | 4 +- test/spec/modules/publinkIdSystem_spec.js | 5 +- test/spec/modules/realTimeDataModule_spec.js | 22 ++ test/spec/modules/relaidoBidAdapter_spec.js | 10 +- test/spec/modules/userId_spec.js | 17 +- .../spec/modules/zeotapIdPlusIdSystem_spec.js | 5 +- test/spec/unit/core/adapterManager_spec.js | 21 ++ test/spec/unit/core/consentHandler_spec.js | 30 +- test/spec/unit/core/storageManager_spec.js | 52 +-- 108 files changed, 685 insertions(+), 488 deletions(-) create mode 100644 src/activities/modules.js diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 3d3c1df0e45..86021d2a90c 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -43,7 +43,7 @@ const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; const GVLID = 617; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; const MAX_SESS_DURATION = 30 * 60 * 1000; const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp'; diff --git a/modules/adkernelAdnAnalyticsAdapter.js b/modules/adkernelAdnAnalyticsAdapter.js index 901c0d2fd98..48897f8516b 100644 --- a/modules/adkernelAdnAnalyticsAdapter.js +++ b/modules/adkernelAdnAnalyticsAdapter.js @@ -5,12 +5,14 @@ import { logError, parseUrl, _each } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {config} from '../src/config.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +const MODULE_CODE = 'adkernelAdn'; const GVLID = 14; const ANALYTICS_VERSION = '1.0.2'; const DEFAULT_QUEUE_TIMEOUT = 4000; const DEFAULT_HOST = 'tag.adkernel.com'; -const storageObj = getStorageManager({gvlid: GVLID}); +const storageObj = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const ADK_HB_EVENTS = { AUCTION_INIT: 'auctionInit', @@ -104,7 +106,7 @@ analyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, - code: 'adkernelAdn', + code: MODULE_CODE, gvlid: GVLID }); diff --git a/modules/admixerIdSystem.js b/modules/admixerIdSystem.js index 49ffe4f4680..7fbebecfc12 100644 --- a/modules/admixerIdSystem.js +++ b/modules/admixerIdSystem.js @@ -9,8 +9,10 @@ import { logError, logInfo } from '../src/utils.js' import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const NAME = 'admixerId'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: NAME}); /** @type {Submodule} */ export const admixerIdSubmodule = { @@ -18,7 +20,7 @@ export const admixerIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'admixerId', + name: NAME, /** * used to specify vendor id * @type {number} diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index ea3b723b316..c3cd1963248 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -55,7 +55,7 @@ const getSegmentsFromOrtb = function (ortb2) { // } const handleMeta = function () { - const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }) + const storage = getStorageManager({ bidderCode: BIDDER_CODE }) let adnMeta = null if (storage.localStorageIsEnabled()) { adnMeta = JSON.parse(storage.getDataFromLocalStorage('adn.metaData')) diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index be5ca4b1057..63e0c7dbe22 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -11,7 +11,7 @@ const ADQUERY_USER_SYNC_DOMAIN = ADQUERY_BIDDER_DOMAIN_PROTOCOL + '://' + ADQUER const ADQUERY_DEFAULT_CURRENCY = 'PLN'; const ADQUERY_NET_REVENUE = true; const ADQUERY_TTL = 360; -const storage = getStorageManager({gvlid: ADQUERY_GVLID, bidderCode: ADQUERY_BIDDER_CODE}); +const storage = getStorageManager({bidderCode: ADQUERY_BIDDER_CODE}); /** @type {BidderSpec} */ export const spec = { diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js index d9552a470d0..5171802caba 100644 --- a/modules/adqueryIdSystem.js +++ b/modules/adqueryIdSystem.js @@ -9,11 +9,12 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import { isFn, isStr, isPlainObject, logError } from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'qid'; const AU_GVLID = 902; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'qid'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'qid'}); /** * Param or default. diff --git a/modules/adriverIdSystem.js b/modules/adriverIdSystem.js index fb8ce99ec16..b3ab00350ea 100644 --- a/modules/adriverIdSystem.js +++ b/modules/adriverIdSystem.js @@ -8,11 +8,12 @@ import { logError, isPlainObject } from '../src/utils.js' import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'adriverId'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const adriverIdSubmodule = { diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 174502d1757..c94a71eecde 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -12,7 +12,8 @@ import { deepAccess, } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'airgrid'; @@ -20,7 +21,7 @@ const AG_TCF_ID = 782; export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids'; export const storage = getStorageManager({ - gvlid: AG_TCF_ID, + moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME, }); @@ -154,6 +155,7 @@ export const airgridSubmodule = { name: SUBMODULE_NAME, init: init, getBidRequestData: passAudiencesToBidders, + gvlid: AG_TCF_ID }; submodule(MODULE_NAME, airgridSubmodule); diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index 1c2af70d737..f0bb7eb3a6c 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -10,6 +10,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; @@ -23,7 +24,7 @@ export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds export const DAP_MAX_RETRY_TOKENIZE = 1; export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' -export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); let dapRetryTokenize = 0; /** diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 65c935db5f5..cc2cd120779 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -1,21 +1,21 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import { - parseUrl, - deepAccess, _each, + deepAccess, formatQS, getUniqueIdentifierStr, - triggerPixel, + isArray, isFn, logError, - isArray, + parseUrl, + triggerPixel, } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {config} from '../src/config.js'; +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'amx'; -const storage = getStorageManager({ gvlid: 737, bidderCode: BIDDER_CODE }); +const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; const VERSION = 'pba1.3.2'; diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 69afec3d1b3..8f499e1e31e 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -94,7 +94,7 @@ const SCRIPT_TAG_START = ' { return storage.getCookie('czaid') || storage.getDataFromLocalStorage('czaid') } diff --git a/modules/dacIdSystem.js b/modules/dacIdSystem.js index 856e1976bb1..5adca074c87 100644 --- a/modules/dacIdSystem.js +++ b/modules/dacIdSystem.js @@ -19,8 +19,9 @@ import { import { getStorageManager } from '../src/storageManager.js'; - -export const storage = getStorageManager(); +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +const MODULE_NAME = 'dacId'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export const FUUID_COOKIE_NAME = '_a1_f'; export const AONEID_COOKIE_NAME = '_a1_d'; @@ -116,7 +117,7 @@ export const dacIdSystemSubmodule = { * used to link submodule with config * @type {string} */ - name: 'dacId', + name: MODULE_NAME, /** * decode the stored id value for passing to bid requests diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js index 43c7af1b3cc..f2b50d535eb 100644 --- a/modules/deepintentDpesIdSystem.js +++ b/modules/deepintentDpesIdSystem.js @@ -6,10 +6,11 @@ */ import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'deepintentId'; -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const deepintentDpesSubmodule = { diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 7930efc4fa8..a0f864d529f 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -6,7 +6,7 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js'; const BIDDER_CODE = 'discovery'; const ENDPOINT_URL = 'https://rtb-jp.mediago.io/api/bid?tn='; const TIME_TO_LIVE = 500; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); let globals = {}; let itemMaps = {}; const MEDIATYPE = [BANNER, NATIVE]; diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js index 88c5f85f15d..be661c96061 100644 --- a/modules/fintezaAnalyticsAdapter.js +++ b/modules/fintezaAnalyticsAdapter.js @@ -2,10 +2,12 @@ import { parseUrl, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import CONSTANTS from '../src/constants.json'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'finteza'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const ANALYTICS_TYPE = 'endpoint'; const FINTEZA_HOST = 'https://content.mql5.com/tr'; @@ -439,7 +441,7 @@ fntzAnalyticsAdapter.enableAnalytics = function (config) { adapterManager.registerAnalyticsAdapter({ adapter: fntzAnalyticsAdapter, - code: 'finteza' + code: MODULE_CODE, }); export default fntzAnalyticsAdapter; diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 244807a3164..5f09a315b34 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -6,19 +6,19 @@ */ import * as utils from '../src/utils.js'; -import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { uspDataHandler } from '../src/adapterManager.js'; -import { loadExternalScript } from '../src/adloader.js'; +import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {uspDataHandler} from '../src/adapterManager.js'; +import {loadExternalScript} from '../src/adloader.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'ftrackId'; const LOG_PREFIX = 'FTRACK - '; const LOCAL_STORAGE_EXP_DAYS = 30; -const VENDOR_ID = null; const LOCAL_STORAGE = 'html5'; const FTRACK_STORAGE_NAME = 'ftrackId'; const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`; -const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME}); +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); let consentInfo = { gdpr: { diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 9553ad0586a..798dfc848da 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -11,7 +11,13 @@ import {getHook} from '../src/hook.js'; import {validateStorageEnforcement} from '../src/storageManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; -import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js'; +import { + MODULE_TYPE_ANALYTICS, + MODULE_TYPE_BIDDER, + MODULE_TYPE_CORE, MODULE_TYPE_RTD, + MODULE_TYPE_UID +} from '../src/activities/modules.js'; export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; @@ -49,77 +55,56 @@ const analyticsBlocked = []; let hooksAdded = false; let strictStorageEnforcement = false; -// Helps in stubbing these functions in unit tests. -export const internal = { - getGvlidForBidAdapter, - getGvlidForUserIdModule, - getGvlidForAnalyticsAdapter -}; +const GVLID_LOOKUP_PRIORITY = [ + MODULE_TYPE_BIDDER, + MODULE_TYPE_UID, + MODULE_TYPE_ANALYTICS, + MODULE_TYPE_RTD +]; /** - * Returns GVL ID for a Bid adapter / an USERID submodule / an Analytics adapter. - * If modules of different types have the same moduleCode: For example, 'appnexus' is the code for both Bid adapter and Analytics adapter, - * then, we assume that their GVL IDs are same. This function first checks if GVL ID is defined for a Bid adapter, if not found, tries to find User ID - * submodule's GVL ID, if not found, tries to find Analytics adapter's GVL ID. In this process, as soon as it finds a GVL ID, it returns it - * without going to the next check. - * @param {{string|Object}} - module - * @return {number} - GVL ID + * Retrieve a module's GVL ID. */ -export function getGvlid(module, ...args) { - let gvlid = null; - if (module) { +export function getGvlid(moduleType, moduleName, fallbackFn) { + if (moduleName) { // Check user defined GVL Mapping in pbjs.setConfig() const gvlMapping = config.getConfig('gvlMapping'); - // For USER ID Module, we pass the submodule object itself as the "module" parameter, this check is required to grab the module code - const moduleCode = typeof module === 'string' ? module : module.name; - // Return GVL ID from user defined gvlMapping - if (gvlMapping && gvlMapping[moduleCode]) { - gvlid = gvlMapping[moduleCode]; - return gvlid; - } - - gvlid = internal.getGvlidForBidAdapter(moduleCode) || internal.getGvlidForUserIdModule(module) || internal.getGvlidForAnalyticsAdapter(moduleCode, ...args); - } - return gvlid; -} - -/** - * Returns GVL ID for a bid adapter. If the adapter does not have an associated GVL ID, it returns 'null'. - * @param {string=} bidderCode - The 'code' property of the Bidder spec. - * @return {number} GVL ID - */ -function getGvlidForBidAdapter(bidderCode) { - let gvlid = null; - bidderCode = bidderCode || config.getCurrentBidder(); - if (bidderCode) { - const bidder = adapterManager.getBidAdapter(bidderCode); - if (bidder && bidder.getSpec) { - gvlid = bidder.getSpec().gvlid; + if (gvlMapping && gvlMapping[moduleName]) { + return gvlMapping[moduleName]; + } else if (moduleType === MODULE_TYPE_CORE) { + return VENDORLESS_GVLID; + } else { + let {gvlid, modules} = GDPR_GVLIDS.get(moduleName); + if (gvlid == null && Object.keys(modules).length > 0) { + // this behavior is for backwards compatibility; if multiple modules with the same + // name declare different GVL IDs, pick the bidder's first, then userId, then analytics + for (const type of GVLID_LOOKUP_PRIORITY) { + if (modules.hasOwnProperty(type)) { + gvlid = modules[type]; + if (type !== moduleType && !fallbackFn) { + logWarn(`Multiple GVL IDs found for module '${moduleName}'; using the ${type} module's ID (${gvlid}) instead of the ${moduleType}'s ID (${modules[moduleType]})`) + } + break; + } + } + } + if (gvlid == null && fallbackFn) { + gvlid = fallbackFn(); + } + return gvlid || null; } } - return gvlid; -} - -/** - * Returns GVL ID for an userId submodule. If an userId submodules does not have an associated GVL ID, it returns 'null'. - * @param {Object} userIdModule - * @return {number} GVL ID - */ -function getGvlidForUserIdModule(userIdModule) { - return (typeof userIdModule === 'object' ? userIdModule.gvlid : null); + return null; } /** - * Returns GVL ID for an analytics adapter. If an analytics adapter does not have an associated GVL ID, it returns 'null'. - * @param {string} code - 'provider' property on the analytics adapter config - * @param {{}} config - analytics configuration object - * @return {number} GVL ID + * Retrieve GVL IDs that are dynamically set on analytics adapters. */ -function getGvlidForAnalyticsAdapter(code, config) { +export function getGvlidFromAnalyticsAdapter(code, config) { const adapter = adapterManager.getAnalyticsAdapter(code); - return adapter?.gvlid || ((gvlid) => { + return ((gvlid) => { if (typeof gvlid !== 'function') return gvlid; try { return gvlid.call(adapter.adapter, config); @@ -185,30 +170,33 @@ export function validateRules(rule, consentData, currentModule, gvlId) { /** * This hook checks whether module has permission to access device or not. Device access include cookie and local storage + * * @param {Function} fn reference to original function (used by hook logic) - * @param {Number=} gvlid gvlid of the module + * @param {string} moduleType type of the module * @param {string=} moduleName name of the module * @param result + * @param validate */ -export function deviceAccessHook(fn, gvlid, moduleName, result, {validate = validateRules} = {}) { +export function deviceAccessHook(fn, moduleType, moduleName, result, {validate = validateRules} = {}) { result = Object.assign({}, { hasEnforcementHook: true }); if (!hasDeviceAccess()) { logWarn('Device access is disabled by Publisher'); result.valid = false; - } else if (gvlid === VENDORLESS_GVLID && !strictStorageEnforcement) { + } else if (moduleType === MODULE_TYPE_CORE && !strictStorageEnforcement) { // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set result.valid = true; } else { const consentData = gdprDataHandler.getConsentData(); + let gvlid; if (shouldEnforce(consentData, 1, moduleName)) { const curBidder = config.getCurrentBidder(); // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder if (curBidder && (curBidder !== moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { - gvlid = getGvlid(curBidder); + gvlid = getGvlid(moduleType, curBidder); } else { - gvlid = getGvlid(moduleName) || gvlid; + gvlid = getGvlid(moduleType, moduleName) } const curModule = moduleName || curBidder; let isAllowed = validate(purpose1Rule, consentData, curModule, gvlid,); @@ -223,7 +211,7 @@ export function deviceAccessHook(fn, gvlid, moduleName, result, {validate = vali result.valid = true; } } - fn.call(this, gvlid, moduleName, result); + fn.call(this, moduleType, moduleName, result); } /** @@ -235,7 +223,7 @@ export function userSyncHook(fn, ...args) { const consentData = gdprDataHandler.getConsentData(); const curBidder = config.getCurrentBidder(); if (shouldEnforce(consentData, 1, curBidder)) { - const gvlid = getGvlid(curBidder); + const gvlid = getGvlid(MODULE_TYPE_BIDDER, curBidder); let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); if (isAllowed) { fn.call(this, ...args); @@ -257,8 +245,8 @@ export function userSyncHook(fn, ...args) { export function userIdHook(fn, submodules, consentData) { if (shouldEnforce(consentData, 1, 'User ID')) { let userIdModules = submodules.map((submodule) => { - const gvlid = getGvlid(submodule.submodule); const moduleName = submodule.submodule.name; + const gvlid = getGvlid(MODULE_TYPE_UID, moduleName); let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); if (isAllowed) { return submodule; @@ -286,7 +274,7 @@ export function makeBidRequestsHook(fn, adUnits, ...args) { adUnits.forEach(adUnit => { adUnit.bids = adUnit.bids.filter(bid => { const currBidder = bid.bidder; - const gvlId = getGvlid(currBidder); + const gvlId = getGvlid(MODULE_TYPE_BIDDER, currBidder); if (includes(biddersBlocked, currBidder)) return false; const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId); if (!isAllowed) { @@ -316,7 +304,7 @@ export function enableAnalyticsHook(fn, config) { } config = config.filter(conf => { const analyticsAdapterCode = conf.provider; - const gvlid = getGvlid(analyticsAdapterCode, conf); + const gvlid = getGvlid(MODULE_TYPE_ANALYTICS, analyticsAdapterCode, () => getGvlidFromAnalyticsAdapter(analyticsAdapterCode, conf)); const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); if (!isAllowed) { analyticsBlocked.push(analyticsAdapterCode); diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js index bbb4dbb30cd..3847a72368a 100644 --- a/modules/glimpseBidAdapter.js +++ b/modules/glimpseBidAdapter.js @@ -12,10 +12,7 @@ import { const GVLID = 1012; const BIDDER_CODE = 'glimpse'; -const storageManager = getStorageManager({ - gvlid: GVLID, - bidderCode: BIDDER_CODE, -}); +const storageManager = getStorageManager({bidderCode: BIDDER_CODE}); const ENDPOINT = 'https://market.glimpsevault.io/public/v1/prebid'; const LOCAL_STORAGE_KEY = { vault: { diff --git a/modules/gnetBidAdapter.js b/modules/gnetBidAdapter.js index 8bab043d0db..0b02a29c0d4 100644 --- a/modules/gnetBidAdapter.js +++ b/modules/gnetBidAdapter.js @@ -4,10 +4,9 @@ import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; -const storage = getStorageManager(); - const BIDDER_CODE = 'gnet'; const ENDPOINT = 'https://service.gnetrtb.com/api'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/gravitoIdSystem.js b/modules/gravitoIdSystem.js index 809263a1c68..aa25ea7db2c 100644 --- a/modules/gravitoIdSystem.js +++ b/modules/gravitoIdSystem.js @@ -6,9 +6,11 @@ */ import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const MODULE_NAME = 'gravitompId'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export const cookieKey = 'gravitompId'; @@ -17,7 +19,7 @@ export const gravitoIdSystemSubmodule = { * used to link submodule with config * @type {string} */ - name: 'gravitompId', + name: MODULE_NAME, /** * performs action to obtain id diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 7da2c778c43..36cfb4bedc3 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -31,7 +31,7 @@ const TIME_TO_LIVE = 360; const USER_ID_KEY = 'tmguid'; const GVLID = 686; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', diff --git a/modules/growthCodeAnalyticsAdapter.js b/modules/growthCodeAnalyticsAdapter.js index 1f11b891139..a2ab4ddbfac 100644 --- a/modules/growthCodeAnalyticsAdapter.js +++ b/modules/growthCodeAnalyticsAdapter.js @@ -6,15 +6,16 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {logError, logInfo} from '../src/utils.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const MODULE_NAME = 'growthCodeAnalytics'; const DEFAULT_PID = 'INVALID_PID' const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb/analytics' -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}); let sessionId = utils.generateUUID(); diff --git a/modules/growthCodeIdSystem.js b/modules/growthCodeIdSystem.js index edd7cd33012..739cea07489 100644 --- a/modules/growthCodeIdSystem.js +++ b/modules/growthCodeIdSystem.js @@ -8,14 +8,15 @@ import {logError, logInfo, tryAppendQueryString} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const GCID_EXPIRY = 7; const MODULE_NAME = 'growthCodeId'; const GC_DATA_KEY = '_gc_data'; const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb?' -export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); /** * Read GrowthCode data from cookie or local storage diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index 52829cf754d..e4c09c5b6c9 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -3,8 +3,9 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; /** * hadronAnalyticsAdapter.js - Audigent Hadron Analytics Adapter @@ -14,8 +15,9 @@ const HADRON_ANALYTICS_URL = 'https://analytics.hadron.ad.gt/api/v1/analytics'; const HADRONID_ANALYTICS_VER = 'pbadgt0'; const DEFAULT_PARTNER_ID = 0; const AU_GVLID = 561; +const MODULE_CODE = 'hadronAnalytics'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); var viewId = utils.generateUUID(); @@ -191,7 +193,7 @@ function sendEvent(event) { adapterManager.registerAnalyticsAdapter({ adapter: hadronAnalyticsAdapter, - code: 'hadronAnalytics', + code: MODULE_CODE, gvlid: AU_GVLID }); diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index a75c03ee1c4..596bf9611e6 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -9,13 +9,14 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isFn, isStr, isPlainObject, logError, logInfo} from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const HADRONID_LOCAL_NAME = 'auHadronId'; const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'hadron'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'hadron'}); /** * Param or default. @@ -48,6 +49,7 @@ export const hadronIdSubmodule = { * @type {string} */ name: MODULE_NAME, + gvlid: AU_GVLID, /** * decode the stored id value for passing to bid requests * @function diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 7a0299fc427..6fb982815c1 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -12,6 +12,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isFn, isStr, isArray, deepEqual, isPlainObject, logError, logInfo} from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const LOG_PREFIX = 'User ID - HadronRtdProvider submodule: '; const MODULE_NAME = 'realTimeData'; @@ -21,7 +22,7 @@ const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebi const HADRON_SEGMENT_URL = 'https://id.hadron.ad.gt/api/v1/rtd'; export const HALOID_LOCAL_NAME = 'auHadronId'; export const RTD_LOCAL_NAME = 'auHadronRtd'; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** * @param {string} url @@ -251,7 +252,8 @@ function init(provider, userConsent) { export const hadronSubmodule = { name: SUBMODULE_NAME, getBidRequestData: getRealTimeData, - init: init + init: init, + gvlid: AU_GVLID, }; submodule(MODULE_NAME, hadronSubmodule); diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index c26ba3662e1..b7ff836a7e6 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -20,6 +20,7 @@ import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; import {uspDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'id5Id'; const GVLID = 131; @@ -34,7 +35,7 @@ const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid'; // cookie in the array is the most preferred to use const LEGACY_COOKIE_NAMES = ['pbjs-id5id', 'id5id.1st', 'id5id']; -export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const id5IdSubmodule = { diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js index 9678739672d..29dda216fdc 100644 --- a/modules/idWardRtdProvider.js +++ b/modules/idWardRtdProvider.js @@ -8,11 +8,12 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'idWard'; -export const storage = getStorageManager({moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** * Add real-time data & merge segments. * @param ortb2 object to merge into diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index df7b03b4e6e..ab10288f38f 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -9,8 +9,11 @@ import * as utils from '../src/utils.js' import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const MODULE_NAME = 'identityLink'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const identityLinkSubmodule = { @@ -18,7 +21,7 @@ export const identityLinkSubmodule = { * used to link submodule with config * @type {string} */ - name: 'identityLink', + name: MODULE_NAME, /** * used to specify vendor id * @type {number} diff --git a/modules/idxIdSystem.js b/modules/idxIdSystem.js index 908edad4c04..3c00bbde34c 100644 --- a/modules/idxIdSystem.js +++ b/modules/idxIdSystem.js @@ -6,11 +6,12 @@ */ import { isStr, isPlainObject, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const IDX_MODULE_NAME = 'idx'; const IDX_COOKIE_NAME = '_idx'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: IDX_MODULE_NAME}); function readIDxFromCookie() { return storage.cookiesAreEnabled ? storage.getCookie(IDX_COOKIE_NAME) : null; diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index bc01896d062..26d49c49f8c 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -18,16 +18,18 @@ import { isFn } from '../src/utils.js' import {submodule} from '../src/hook.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; export const imUidLocalName = '__im_uid'; export const imVidCookieName = '_im_vid'; export const imRtdLocalName = '__im_sids'; -export const storage = getStorageManager(); const submoduleName = 'im'; const segmentsMaxAge = 3600000; // 1 hour (30 * 60 * 1000) const uidMaxAge = 1800000; // 30 minites (30 * 60 * 1000) const vidMaxAge = 97200000000; // 37 months ((365 * 3 + 30) * 24 * 60 * 60 * 1000) +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: submoduleName}); + function setImDataInCookie(value) { storage.setCookie( imVidCookieName, diff --git a/modules/imuIdSystem.js b/modules/imuIdSystem.js index 41ff95b6702..898f32b27b0 100644 --- a/modules/imuIdSystem.js +++ b/modules/imuIdSystem.js @@ -8,9 +8,12 @@ import { timestamp, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js' import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const MODULE_NAME = 'imuid'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export const storageKey = '__im_uid'; export const storagePpKey = '__im_ppid'; @@ -112,7 +115,7 @@ export const imuIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'imuid', + name: MODULE_NAME, /** * decode the stored id value for passing to bid requests * @function diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 150e9d3c5c2..46ff17d2a5a 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -12,7 +12,7 @@ const USER_ID_COOKIE_EXP = 2592000000; // 30 days const BID_TTL = 300; // 5 minutes const GVLID = 910; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); config.setDefaults({ insticator: { diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 563435dee65..b16624ac368 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -8,7 +8,8 @@ import { logError, logInfo } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const PCID_EXPIRY = 365; @@ -16,7 +17,7 @@ const MODULE_NAME = 'intentIqId'; export const FIRST_PARTY_KEY = '_iiq_fdata'; export var FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); const INVALID_ID = 'INVALID_ID'; diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index c3e5cf6cca8..b2444043c22 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -17,7 +17,7 @@ const CONSTANTS = { DISABLE_USER_SYNC: true }; -const storage = getStorageManager({gvlid: CONSTANTS.INVIBES_VENDOR_ID, bidderCode: CONSTANTS.BIDDER_CODE}); +const storage = getStorageManager({bidderCode: CONSTANTS.BIDDER_CODE}); export const spec = { code: CONSTANTS.BIDDER_CODE, diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index a00fd90506a..ee07c34fb95 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -102,7 +102,7 @@ const VIDEO_PARAMS_ALLOW_LIST = [ const LOCAL_STORAGE_KEY = 'ixdiag'; export const LOCAL_STORAGE_FEATURE_TOGGLES_KEY = `${BIDDER_CODE}_features`; let hasRegisteredHandler = false; -export const storage = getStorageManager({ gvlid: GLOBAL_VENDOR_ID, bidderCode: BIDDER_CODE }); +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const FEATURE_TOGGLES = { featureToggles: {}, isFeatureEnabled: function (ft) { diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index b3d5bc2af64..0c647095e24 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -11,7 +11,7 @@ const PREBID_VERSION = '$prebid.version$'; const SYNC_COUNT = 5; const GVLID = 972; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); let sessionId, lastPageUrl, diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 26c0d871a12..3bd468a581f 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -23,7 +23,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 4fd2a80afd0..b594c717c8b 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -9,11 +9,12 @@ import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const EVENTS_TOPIC = 'pre_lips' const MODULE_NAME = 'liveIntentId'; -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); const defaultRequestedAttributes = {'nonId': true} const calls = { ajaxGet: (url, onSuccess, onError, timeout) => { diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 883c931824b..02b01b8bd9d 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -16,8 +16,9 @@ import { } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import { uspDataHandler } from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const KEY_ID = 'panoramaId'; const KEY_EXPIRY = `${KEY_ID}_expiry`; @@ -31,7 +32,7 @@ const GVLID = 95; const ID_HOST = 'id.crwdcntrl.net'; const ID_HOST_COOKIELESS = 'c.ltmsphrcl.net'; -export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); let cookieDomain; /** diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 9d437c0b246..afe73c097fb 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -1,14 +1,33 @@ -import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, deepSetValue, deepClone, logInfo, isGptPubadsDefined } from '../src/utils.js'; +import { + debugTurnedOn, + deepAccess, + deepClone, + deepSetValue, + generateUUID, + getWindowLocation, + isAdUnitCodeMatchingSlot, + isEmpty, + isGptPubadsDefined, + isNumber, + logError, + logInfo, + logWarn, + mergeDeep, + parseQS, + parseUrl, + pick +} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const RUBICON_GVL_ID = 52; -export const storage = getStorageManager({ gvlid: RUBICON_GVL_ID, moduleName: 'magnite' }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'magnite' }); const COOKIE_NAME = 'mgniSession'; const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index e61c2e65c39..87347ca8d27 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -62,7 +62,7 @@ const SCRIPT_TAG_START = '= 0) { diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 76043b71c64..7bbc435cb27 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -5,10 +5,11 @@ import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const RUBICON_GVL_ID = 52; -export const storage = getStorageManager({gvlid: RUBICON_GVL_ID, moduleName: 'rubicon'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'rubicon'}); const COOKIE_NAME = 'rpaSession'; const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 860c9d7483d..69c19929975 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -10,8 +10,9 @@ import {submodule} from '../src/hook.js'; import { coppaDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager({moduleName: 'pubCommonId', gvlid: VENDORLESS_GVLID}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'pubCommonId'}); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const OPTOUT_NAME = '_pubcid_optout'; diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js index dc386813978..18e1e20e3e3 100644 --- a/modules/sigmoidAnalyticsAdapter.js +++ b/modules/sigmoidAnalyticsAdapter.js @@ -6,8 +6,10 @@ import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {generateUUID, logError, logInfo} from '../src/utils.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'sigmoid'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const url = 'https://kinesis.us-east-1.amazonaws.com/'; const analyticsType = 'endpoint'; @@ -285,7 +287,7 @@ function pushEvent(eventType, args) { adapterManager.registerAnalyticsAdapter({ adapter: sigmoidAdapter, - code: 'sigmoid' + code: MODULE_CODE, }); export default sigmoidAdapter; diff --git a/modules/staqAnalyticsAdapter.js b/modules/staqAnalyticsAdapter.js index 13996adfb7e..c1aaa727af5 100644 --- a/modules/staqAnalyticsAdapter.js +++ b/modules/staqAnalyticsAdapter.js @@ -4,9 +4,11 @@ import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { ajax } from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storageObj = getStorageManager(); +const MODULE_CODE = 'staq'; +const storageObj = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const ANALYTICS_VERSION = '1.0.0'; const DEFAULT_QUEUE_TIMEOUT = 4000; @@ -117,7 +119,7 @@ analyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, - code: 'staq' + code: MODULE_CODE, }); export default analyticsAdapter; diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index a833d8ec552..49ca8da71c8 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -24,7 +24,7 @@ const COOKIE_KEY = 'trc_cookie_storage'; * 4. new user set it to 0 */ export const userData = { - storageManager: getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}), + storageManager: getStorageManager({bidderCode: BIDDER_CODE}), getUserId: () => { const {getFromLocalStorage, getFromCookie, getFromTRC} = userData; diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 56b21d0d4cf..f750af0f64d 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -12,7 +12,7 @@ const gdprStatus = { CMP_NOT_FOUND_OR_ERROR: 22 }; const FP_TEADS_ID_COOKIE_NAME = '_tfpvi'; -export const storage = getStorageManager({gvlid: GVL_ID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, diff --git a/modules/teadsIdSystem.js b/modules/teadsIdSystem.js index c18f8fb76ac..b4067bf75c3 100644 --- a/modules/teadsIdSystem.js +++ b/modules/teadsIdSystem.js @@ -10,6 +10,7 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {uspDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'teadsId'; const GVL_ID = 132; @@ -28,7 +29,7 @@ export const gdprReason = { GDPR_APPLIES_PUBLISHER_CLASSIC: 120, }; -export const storage = getStorageManager({gvlid: GVL_ID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const teadsIdSubmodule = { diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 1f654d34c6a..038cd7d757d 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -11,7 +11,7 @@ const BANNER_TIME_TO_LIVE = 300; const VIDEO_TIME_TO_LIVE = 3600; let gdprApplies = true; let consentString = null; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const tripleliftAdapterSpec = { gvlid: GVLID, diff --git a/modules/trustpidSystem.js b/modules/trustpidSystem.js index cb61ffcc8b5..1d971b3c813 100644 --- a/modules/trustpidSystem.js +++ b/modules/trustpidSystem.js @@ -6,13 +6,14 @@ */ import { logInfo } from '../src/utils.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'trustpid'; const LOG_PREFIX = 'Trustpid module' let mnoDomain = ''; -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** * Handle an event for an iframe. diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 9fd75f12591..2ffab8cc68f 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -8,7 +8,8 @@ import { logInfo } from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'uid2'; const MODULE_REVISION = `1.0`; @@ -24,7 +25,7 @@ const UID2_PROD_URL = 'https://prod.uidapi.com'; const UID2_BASE_URL = UID2_PROD_URL; function getStorage() { - return getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); + return getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); } function createLogInfo(prefix) { diff --git a/modules/userId/index.js b/modules/userId/index.js index d4a8679f0ca..d16d341fd2d 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -157,6 +157,8 @@ import {hasPurpose1Consent} from '../../src/utils/gpdr.js'; import {registerOrtbProcessor, REQUEST} from '../../src/pbjsORTB.js'; import {newMetrics, timedAuctionHook, useMetrics} from '../../src/utils/perfMetrics.js'; import {findRootDomain} from '../../src/fpd/rootDomain.js'; +import {GDPR_GVLIDS} from '../../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../../src/activities/modules.js'; const MODULE_NAME = 'User ID'; const COOKIE = STORAGE_TYPE_COOKIES; @@ -1019,6 +1021,7 @@ export function requestDataDeletion(next, ...args) { export function attachIdSystem(submodule) { if (!find(submoduleRegistry, i => i.name === submodule.name)) { submoduleRegistry.push(submodule); + GDPR_GVLIDS.register(MODULE_TYPE_UID, submodule.name, submodule.gvlid) updateSubmodules(); // TODO: a test case wants this to work even if called after init (the setConfig({userId})) // so we trigger a refresh. But is that even possible outside of tests? diff --git a/modules/validationFpdModule/index.js b/modules/validationFpdModule/index.js index 8771e50b156..70af9d30ec3 100644 --- a/modules/validationFpdModule/index.js +++ b/modules/validationFpdModule/index.js @@ -5,9 +5,10 @@ import {deepAccess, isEmpty, isNumber, logWarn} from '../../src/utils.js'; import {ORTB_MAP} from './config.js'; import {submodule} from '../../src/hook.js'; -import {getStorageManager} from '../../src/storageManager.js'; +import {getCoreStorageManager} from '../../src/storageManager.js'; -const STORAGE = getStorageManager(); +// TODO: do FPD modules need their own namespace? +const STORAGE = getCoreStorageManager('FPDValidation'); let optout; /** diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 44538e30921..ca157ed3694 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -28,7 +28,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'pubProvidedId': 1 }; export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 12e8d4b23c8..7e5b21de5a6 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -122,6 +122,7 @@ import { getStorageManager } from '../src/storageManager.js'; import adapterManager from '../src/adapterManager.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; @@ -153,7 +154,7 @@ const SFBX_LITE_DATA_SOURCE_LABEL = 'lite'; const GVLID = 284; export const storage = getStorageManager({ - gvlid: GVLID, + moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); @@ -180,6 +181,7 @@ export const storage = getStorageManager({ class WeboramaRtdProvider { #components; name = SUBMODULE_NAME; + gvlid = GVLID; /** * @param {Components} components */ diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index a16e4ec8d36..820e6365a9f 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -7,8 +7,10 @@ import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {includes as strIncludes} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'yuktamedia'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const yuktamediaAnalyticsVersion = 'v3.1.0'; let initOptions; @@ -261,7 +263,7 @@ yuktamediaAnalyticsAdapter.enableAnalytics = function (config) { adapterManager.registerAnalyticsAdapter({ adapter: yuktamediaAnalyticsAdapter, - code: 'yuktamedia' + code: MODULE_CODE, }); export default yuktamediaAnalyticsAdapter; diff --git a/modules/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js index 3437928df4b..4cb827cbdff 100644 --- a/modules/zeotapIdPlusIdSystem.js +++ b/modules/zeotapIdPlusIdSystem.js @@ -6,7 +6,8 @@ */ import { isStr, isPlainObject } from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; const ZEOTAP_VENDOR_ID = 301; @@ -21,7 +22,7 @@ function readFromLocalStorage() { } export function getStorage() { - return getStorageManager({gvlid: ZEOTAP_VENDOR_ID, moduleName: ZEOTAP_MODULE_NAME}); + return getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: ZEOTAP_MODULE_NAME}); } export const storage = getStorage(); diff --git a/src/activities/modules.js b/src/activities/modules.js new file mode 100644 index 00000000000..d140b10387f --- /dev/null +++ b/src/activities/modules.js @@ -0,0 +1,5 @@ +export const MODULE_TYPE_CORE = 'core'; +export const MODULE_TYPE_BIDDER = 'bidder'; +export const MODULE_TYPE_UID = 'userId'; +export const MODULE_TYPE_RTD = 'rtd'; +export const MODULE_TYPE_ANALYTICS = 'analytics'; diff --git a/src/adapterManager.js b/src/adapterManager.js index 850488af8ae..45438f59b55 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -31,11 +31,12 @@ import {hook} from './hook.js'; import {find, includes} from './polyfill.js'; import {adunitCounter} from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; -import {GdprConsentHandler, UspConsentHandler, GppConsentHandler} from './consentHandler.js'; +import {GdprConsentHandler, UspConsentHandler, GppConsentHandler, GDPR_GVLIDS} from './consentHandler.js'; import * as events from './events.js'; import CONSTANTS from './constants.json'; import {useMetrics} from './utils/perfMetrics.js'; import {auctionManager} from './auctionManager.js'; +import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER} from './activities/modules.js'; export const PARTITIONS = { CLIENT: 'client', @@ -463,6 +464,7 @@ adapterManager.registerBidAdapter = function (bidAdapter, bidderCode, {supported if (bidAdapter && bidderCode) { if (typeof bidAdapter.callBids === 'function') { _bidderRegistry[bidderCode] = bidAdapter; + GDPR_GVLIDS.register(MODULE_TYPE_BIDDER, bidderCode, bidAdapter.getSpec?.().gvlid); if (FEATURES.VIDEO && includes(supportedMediaTypes, 'video')) { adapterManager.videoAdapters.push(bidderCode); @@ -532,6 +534,7 @@ adapterManager.registerAnalyticsAdapter = function ({adapter, code, gvlid}) { if (typeof adapter.enableAnalytics === 'function') { adapter.code = code; _analyticsRegistry[code] = { adapter, gvlid }; + GDPR_GVLIDS.register(MODULE_TYPE_ANALYTICS, code, gvlid); } else { logError(`Prebid Error: Analytics adaptor error for analytics "${code}" analytics adapter must implement an enableAnalytics() function`); diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 9357041c505..53d2a4d3ca6 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -199,7 +199,7 @@ export function registerBidder(spec) { export function newBidder(spec) { return Object.assign(new Adapter(spec.code), { getSpec: function() { - return Object.freeze(spec); + return Object.freeze(Object.assign({}, spec)); }, registerSyncs, callBids: function(bidderRequest, addBidResponse, done, ajax, onTimelyResponse, configEnabledCallback) { diff --git a/src/consentHandler.js b/src/consentHandler.js index b1b2a04c043..4776a8ece02 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -118,3 +118,45 @@ export class GppConsentHandler extends ConsentHandler { } } } + +export function gvlidRegistry() { + const registry = {}; + const flat = {}; + const none = {}; + return { + /** + * Register a module's GVL ID. + * @param {string} moduleType defined in `activities/modules.js` + * @param {string} moduleName + * @param {number} gvlid + */ + register(moduleType, moduleName, gvlid) { + if (gvlid) { + (registry[moduleName] = registry[moduleName] || {})[moduleType] = gvlid; + if (flat.hasOwnProperty(moduleName)) { + if (flat[moduleName] !== gvlid) flat[moduleName] = none; + } else { + flat[moduleName] = gvlid; + } + } + }, + /** + * Get a module's GVL ID(s). + * + * @param {string} moduleName + * @return {{modules: {[moduleType]: number}, gvlid?: number}} an object where: + * `modules` is a map from module type to that module's GVL ID; + * `gvlid` is the single GVL ID for this family of modules (only defined + * if all modules with this name declared the same ID). + */ + get(moduleName) { + const result = {modules: registry[moduleName] || {}}; + if (flat.hasOwnProperty(moduleName) && flat[moduleName] !== none) { + result.gvlid = flat[moduleName]; + } + return result; + } + } +} + +export const GDPR_GVLIDS = gvlidRegistry(); diff --git a/src/storageManager.js b/src/storageManager.js index b8906b131e4..0248237fbc4 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,51 +1,30 @@ import {hook} from './hook.js'; -import {hasDeviceAccess, checkCookieSupport, logError, logInfo, isPlainObject} from './utils.js'; +import {checkCookieSupport, hasDeviceAccess, logError, logInfo} from './utils.js'; import {bidderSettings as defaultBidderSettings} from './bidderSettings.js'; -import {VENDORLESS_GVLID} from './consentHandler.js'; - -const moduleTypeWhiteList = ['core', 'prebid-module']; - -export let storageCallbacks = []; +import {MODULE_TYPE_BIDDER, MODULE_TYPE_CORE} from './activities/modules.js'; export const STORAGE_TYPE_LOCALSTORAGE = 'html5'; export const STORAGE_TYPE_COOKIES = 'cookie'; -/** - * Storage options - * @typedef {Object} storageOptions - * @property {Number=} gvlid - Vendor id - * @property {string} moduleName? - Module name - * @property {string=} bidderCode? - Bidder code - * @property {string=} moduleType - Module type, value can be anyone of core or prebid-module - */ +export let storageCallbacks = []; -/** - * Returns list of storage related functions with gvlid, module name and module type in its scope. - * All three argument are optional here. Below shows the usage of of these - * - GVL Id: Pass GVL id if you are a vendor - * - Bidder code: All bid adapters need to pass bidderCode - * - Module name: All other modules need to pass module name - * - Module type: Some modules may need these functions but are not vendor. e.g prebid core files in src and modules like currency. - * @param {storageOptions} options +/* + * Storage manager constructor. Consumers should prefer one of `getStorageManager` or `getCoreStorageManager`. */ -export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}, {bidderSettings = defaultBidderSettings} = {}) { +export function newStorageManager({moduleName, moduleType} = {}, {bidderSettings = defaultBidderSettings} = {}) { function isBidderAllowed(storageType) { - if (bidderCode == null) { + if (moduleType !== MODULE_TYPE_BIDDER) { return true; } - const storageAllowed = bidderSettings.get(bidderCode, 'storageAllowed'); + const storageAllowed = bidderSettings.get(moduleName, 'storageAllowed'); if (!storageAllowed || storageAllowed === true) return !!storageAllowed; if (Array.isArray(storageAllowed)) return storageAllowed.some((e) => e === storageType); return storageAllowed === storageType; } - if (moduleTypeWhiteList.includes(moduleType)) { - gvlid = gvlid || VENDORLESS_GVLID; - } - function isValid(cb, storageType) { if (!isBidderAllowed(storageType)) { - logInfo(`bidderSettings denied access to device storage for bidder '${bidderCode}'`); + logInfo(`bidderSettings denied access to device storage for bidder '${moduleName}'`); const result = {valid: false}; return cb(result); } else { @@ -53,7 +32,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = let hookDetails = { hasEnforcementHook: false } - validateStorageEnforcement(gvlid, bidderCode || moduleName, hookDetails, function(result) { + validateStorageEnforcement(moduleType, moduleName, hookDetails, function(result) { if (result && result.hasEnforcementHook) { value = cb(result); } else { @@ -252,31 +231,38 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = /** * This hook validates the storage enforcement if gdprEnforcement module is included */ -export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, hookDetails, callback) { +export const validateStorageEnforcement = hook('async', function(moduleType, moduleName, hookDetails, callback) { callback(hookDetails); }, 'validateStorageEnforcement'); /** - * This function returns storage functions to access cookies and localstorage. This function will bypass the gdpr enforcement requirement. Prebid as a software needs to use storage in some scenarios and is not a vendor so GDPR enforcement rules does not apply on Prebid. - * @param {string} moduleName Module name + * Get a storage manager for a particular module. + * + * Either bidderCode or a combination of moduleType + moduleName must be provided. The former is a shorthand + * for `{moduleType: 'bidder', moduleName: bidderCode}`. + * */ -export function getCoreStorageManager(moduleName) { - return newStorageManager({moduleName: moduleName, moduleType: 'core'}); +export function getStorageManager({moduleType, moduleName, bidderCode} = {}) { + function err() { + throw new Error(`Invalid invocation for getStorageManager: must set either bidderCode, or moduleType + moduleName`) + } + if (bidderCode) { + if ((moduleType && moduleType !== MODULE_TYPE_BIDDER) || moduleName) err() + moduleType = MODULE_TYPE_BIDDER; + moduleName = bidderCode; + } else if (!moduleName || !moduleType) { + err() + } + return newStorageManager({moduleType, moduleName}); } /** - * Note: Core modules or Prebid modules like Currency, SizeMapping should use getCoreStorageManager - * This function returns storage functions to access cookies and localstorage. Bidders and User id modules should import this and use it in their module if needed. - * Bid adapters should always provide `bidderCode`. GVL ID and Module name are optional param but gvl id is needed for when gdpr enforcement module is used. - * @param {Number=} gvlid? Vendor id - required for proper GDPR integration - * @param {string=} bidderCode? - required for bid adapters - * @param {string=} moduleName? module name + * Get a storage manager for "core" (vendorless, or first-party) modules. Shorthand for `getStorageManager({moduleName, moduleType: 'core'})`. + * + * @param {string} moduleName Module name */ -export function getStorageManager({gvlid, moduleName, bidderCode} = {}) { - if (arguments.length > 1 || (arguments.length > 0 && !isPlainObject(arguments[0]))) { - throw new Error('Invalid invocation for getStorageManager') - } - return newStorageManager({gvlid, moduleName, bidderCode}); +export function getCoreStorageManager(moduleName) { + return newStorageManager({moduleName: moduleName, moduleType: MODULE_TYPE_CORE}); } export function resetData() { diff --git a/test/spec/modules/admixerIdSystem_spec.js b/test/spec/modules/admixerIdSystem_spec.js index 18107b780db..753b1e3c2d5 100644 --- a/test/spec/modules/admixerIdSystem_spec.js +++ b/test/spec/modules/admixerIdSystem_spec.js @@ -1,9 +1,6 @@ import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; -import {getStorageManager} from '../../../src/storageManager.js'; - -export const storage = getStorageManager(); const pid = '4D393FAC-B6BB-4E19-8396-0A4813607316'; const getIdParams = {params: {pid: pid}}; diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index b787a52d6f2..150e013af98 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -13,21 +13,21 @@ describe('adnuntiusBidAdapter', function () { const meta = [{ key: 'usi', value: usi }] before(() => { - const storage = getStorageManager({ gvlid: GVLID, moduleName: 'adnuntius' }) - storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) - }); - - beforeEach(function () { $$PREBID_GLOBAL$$.bidderSettings = { adnuntius: { storageAllowed: true } }; + const storage = getStorageManager({ bidderCode: 'adnuntius' }) + storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) + }); + + after(() => { + $$PREBID_GLOBAL$$.bidderSettings = {}; }); afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.bidderSettings = {}; }); const tzo = new Date().getTimezoneOffset(); diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js index cae90a19223..2316f96ec8e 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -3,14 +3,14 @@ import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import {server} from '../../mocks/xhr.js'; import {parseBrowser} from '../../../modules/atsAnalyticsAdapter.js'; -import {getStorageManager} from '../../../src/storageManager.js'; +import {getCoreStorageManager, getStorageManager} from '../../../src/storageManager.js'; import {analyticsUrl} from '../../../modules/atsAnalyticsAdapter.js'; let utils = require('src/utils'); let events = require('src/events'); let constants = require('src/constants.json'); -export const storage = getStorageManager(); +const storage = getCoreStorageManager(); let sandbox; let clock; let now = new Date(); diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 8e203510b10..537b82b0e2e 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -2,7 +2,6 @@ import { expect } from 'chai'; import { spec } from '../../../modules/datablocksBidAdapter.js'; import { BotClientTests } from '../../../modules/datablocksBidAdapter.js'; import { getStorageManager } from '../../../src/storageManager.js'; -export let storage = getStorageManager(); const bid = { bidId: '2dd581a2b6281d', diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 8d58990bb66..941f2b3c8df 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -1,26 +1,33 @@ import { deviceAccessHook, - setEnforcementConfig, - userSyncHook, - userIdHook, - makeBidRequestsHook, - validateRules, + enableAnalyticsHook, enforcementRules, + getGvlid, + getGvlidFromAnalyticsAdapter, + makeBidRequestsHook, purpose1Rule, purpose2Rule, - enableAnalyticsHook, - getGvlid, - internal, STRICT_STORAGE_ENFORCEMENT + setEnforcementConfig, + STRICT_STORAGE_ENFORCEMENT, + userIdHook, + userSyncHook, + validateRules } from 'modules/gdprEnforcement.js'; -import { config } from 'src/config.js'; -import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; +import {config} from 'src/config.js'; +import adapterManager, {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import { validateStorageEnforcement } from 'src/storageManager.js'; +import { + MODULE_TYPE_ANALYTICS, + MODULE_TYPE_BIDDER, + MODULE_TYPE_CORE, + MODULE_TYPE_UID +} from '../../../src/activities/modules.js'; import * as events from 'src/events.js'; import 'modules/appnexusBidAdapter.js'; // some tests expect this to be in the adapter registry -import 'src/prebid.js' +import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; -import {VENDORLESS_GVLID} from '../../../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../../../src/consentHandler.js'; +import {validateStorageEnforcement} from '../../../src/storageManager.js'; describe('gdpr enforcement', function () { let nextFnSpy; @@ -100,6 +107,7 @@ describe('gdpr enforcement', function () { } } }; + let gvlids; before(() => { hook.ready(); @@ -111,31 +119,28 @@ describe('gdpr enforcement', function () { adapterManager.makeBidRequests.getHooks({ hook: makeBidRequestsHook }).remove(); }) - describe('deviceAccessHook', function () { - let adapterManagerStub; + beforeEach(() => { + gvlids = {}; + sinon.stub(GDPR_GVLIDS, 'get').callsFake((name) => ({gvlid: gvlids[name], modules: {}})); + }); - function getBidderSpec(gvlid) { - return { - getSpec: () => { - return { - gvlid - } - } - } - } + afterEach(() => { + GDPR_GVLIDS.get.restore(); + }); + describe('deviceAccessHook', function () { beforeEach(function () { nextFnSpy = sinon.spy(); gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); logWarnSpy = sinon.spy(utils, 'logWarn'); - adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); }); + afterEach(function () { config.resetConfig(); gdprDataHandler.getConsentData.restore(); logWarnSpy.restore(); - adapterManagerStub.restore(); }); + it('should not allow device access when device access flag is set to false', function () { config.setConfig({ deviceAccess: false, @@ -161,8 +166,10 @@ describe('gdpr enforcement', function () { }); it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function () { - adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); - adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(5)); + Object.assign(gvlids, { + appnexus: 1, + rubicon: 5 + }); setEnforcementConfig({ gdpr: { rules: [{ @@ -179,14 +186,16 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 5, 'rubicon'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'rubicon'); expect(logWarnSpy.callCount).to.equal(0); }); it('should check consent for all vendors when enforcePurpose and enforceVendor are true', function () { - adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); - adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(3)); + Object.assign(gvlids, { + appnexus: 1, + rubicon: 3 + }); setEnforcementConfig({ gdpr: { rules: [{ @@ -202,13 +211,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 3, 'rubicon'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'rubicon'); expect(logWarnSpy.callCount).to.equal(1); }); it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function () { - adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + gvlids.appnexus = 1; setEnforcementConfig({ gdpr: { rules: [{ @@ -225,13 +234,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus', result); }); it('should use gvlMapping set by publisher', function() { @@ -256,13 +265,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus', result); config.resetConfig(); }); @@ -291,13 +300,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus', result); config.resetConfig(); curBidderStub.restore(); }); @@ -310,9 +319,9 @@ describe('gdpr enforcement', function () { } gdprDataHandlerStub.returns(consentData); const validate = sinon.stub().callsFake(() => false); - deviceAccessHook(nextFnSpy, VENDORLESS_GVLID, 'mockModule', undefined, {validate}); + deviceAccessHook(nextFnSpy, MODULE_TYPE_CORE, 'mockModule', undefined, {validate}); sinon.assert.callCount(validate, 0); - sinon.assert.calledWith(nextFnSpy, VENDORLESS_GVLID, 'mockModule', {hasEnforcementHook: true, valid: true}); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_CORE, 'mockModule', {hasEnforcementHook: true, valid: true}); }) }); @@ -354,23 +363,11 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); + gvlids.sampleBidder1 = 1; userSyncHook(nextFnSpy); curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); + gvlids.sampleBidder2 = 3; userSyncHook(nextFnSpy); expect(nextFnSpy.calledTwice).to.equal(true); }); @@ -393,23 +390,11 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); + gvlids.sampleBidder1 = 1; userSyncHook(nextFnSpy); curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); + gvlids.sampleBidder2 = 3; userSyncHook(nextFnSpy); expect(nextFnSpy.calledOnce).to.equal(true); expect(logWarnSpy.callCount).to.equal(1); @@ -433,23 +418,11 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); + gvlids.sampleBidder1 = 1; userSyncHook(nextFnSpy); curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); + gvlids.sampleBidder2 = 3; userSyncHook(nextFnSpy); expect(nextFnSpy.calledTwice).to.equal(true); expect(logWarnSpy.callCount).to.equal(0); @@ -486,6 +459,7 @@ describe('gdpr enforcement', function () { name: 'sampleUserId' } }] + gvlids.sampleUserId = 1; userIdHook(nextFnSpy, submodules, consentData); // Should pass back hasValidated flag since version 2 const args = nextFnSpy.getCalls()[0].args; @@ -501,6 +475,7 @@ describe('gdpr enforcement', function () { name: 'sampleUserId' } }]; + gvlids.sampleUserId = 1; let consentData = null; userIdHook(nextFnSpy, submodules, consentData); // Should not pass back hasValidated flag since version 2 @@ -537,6 +512,10 @@ describe('gdpr enforcement', function () { name: 'sampleUserId1' } }] + Object.assign(gvlids, { + sampleUserId: 1, + sampleUserId1: 3 + }); userIdHook(nextFnSpy, submodules, consentData); expect(logWarnSpy.callCount).to.equal(1); let expectedSubmodules = [{ @@ -602,20 +581,9 @@ describe('gdpr enforcement', function () { consentData.gdprApplies = true; gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('bidder_1').returns({ - getSpec: function () { - return { 'gvlid': 4 } - } - }); - adapterManagerStub.withArgs('bidder_2').returns({ - getSpec: function () { - return { 'gvlid': 5 } - } - }); - adapterManagerStub.withArgs('bidder_3').returns({ - getSpec: function () { - return { 'gvlid': undefined } - } + Object.assign(gvlids, { + bidder_1: 4, + bidder_2: 5, }); makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); @@ -660,21 +628,10 @@ describe('gdpr enforcement', function () { consentData.gdprApplies = true; gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('bidder_1').returns({ - getSpec: function () { - return { 'gvlid': 4 } - } - }); - adapterManagerStub.withArgs('bidder_2').returns({ - getSpec: function () { - return { 'gvlid': 5 } - } - }); - adapterManagerStub.withArgs('bidder_3').returns({ - getSpec: function () { - return { 'gvlid': undefined } - } - }); + Object.assign(gvlids, { + bidder_1: 4, + bidder_2: 5, + }) makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); @@ -771,9 +728,11 @@ describe('gdpr enforcement', function () { consentData.gdprApplies = true; gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('analyticsAdapter_A').returns({ gvlid: 3 }); - adapterManagerStub.withArgs('analyticsAdapter_B').returns({ gvlid: 5 }); - adapterManagerStub.withArgs('analyticsAdapter_C').returns({ gvlid: 1 }); + Object.assign(gvlids, { + analyticsAdapter_A: 3, + analyticsAdapter_B: 5, + analyticsAdapter_C: 1 + }); enableAnalyticsHook(nextFnSpy, MOCK_ANALYTICS_ADAPTER_CONFIG); @@ -1142,13 +1101,13 @@ describe('gdpr enforcement', function () { }); describe('getGvlid', function() { - let getGvlidForBidAdapterStub; - let getGvlidForUserIdModuleStub; - let getGvlidForAnalyticsAdapterStub; + const MOCK_MODULE = 'moduleA'; + let entry; + beforeEach(function() { - getGvlidForBidAdapterStub = sandbox.stub(internal, 'getGvlidForBidAdapter'); - getGvlidForUserIdModuleStub = sandbox.stub(internal, 'getGvlidForUserIdModule'); - getGvlidForAnalyticsAdapterStub = sandbox.stub(internal, 'getGvlidForAnalyticsAdapter'); + entry = {modules: {}}; + GDPR_GVLIDS.get.reset(); + GDPR_GVLIDS.get.callsFake((mod) => mod === MOCK_MODULE ? entry : {modules: {}}); }); it('should return "null" if called without passing any argument', function() { @@ -1156,46 +1115,63 @@ describe('gdpr enforcement', function () { expect(gvlid).to.equal(null); }); - it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function() { - getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); - getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); - getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(null); - - const gvlid = getGvlid('moduleA'); + it('should return "null" if no GVL ID was registered', function() { + const gvlid = getGvlid('type', MOCK_MODULE); expect(gvlid).to.equal(null); }); - it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { - config.setConfig({ - gvlMapping: { - moduleA: 1 - } - }); - - // Actual GVL ID for moduleA is 2, as defined on its the bidAdapter.js file. - getGvlidForBidAdapterStub.withArgs('moduleA').returns(2); - - const gvlid = getGvlid('moduleA'); - expect(gvlid).to.equal(1); - }); - - it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function() { - getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); - getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); - getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(7); + it('should return null if the wrong GVL ID was registered', () => { + entry = {gvlid: 123}; + expect(getGvlid('type', 'someOtherModule')).to.equal(null); + }) - expect(getGvlid('moduleA')).to.equal(7); - }); + Object.entries({ + 'without fallback': null, + 'with fallback': () => 'shouldBeIgnored' + }).forEach(([t, fallbackFn]) => { + describe(t, () => { + it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { + config.setConfig({ + gvlMapping: { + [MOCK_MODULE]: 1 + } + }); + + entry = {gvlid: 2}; + + const gvlid = getGvlid('type', MOCK_MODULE, fallbackFn); + expect(gvlid).to.equal(1); + }); + + it('should return the GVL ID that was registered', function() { + entry = {gvlid: 7}; + expect(getGvlid('type', MOCK_MODULE, fallbackFn)).to.equal(7); + }); + + it('should return VENDORLESS_GVLID for core modules', () => { + entry = {gvlid: 123}; + expect(getGvlid(MODULE_TYPE_CORE, MOCK_MODULE, fallbackFn)).to.equal(VENDORLESS_GVLID); + }); + + describe('multiple GVL IDs are found', () => { + it('should use bidder over others', () => { + entry = {modules: {[MODULE_TYPE_BIDDER]: 123, [MODULE_TYPE_UID]: 321}}; + expect(getGvlid(MODULE_TYPE_UID, MOCK_MODULE, fallbackFn)).to.equal(123); + }); + it('should use uid over analytics', () => { + entry = {modules: {[MODULE_TYPE_UID]: 123, [MODULE_TYPE_ANALYTICS]: 321}}; + expect(getGvlid(MODULE_TYPE_ANALYTICS, MOCK_MODULE, fallbackFn)).to.equal(123); + }) + }) + }) + }) - it('should pass extra arguments to analytics\' getGvlid', () => { - getGvlidForAnalyticsAdapterStub.withArgs('analytics').returns(321); - const cfg = {some: 'args'}; - getGvlid('analytics', cfg); - sinon.assert.calledWith(getGvlidForAnalyticsAdapterStub, 'analytics', cfg); + it('should use fallbackFn if no other lookup produces a gvl id', () => { + expect(getGvlid('type', MOCK_MODULE, () => 321)).to.equal(321); }); }); - describe('getGvlidForAnalyticsAdapter', () => { + describe('getGvlidFromAnalyticsConfig', () => { let getAnalyticsAdapter, adapter, adapterEntry; beforeEach(() => { @@ -1207,26 +1183,20 @@ describe('gdpr enforcement', function () { getAnalyticsAdapter.withArgs('analytics').returns(adapterEntry); }); - it('should return gvlid from adapterManager if defined', () => { - adapterEntry.gvlid = 123; - adapter.gvlid = 321 - expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.equal(123); - }); - it('should return gvlid from adapter if defined', () => { adapter.gvlid = 321; - expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.equal(321); + expect(getGvlidFromAnalyticsAdapter('analytics')).to.equal(321); }); it('should invoke adapter.gvlid if it\'s a function', () => { adapter.gvlid = (cfg) => cfg.k const cfg = {k: 231}; - expect(internal.getGvlidForAnalyticsAdapter('analytics', cfg)).to.eql(231); + expect(getGvlidFromAnalyticsAdapter('analytics', cfg)).to.eql(231); }); it('should not choke if adapter gvlid fn throws', () => { adapter.gvlid = () => { throw new Error(); }; - expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.not.be.ok; + expect(getGvlidFromAnalyticsAdapter('analytics')).to.not.be.ok; }); }); }) diff --git a/test/spec/modules/growthCodeIdSystem_spec.js b/test/spec/modules/growthCodeIdSystem_spec.js index dce995d25e0..97083047d4e 100644 --- a/test/spec/modules/growthCodeIdSystem_spec.js +++ b/test/spec/modules/growthCodeIdSystem_spec.js @@ -4,12 +4,13 @@ import { server } from 'test/mocks/xhr.js'; import { uspDataHandler } from 'src/adapterManager.js'; import {expect} from 'chai'; import {getStorageManager} from '../../../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; const GCID_EXPIRY = 45; const MODULE_NAME = 'growthCodeId'; const SHAREDID = 'fe9c5c89-7d56-4666-976d-e07e73b3b664'; -export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); const getIdParams = {params: { pid: 'TEST01', diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index a31270c86c7..52e9f9171d6 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -1,9 +1,9 @@ import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; -import {getStorageManager} from '../../../src/storageManager.js'; +import {getCoreStorageManager} from '../../../src/storageManager.js'; -export const storage = getStorageManager(); +const storage = getCoreStorageManager(); const pid = '14'; let defaultConfigParams; diff --git a/test/spec/modules/publinkIdSystem_spec.js b/test/spec/modules/publinkIdSystem_spec.js index 4656afe1585..7d98b724bd8 100644 --- a/test/spec/modules/publinkIdSystem_spec.js +++ b/test/spec/modules/publinkIdSystem_spec.js @@ -1,11 +1,12 @@ import {publinkIdSubmodule} from 'modules/publinkIdSystem.js'; -import {getStorageManager} from '../../../src/storageManager'; +import {getCoreStorageManager, getStorageManager} from '../../../src/storageManager'; import {server} from 'test/mocks/xhr.js'; import sinon from 'sinon'; import {uspDataHandler} from '../../../src/adapterManager'; import {parseUrl} from '../../../src/utils'; -export const storage = getStorageManager({gvlid: 24}); +const storage = getCoreStorageManager(); + const TEST_COOKIE_VALUE = 'cookievalue'; describe('PublinkIdSystem', () => { describe('decode', () => { diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index ced2f697649..f9c41b2fda0 100644 --- a/test/spec/modules/realTimeDataModule_spec.js +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -5,6 +5,8 @@ import {default as CONSTANTS} from '../../../src/constants.json'; import * as events from '../../../src/events.js'; import 'src/prebid.js'; import {attachRealTimeDataProvider, onDataDeletionRequest} from 'modules/rtdModule/index.js'; +import {GDPR_GVLIDS} from '../../../src/consentHandler.js'; +import {MODULE_TYPE_RTD} from '../../../src/activities/modules.js'; const getBidRequestDataSpy = sinon.spy(); @@ -84,6 +86,26 @@ describe('Real time module', function () { sandbox.restore(); }); + describe('GVL IDs', () => { + beforeEach(() => { + sinon.stub(GDPR_GVLIDS, 'register'); + }); + + afterEach(() => { + GDPR_GVLIDS.register.restore(); + }); + + it('are registered when RTD module is registered', () => { + let mod; + try { + mod = attachRealTimeDataProvider({name: 'mockRtd', gvlid: 123}); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_RTD, 'mockRtd', 123); + } finally { + mod && mod(); + } + }) + }) + describe('', () => { const PROVIDERS = [validSM, invalidSM, failureSM, nonConfSM, validSMWait]; let _detachers; diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 6a6e79c633d..0f2f9abd583 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -1,13 +1,13 @@ -import { expect } from 'chai'; -import { spec } from 'modules/relaidoBidAdapter.js'; +import {expect} from 'chai'; +import {spec} from 'modules/relaidoBidAdapter.js'; import * as utils from 'src/utils.js'; -import { BANNER, VIDEO } from 'src/mediaTypes.js'; -import { getStorageManager } from '../../../src/storageManager.js'; +import {VIDEO} from 'src/mediaTypes.js'; +import {getCoreStorageManager} from '../../../src/storageManager.js'; const UUID_KEY = 'relaido_uuid'; const relaido_uuid = 'hogehoge'; -const storage = getStorageManager(); +const storage = getCoreStorageManager(); storage.setCookie(UUID_KEY, relaido_uuid); describe('RelaidoAdapter', function () { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index f092486c587..a2f2bfd8713 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -53,7 +53,8 @@ import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import {getPPID} from '../../../src/adserver.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; -import {getCoreStorageManager} from '../../../src/storageManager.js'; +import {GDPR_GVLIDS} from '../../../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -167,6 +168,20 @@ describe('User ID', function () { sandbox.restore(); }); + describe('GVL IDs', () => { + beforeEach(() => { + sinon.stub(GDPR_GVLIDS, 'register'); + }); + afterEach(() => { + GDPR_GVLIDS.register.restore(); + }); + + it('are registered when ID submodule is registered', () => { + attachIdSystem({name: 'gvlidMock', gvlid: 123}); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_UID, 'gvlidMock', 123); + }) + }) + describe('Decorate Ad Units', function () { beforeEach(function () { // reset mockGpt so nothing else interferes diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 6494a7cbfef..54483f0c00e 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, getStorage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; import * as storageManager from 'src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; const ZEOTAP_COOKIE = 'THIS-IS-A-DUMMY-COOKIE'; @@ -52,9 +53,9 @@ describe('Zeotap ID System', function() { }); it('when a stored Zeotap ID exists it is added to bids', function() { - let store = getStorage(); + getStorage(); expect(getStorageManagerSpy.calledOnce).to.be.true; - sinon.assert.calledWith(getStorageManagerSpy, {gvlid: 301, moduleName: 'zeotapIdPlus'}); + sinon.assert.calledWith(getStorageManagerSpy, {moduleType: MODULE_TYPE_UID, moduleName: 'zeotapIdPlus'}); }); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 58334595d71..8d887474180 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -21,6 +21,8 @@ import {find, includes} from 'src/polyfill.js'; import s2sTesting from 'modules/s2sTesting.js'; import {hook} from '../../../../src/hook.js'; import {auctionManager} from '../../../../src/auctionManager.js'; +import {GDPR_GVLIDS} from '../../../../src/consentHandler.js'; +import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER} from '../../../../src/activities/modules.js'; var events = require('../../../../src/events'); const CONFIG = { @@ -2737,4 +2739,23 @@ describe('adapterManager tests', function () { }) }) }); + + describe('registers GVL IDs', () => { + beforeEach(() => { + sinon.stub(GDPR_GVLIDS, 'register'); + }); + afterEach(() => { + GDPR_GVLIDS.register.restore(); + }); + + it('for bid adapters', () => { + adapterManager.registerBidAdapter({getSpec: () => ({gvlid: 123}), callBids: sinon.stub()}, 'mock'); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_BIDDER, 'mock', 123); + }); + + it('for analytics adapters', () => { + adapterManager.registerAnalyticsAdapter({adapter: {enableAnalytics: sinon.stub()}, code: 'mock', gvlid: 123}); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_ANALYTICS, 'mock', 123); + }); + }); }); diff --git a/test/spec/unit/core/consentHandler_spec.js b/test/spec/unit/core/consentHandler_spec.js index 082ff34f90c..98b317e0d36 100644 --- a/test/spec/unit/core/consentHandler_spec.js +++ b/test/spec/unit/core/consentHandler_spec.js @@ -1,4 +1,4 @@ -import {ConsentHandler} from '../../../../src/consentHandler.js'; +import {ConsentHandler, gvlidRegistry} from '../../../../src/consentHandler.js'; describe('Consent data handler', () => { let handler; @@ -57,3 +57,31 @@ describe('Consent data handler', () => { }) }); }) + +describe('gvlidRegistry', () => { + let registry; + beforeEach(() => { + registry = gvlidRegistry(); + }); + + it('returns undef when id cannoot be found', () => { + expect(registry.get('name')).to.eql({modules: {}}) + }); + + it('does not register null ids', () => { + registry.register('type', 'name', null); + expect(registry.get('type', 'name')).to.eql({modules: {}}); + }) + + it('can retrieve registered GVL IDs', () => { + registry.register('type', 'name', 123); + registry.register('otherType', 'name', 123); + expect(registry.get('name')).to.eql({gvlid: 123, modules: {type: 123, otherType: 123}}); + }); + + it('does not return `gvlid` if there is more than one', () => { + registry.register('type', 'name', 123); + registry.register('otherType', 'name', 321); + expect(registry.get('name')).to.eql({modules: {type: 123, otherType: 321}}) + }); +}) diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index d4b3c8e583e..9e31389d96f 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -1,14 +1,14 @@ import { + getCoreStorageManager, getStorageManager, + newStorageManager, resetData, - getCoreStorageManager, storageCallbacks, - getStorageManager, - newStorageManager, validateStorageEnforcement + validateStorageEnforcement } from 'src/storageManager.js'; -import { config } from 'src/config.js'; +import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {hook} from '../../../../src/hook.js'; -import {VENDORLESS_GVLID} from '../../../../src/consentHandler.js'; +import {MODULE_TYPE_BIDDER} from '../../../../src/activities/modules.js'; describe('storage manager', function() { before(() => { @@ -34,7 +34,7 @@ describe('storage manager', function() { it('should add done callbacks to storageCallbacks array', function() { let noop = sinon.spy(); - const coreStorage = getStorageManager(); + const coreStorage = newStorageManager(); coreStorage.setCookie('foo', 'bar', null, null, null, noop); coreStorage.getCookie('foo', noop); @@ -50,17 +50,16 @@ describe('storage manager', function() { it('should allow bidder to access device if gdpr enforcement module is not included', function() { let deviceAccessSpy = sinon.spy(utils, 'hasDeviceAccess'); - const storage = getStorageManager(); + const storage = newStorageManager(); storage.setCookie('foo1', 'baz1'); expect(deviceAccessSpy.calledOnce).to.equal(true); deviceAccessSpy.restore(); }); - describe(`core storage`, () => { - let storage, validateHook; + describe(`enforcement`, () => { + let validateHook; beforeEach(() => { - storage = getCoreStorageManager(); validateHook = sinon.stub().callsFake(function (next, ...args) { next.apply(this, args); }); @@ -72,15 +71,26 @@ describe('storage manager', function() { config.resetConfig(); }) - it('should respect (vendorless) consent enforcement', () => { - storage.localStorageIsEnabled(); - expect(validateHook.args[0][1]).to.equal(VENDORLESS_GVLID); // gvlid should be set to VENDORLESS_GVLID - }); + Object.entries({ + 'core': () => getCoreStorageManager('mock'), + 'other': () => getStorageManager({moduleType: 'other', moduleName: 'mock'}) + }).forEach(([moduleType, getMgr]) => { + describe(`for ${moduleType} modules`, () => { + let storage; + beforeEach(() => { + storage = getMgr(); + }); + it(`should pass '${moduleType}' module type to consent enforcement`, () => { + storage.localStorageIsEnabled(); + expect(validateHook.args[0][1]).to.equal(moduleType); + }); - it('should respect the deviceAccess flag', () => { - config.setConfig({deviceAccess: false}); - expect(storage.localStorageIsEnabled()).to.be.false - }) + it('should respect the deviceAccess flag', () => { + config.setConfig({deviceAccess: false}); + expect(storage.localStorageIsEnabled()).to.be.false + }); + }); + }); }) describe('localstorage forbidden access in 3rd-party context', function() { @@ -100,7 +110,7 @@ describe('storage manager', function() { }) it('should not throw if the localstorage is not accessible when setting/getting/removing from localstorage', function() { - const coreStorage = getStorageManager(); + const coreStorage = newStorageManager(); coreStorage.setDataInLocalStorage('key', 'value'); const val = coreStorage.getDataFromLocalStorage('key'); @@ -124,7 +134,7 @@ describe('storage manager', function() { }) it('should remove side-effect after checking', function () { - const storage = getStorageManager(); + const storage = newStorageManager(); localStorage.setItem('unrelated', 'dummy'); const val = storage.localStorageIsEnabled(); @@ -206,7 +216,7 @@ describe('storage manager', function() { let mgr; beforeEach(() => { - mgr = newStorageManager({bidderCode: bidderCode}, {bidderSettings: mockBidderSettings(configValue)}); + mgr = newStorageManager({moduleType: MODULE_TYPE_BIDDER, moduleName: bidderCode}, {bidderSettings: mockBidderSettings(configValue)}); }) afterEach(() => { From 3b82f53ea2b2bb76a3073219ccd6dab04c9c2bd2 Mon Sep 17 00:00:00 2001 From: TM Date: Fri, 7 Apr 2023 15:50:02 +0200 Subject: [PATCH 285/375] Multiple bids in one request to Adrino Adserver (#9742) Co-authored-by: Tomasz Mielcarz --- modules/adrinoBidAdapter.js | 40 +++++---- test/spec/modules/adrinoBidAdapter_spec.js | 99 ++++++++++++++-------- 2 files changed, 89 insertions(+), 50 deletions(-) diff --git a/modules/adrinoBidAdapter.js b/modules/adrinoBidAdapter.js index 2a5588eeb8c..efd23761bfd 100644 --- a/modules/adrinoBidAdapter.js +++ b/modules/adrinoBidAdapter.js @@ -31,11 +31,9 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const bidRequests = []; + let bids = []; for (let i = 0; i < validBidRequests.length; i++) { - let host = this.getBidderConfig('host') || BIDDER_HOST; - let requestData = { bidId: validBidRequests[i].bidId, nativeParams: validBidRequests[i].nativeParams, @@ -52,27 +50,37 @@ export const spec = { } } - bidRequests.push({ - method: REQUEST_METHOD, - url: host + '/bidder/bid/', - data: requestData, - options: { - contentType: 'application/json', - withCredentials: false, - } - }); + bids.push(requestData); } + let host = this.getBidderConfig('host') || BIDDER_HOST; + let bidRequests = []; + bidRequests.push({ + method: REQUEST_METHOD, + url: host + '/bidder/bids/', + data: bids, + options: { + contentType: 'application/json', + withCredentials: false, + } + }); + return bidRequests; }, interpretResponse: function (serverResponse, bidRequest) { const response = serverResponse.body; - const bidResponses = []; - if (!response.noAd) { - bidResponses.push(response); + const output = []; + + if (response.bidResponses) { + for (const bidResponse of response.bidResponses) { + if (!bidResponse.noAd) { + output.push(bidResponse); + } + } } - return bidResponses; + + return output; }, onBidWon: function (bid) { diff --git a/test/spec/modules/adrinoBidAdapter_spec.js b/test/spec/modules/adrinoBidAdapter_spec.js index 78cea8da9ac..577dd3e9164 100644 --- a/test/spec/modules/adrinoBidAdapter_spec.js +++ b/test/spec/modules/adrinoBidAdapter_spec.js @@ -86,16 +86,16 @@ describe('adrinoBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://stg-prebid-bidder.adrino.io/bidder/bid/'); - expect(result[0].data.bidId).to.equal('12345678901234'); - expect(result[0].data.placementHash).to.equal('abcdef123456'); - expect(result[0].data.referer).to.equal('http://example.com/'); - expect(result[0].data.userAgent).to.equal(navigator.userAgent); - expect(result[0].data).to.have.property('nativeParams'); - expect(result[0].data).not.to.have.property('gdprConsent'); - expect(result[0].data).to.have.property('userId'); - expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); - expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + expect(result[0].url).to.equal('https://stg-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('nativeParams'); + expect(result[0].data[0]).not.to.have.property('gdprConsent'); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); }); it('should build the request correctly with gdpr', function () { @@ -105,16 +105,16 @@ describe('adrinoBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bid/'); - expect(result[0].data.bidId).to.equal('12345678901234'); - expect(result[0].data.placementHash).to.equal('abcdef123456'); - expect(result[0].data.referer).to.equal('http://example.com/'); - expect(result[0].data.userAgent).to.equal(navigator.userAgent); - expect(result[0].data).to.have.property('nativeParams'); - expect(result[0].data).to.have.property('gdprConsent'); - expect(result[0].data).to.have.property('userId'); - expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); - expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('nativeParams'); + expect(result[0].data[0]).to.have.property('gdprConsent'); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); }); it('should build the request correctly without gdpr', function () { @@ -124,22 +124,22 @@ describe('adrinoBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bid/'); - expect(result[0].data.bidId).to.equal('12345678901234'); - expect(result[0].data.placementHash).to.equal('abcdef123456'); - expect(result[0].data.referer).to.equal('http://example.com/'); - expect(result[0].data.userAgent).to.equal(navigator.userAgent); - expect(result[0].data).to.have.property('nativeParams'); - expect(result[0].data).not.to.have.property('gdprConsent'); - expect(result[0].data).to.have.property('userId'); - expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); - expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('nativeParams'); + expect(result[0].data[0]).not.to.have.property('gdprConsent'); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); }); }); describe('interpretResponse', function () { it('should interpret the response correctly', function () { - const response = { + const response1 = { requestId: '31662c69728811', mediaType: 'native', cpm: 0.53, @@ -167,13 +167,44 @@ describe('adrinoBidAdapter', function () { } }; + const response2 = { + requestId: '31662c69728812', + mediaType: 'native', + cpm: 0.77, + currency: 'PLN', + creativeId: '859120', + netRevenue: true, + ttl: 600, + width: 1, + height: 1, + noAd: false, + testAd: false, + native: { + title: 'Ad Title', + body: 'Ad Body', + image: { + url: 'http://emisja.contentstream.pl/_/getImageII/?vid=17180728299&typ=cs_300_150&element=IMAGE&scale=1&prefix=adart&nc=1643878278955', + height: 150, + width: 300 + }, + clickUrl: 'http://emisja.contentstream.pl/_/ctr2/?u=https%3A%2F%2Fonline.efortuna.pl%2Fpage%3Fkey%3Dej0xMzUzMTM1NiZsPTE1Mjc1MzY1JnA9NTMyOTA%253D&e=znU3tABN8K4N391dmUxYfte5G9tBaDXELJVo1_-kvaTJH2XwWRw77fmfL2YjcEmrbqRQ3M0GcJ0vPWcLtZlsrf8dWrAEHNoZKAC6JMnZF_65IYhTPbQIJ-zn3ac9TU7gEZftFKksH1al7rMuieleVv9r6_DtrOk_oZcYAe4rMRQM-TiWvivJRPBchAAblE0cqyG7rCunJFpal43sxlYm4GvcBJaYHzErn5PXjEzNbd3xHjkdiap-xU9y6BbfkUZ1xIMS8QZLvwNrTXMFCSfSRN2tgVfEj7KyGdLCITHSaFtuIKT2iW2pxC7f2RtPHnzsEPXH0SgAfhA3OxZ5jkQjOZy0PsO7MiCv3sJai5ezUAOjFgayU91ZhI0Y9r2YpB1tTGIjnO23wot8PvRENlThHQ%3D%3D&ref=https%3A%2F%2Fbox.adrino.cloud%2Ftmielcarz%2Fadrino_prebid%2Ftest_page3.html%3Fpbjs_debug%3Dtrue', + privacyLink: 'https://adrino.pl/wp-content/uploads/2021/01/POLITYKA-PRYWATNOS%CC%81CI-Adrino-Mobile.pdf', + impressionTrackers: [ + 'https://prd-impression-tracker-producer.adrino.io/impression/eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpbXByZXNzaW9uSWRcIjpcIjMxNjYyYzY5NzI4ODExXCIsXCJkYXRlXCI6WzIwMjIsMiwzXSxcInBsYWNlbWVudEhhc2hcIjpcIjk0NTVjMDQxYzlkMTI1ZmIwNDE4MWVhMGVlZTJmMmFlXCIsXCJjYW1wYWlnbklkXCI6MTc5MjUsXCJhZHZlcnRpc2VtZW50SWRcIjo5MjA3OSxcInZpc3VhbGlzYXRpb25JZFwiOjg1OTExNSxcImNwbVwiOjUzLjB9IiwiZXhwIjoxNjQzOTE2MjUxLCJpYXQiOjE2NDM5MTU2NTF9.0Y_HvInGl6Xo5xP6rDLC8lzQRGvy-wKe0blk1o8ebWyVRFiUY1JGLUeE0k3sCsPNxgdHAv-o6EcbogpUuqlMJA' + ] + } + }; + const serverResponse = { - body: response + body: { bidResponses: [response1, response2] } }; const result = spec.interpretResponse(serverResponse, {}); - expect(result.length).to.equal(1); - expect(result[0]).to.equal(response); + expect(result.length).to.equal(2); + expect(result[0]).to.equal(response1); + expect(result[0].requestId).to.equal('31662c69728811'); + expect(result[1]).to.equal(response2); + expect(result[1].requestId).to.equal('31662c69728812'); }); it('should return empty array of responses', function () { From 34b7ea28ef88a7a05a9bd6ed83296022840eb02e Mon Sep 17 00:00:00 2001 From: Taro FURUKAWA <6879289+0tarof@users.noreply.github.com> Date: Mon, 10 Apr 2023 22:27:10 +0900 Subject: [PATCH 286/375] size map support (#9772) --- modules/ajaBidAdapter.js | 54 ++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 67b448eb484..133a2fdbadf 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -2,19 +2,28 @@ import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const BIDDER_CODE = 'aja'; +const BidderCode = 'aja'; const URL = 'https://ad.as.amanad.adtdp.com/v2/prebid'; -const SDK_TYPE = 5; -const AD_TYPE = { - BANNER: 1, - NATIVE: 2, - VIDEO: 3, +const SDKType = 5; +const AdType = { + Banner: 1, + Native: 2, + Video: 3, }; +const BannerSizeMap = { + '970x250': 1, + '300x250': 2, + '320x50': 3, + '728x90': 4, + '320x100': 6, + '336x280': 31, + '300x600': 32, +} + export const spec = { - code: BIDDER_CODE, + code: BidderCode, supportedMediaTypes: [VIDEO, BANNER, NATIVE], /** @@ -36,9 +45,6 @@ export const spec = { * @returns {ServerRequest|ServerRequest[]} */ buildRequests: function(validBidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const bidRequests = []; const pageUrl = bidderRequest?.refererInfo?.page || undefined; @@ -48,7 +54,7 @@ export const spec = { const asi = getBidIdParameter('asi', bidRequest.params); queryString = tryAppendQueryString(queryString, 'asi', asi); - queryString = tryAppendQueryString(queryString, 'skt', SDK_TYPE); + queryString = tryAppendQueryString(queryString, 'skt', SDKType); queryString = tryAppendQueryString(queryString, 'tid', bidRequest.transactionId) queryString = tryAppendQueryString(queryString, 'prebid_id', bidRequest.bidId); queryString = tryAppendQueryString(queryString, 'prebid_ver', '$prebid.version$'); @@ -57,11 +63,27 @@ export const spec = { queryString = tryAppendQueryString(queryString, 'page_url', pageUrl); } + const banner = deepAccess(bidRequest, `mediaTypes.${BANNER}`) + if (banner) { + const adFormatIDs = []; + for (const size of banner.sizes || []) { + if (size.length !== 2) { + continue + } + + const adFormatID = BannerSizeMap[`${size[0]}x${size[1]}`]; + if (adFormatID) { + adFormatIDs.push(adFormatID); + } + } + queryString = tryAppendQueryString(queryString, 'ad_format_ids', adFormatIDs.join(',')); + } + const eids = bidRequest.userIdAsEids; if (eids && eids.length) { queryString = tryAppendQueryString(queryString, 'eids', JSON.stringify({ 'eids': eids, - })) + })); } const sua = deepAccess(bidRequest, 'ortb2.device.sua'); @@ -101,7 +123,7 @@ export const spec = { }, } - if (AD_TYPE.VIDEO === ad.ad_type) { + if (AdType.Video === ad.ad_type) { const videoAd = bidderResponseBody.ad.video; Object.assign(bid, { vastXml: videoAd.vtag, @@ -113,7 +135,7 @@ export const spec = { }); Array.prototype.push.apply(bid.meta.advertiserDomains, videoAd.adomain) - } else if (AD_TYPE.BANNER === ad.ad_type) { + } else if (AdType.Banner === ad.ad_type) { const bannerAd = bidderResponseBody.ad.banner; Object.assign(bid, { width: bannerAd.w, @@ -131,7 +153,7 @@ export const spec = { } Array.prototype.push.apply(bid.meta.advertiserDomains, bannerAd.adomain) - } else if (AD_TYPE.NATIVE === ad.ad_type) { + } else if (AdType.Native === ad.ad_type) { const nativeAds = ad.native.template_and_ads.ads; if (nativeAds.length === 0) { return []; From 9a05eb0f20fd207b4950a4526564d9997ab321a3 Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Mon, 10 Apr 2023 07:13:34 -0700 Subject: [PATCH 287/375] OpenxOrtbAdapter: add back missing params support for coppa and video (#9782) --- modules/openxOrtbBidAdapter.js | 14 +++ modules/openxOrtbBidAdapter.md | 11 +- test/spec/modules/openxOrtbBidAdapter_spec.js | 107 ++++++++++++++++-- 3 files changed, 116 insertions(+), 16 deletions(-) diff --git a/modules/openxOrtbBidAdapter.js b/modules/openxOrtbBidAdapter.js index e550423094f..fbdf96fb72a 100644 --- a/modules/openxOrtbBidAdapter.js +++ b/modules/openxOrtbBidAdapter.js @@ -55,6 +55,9 @@ const converter = ortbConverter({ } }) const bid = context.bidRequests[0]; + if (bid.params.coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); + } if (bid.params.doNotTrack) { utils.deepSetValue(req, 'device.dnt', 1); } @@ -127,6 +130,17 @@ const converter = ortbConverter({ if (floor.bidfloorcur === 'USD') { Object.assign(imp, floor); } + }, + video(orig, imp, bidRequest, context) { + // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] + // to populate imp.video + // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); } } } diff --git a/modules/openxOrtbBidAdapter.md b/modules/openxOrtbBidAdapter.md index fd926b27b9f..04ed32608cb 100644 --- a/modules/openxOrtbBidAdapter.md +++ b/modules/openxOrtbBidAdapter.md @@ -21,7 +21,7 @@ Publishers are welcome to test this adapter and give feedback. Please note you s | `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` | `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

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

**WARNING:**
Request-level setting. May impact revenue. | true -| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. Use of `pbjs.setConfig({coppa: true});` is now preferred. | true ## Video @@ -29,7 +29,7 @@ Publishers are welcome to test this adapter and give feedback. Please note you s | ---- | ----- | ---- | ----------- | ------- | `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" -| `video` | optional | OpenRTB video subtypes | Alternatively can be added under adUnit.mediaTypes.video | `{ video: {mimes: ['video/mp4']}` +| `video` | optional | OpenRTB video subtypes | Use of adUnit.mediaTypes.video is now preferred. | `{ video: {mimes: ['video/mp4']}` # Example @@ -68,7 +68,8 @@ var adUnits = [ mediaTypes: { video: { playerSize: [640, 480], - context: 'instream' + context: 'instream', + mimes: ['video/x-ms-wmv, video/mp4'] } }, bids: [{ @@ -77,10 +78,10 @@ var adUnits = [ unit: '1611023124', delDomain: 'PUBLISHER-d.openx.net', video: { - mimes: ['video/x-ms-wmv, video/mp4'] + mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }] + }]p } ]; ``` diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index c1eeba62a29..efa205808c3 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -19,6 +19,57 @@ import {hook} from '../../../src/hook.js'; const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; +const BidRequestBuilder = function BidRequestBuilder(options) { + const defaults = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'openx' + }, + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + sizes: [[300, 250], [300, 600]], + }; + + const request = { + ...defaults.request, + ...options + }; + + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; + }; + + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'openx', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' + } + }; + + const request = { + ...defaults, + ...options + }; + + this.build = () => request; +}; + describe('OpenxRtbAdapter', function () { before(() => { hook.ready(); @@ -789,6 +840,12 @@ describe('OpenxRtbAdapter', function () { expect(request[0].data.regs.coppa).to.equal(1); }); + it('should send a coppa flag there is when there is coppa param settings in the bid params', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + request.params = {coppa: true}; + expect(request[0].data.regs.coppa).to.equal(1); + }); + after(function () { config.getConfig.restore() }); @@ -963,17 +1020,6 @@ describe('OpenxRtbAdapter', function () { }); context('FLEDGE', function() { - it('when FLEDGE is disabled, should not send imp.ext.ae', function () { - const request = spec.buildRequests( - bidRequestsWithMediaTypes, - { - ...mockBidderRequest, - fledgeEnabled: false - } - ); - expect(request[0].data.imp[0].ext).to.not.have.property('ae'); - }); - it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, { ...mockBidderRequest, @@ -997,6 +1043,45 @@ describe('OpenxRtbAdapter', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); }); + + it('Update imp.video with OpenRTB options from mimeTypes and params', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + protocols: [8] + } + }, + }).withParams({ + // options in video, will merge + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30 + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: 4, + protocols: [8], + w: 300, + h: 250 + }; + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests).to.have.lengthOf(2); + expect(requests[1].data.imp[0].video).to.deep.equal(expected); + }); }); } From 9a5553aaa8ce22833c3e6459e73f21f857639784 Mon Sep 17 00:00:00 2001 From: dzhang-criteo <87757739+dzhang-criteo@users.noreply.github.com> Date: Mon, 10 Apr 2023 16:15:08 +0200 Subject: [PATCH 288/375] Criteo Bid Adapter: Read GPP from ortb2 object (#9775) Add support of GPP consent string when it is present in ortb2 object. --- modules/criteoBidAdapter.js | 4 ++- test/spec/modules/criteoBidAdapter_spec.js | 29 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 7c2e317dc7f..9e179d7b332 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -418,7 +418,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { ext: bidderRequest.publisherExt, }, regs: { - coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined) + coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined), + gpp: bidderRequest.ortb2?.regs?.gpp, + gpp_sid: bidderRequest.ortb2?.regs?.gpp_sid }, slots: bidRequests.map(bidRequest => { networkId = bidRequest.params.networkId || networkId; diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index e958f1d0019..69574166481 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -883,6 +883,35 @@ describe('The Criteo bidding adapter', function () { expect(request.data.user.uspIab).to.equal('1YNY'); }); + it('should properly build a request with gpp consent field', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const ortb2 = { + regs: { + gpp: 'gpp_consent_string', + gpp_sid: [0, 1, 2] + } + }; + + const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); + expect(request.data.regs).to.not.be.null; + expect(request.data.regs.gpp).to.equal('gpp_consent_string'); + expect(request.data.regs.gpp_sid).to.deep.equal([0, 1, 2]); + }); + it('should properly build a request with schain object', function () { const expectedSchain = { someProperty: 'someValue' From 5b118fff41e6ac38ef38a65380dfeb75e69cefa4 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Mon, 10 Apr 2023 17:15:50 +0300 Subject: [PATCH 289/375] vidazoo Bid Adapter: update support of metaData (#9749) * 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: support metaData from server response. * remove forgotten only on spec. --------- Co-authored-by: Udi Talias Co-authored-by: roman --- modules/vidazooBidAdapter.js | 17 +++++++++++++---- test/spec/modules/vidazooBidAdapter_spec.js | 13 +++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index ca157ed3694..0d7aaa1a12d 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -227,7 +227,7 @@ function interpretResponse(serverResponse, request) { 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, metaData, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -241,11 +241,20 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - meta: { - advertiserDomains: advertiserDomains || [] - } }; + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + if (mediaType === BANNER) { Object.assign(response, { ad: ad, diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 97f8af97339..0429a2a51bf 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -477,6 +477,19 @@ describe('VidazooBidAdapter', function () { }); }); + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['vidazoo.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['vidazoo.com'], + agencyName: 'Agency Name' + }); + }); + it('should return an array of interpreted video responses', function () { const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); From 86e6ef552ea5c207aa8b5573e86283dbe841b517 Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Mon, 10 Apr 2023 08:19:47 -0600 Subject: [PATCH 290/375] Concert Bid Adapter: Enable support for additional userId's (#9780) * collect EIDs for bid request * add ad slot positioning to payload * RPO-2012: Update local storage name-spacing for c_uid (#8) * Updates c_uid namespacing to be more specific for concert * fixes unit tests * remove console.log * RPO-2012: Add check for shared id (#9) * Adds check for sharedId * Updates cookie name * remove trailing comma * [RPO-3152] Enable Support for GPP Consent (#12) * Adds gpp consent integration to concert bid adapter * Update tests to check for gpp consent string param * removes user sync endpoint and tests * updates comment * cleans up consentAllowsPpid function * comment fix * rename variables for clarity * fixes conditional logic for consent allows function (#13) * [RPO-3262] Update getUid function to check for pubcid and sharedid (#14) * Update getUid function to check for pubcid and sharedid * updates adapter version --------- Co-authored-by: antoin Co-authored-by: Antoin --- modules/concertBidAdapter.js | 21 ++++++++++++++------- test/spec/modules/concertBidAdapter_spec.js | 11 +++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 176729dd607..a25d9086446 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -42,9 +42,9 @@ export const spec = { pageUrl: bidderRequest.refererInfo.page, screen: [window.screen.width, window.screen.height].join('x'), debug: debugTurnedOn(), - uid: getUid(bidderRequest), + uid: getUid(bidderRequest, validBidRequests), optedOut: hasOptedOutOfPersonalization(), - adapterVersion: '1.1.1', + adapterVersion: '1.2.0', uspConsent: bidderRequest.uspConsent, gdprConsent: bidderRequest.gdprConsent, gppConsent: bidderRequest.gppConsent, @@ -158,16 +158,23 @@ export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * Check or generate a UID for the current user. */ -function getUid(bidderRequest) { +function getUid(bidderRequest, validBidRequests) { if (hasOptedOutOfPersonalization() || !consentAllowsPpid(bidderRequest)) { return false; } - const sharedId = deepAccess(bidderRequest, 'userId._sharedid.id'); + /** + * check for shareId or pubCommonId before generating a new one + * sharedId: @see https://docs.prebid.org/dev-docs/modules/userId.html + * pubCid (no longer supported): @see https://docs.prebid.org/dev-docs/modules/pubCommonId.html#adapter-integration + */ + const sharedId = + deepAccess(validBidRequests[0], 'userId.sharedid.id') || + deepAccess(validBidRequests[0], 'userId.pubcid') + const pubCid = deepAccess(validBidRequests[0], 'crumbs.pubcid'); - if (sharedId) { - return sharedId; - } + if (sharedId) return sharedId; + if (pubCid) return pubCid; const LEGACY_CONCERT_UID_KEY = 'c_uid'; const CONCERT_UID_KEY = 'vmconcert_uid'; diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index d5e7140a9f7..f5c807b4703 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -149,15 +149,10 @@ describe('ConcertAdapter', function () { it('should use sharedid if it exists', function() { storage.removeDataFromLocalStorage('c_nap'); - const request = spec.buildRequests(bidRequests, { - ...bidRequest, - userId: { - _sharedid: { - id: '123abc' - } - } - }); + const bidRequestsWithSharedId = [{ ...bidRequests[0], userId: { sharedid: { id: '123abc' } } }] + const request = spec.buildRequests(bidRequestsWithSharedId, bidRequest); const payload = JSON.parse(request.data); + expect(payload.meta.uid).to.equal('123abc'); }) From 48a7e4a9eacae068307c64406bb15da661d8c9bf Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 10 Apr 2023 15:09:04 +0000 Subject: [PATCH 291/375] Prebid 7.44.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 276fa15775a..66a5c5d0f67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.44.0-pre", + "version": "7.44.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c4efb745c42..86a456c26c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.44.0-pre", + "version": "7.44.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f270145ac91121e76171a560b53d808156728604 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 10 Apr 2023 15:09:05 +0000 Subject: [PATCH 292/375] Increment version to 7.45.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66a5c5d0f67..75ceaa1ac8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.44.0", + "version": "7.45.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 86a456c26c3..f710f319cd5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.44.0", + "version": "7.45.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 52696534eb3824e5a70b388f08c795737265fd84 Mon Sep 17 00:00:00 2001 From: Nisar Thadathil Date: Tue, 11 Apr 2023 15:05:10 +0530 Subject: [PATCH 293/375] vidoomy adapter: added bidfloor module (#9784) --- modules/vidoomyBidAdapter.js | 23 +++++++++++++- test/spec/modules/vidoomyBidAdapter_spec.js | 35 +++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index 08c7d1b31ac..29128998fd1 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -74,6 +74,26 @@ function serializeSupplyChainObj(schainObj) { return serializedSchain; } +/** + * Gets highest floor between getFloor.floor and params.bidfloor + * @param {Object} bid + * @param {Object} mediaType + * @param {Array} sizes + * @param {Number} bidfloor + * @returns {Number} floor + */ +function getBidFloor(bid, mediaType, sizes, bidfloor) { + let floor = bidfloor; + var size = sizes && sizes.length > 0 ? sizes[0] : '*'; + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({currency: 'USD', mediaType, size}); + if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(bidfloor, parseFloat(floorInfo.floor)); + } + } + return floor; +} + const isBidResponseValid = bid => { if (!bid || !bid.requestId || !bid.cpm || !bid.ttl || !bid.currency) { return false; @@ -106,6 +126,7 @@ const buildRequests = (validBidRequests, bidderRequest) => { const videoContext = deepAccess(bid, 'mediaTypes.video.context'); const bidfloor = deepAccess(bid, `params.bidfloor`, 0); + const floor = getBidFloor(bid, adType, sizes, bidfloor); const queryParams = { id: bid.params.id, @@ -120,7 +141,7 @@ const buildRequests = (validBidRequests, bidderRequest) => { pid: bid.params.pid, requestId: bid.bidId, schain: serializeSupplyChainObj(bid.schain) || '', - bidfloor, + bidfloor: floor, d: getDomainWithoutSubdomain(hostname), // 'vidoomy.com', // TODO: does the fallback make sense here? sp: encodeURIComponent(bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation), diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 8a3e61ca43a..607fdf4b5b8 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -161,6 +161,41 @@ describe('vidoomyBidAdapter', function() { expect(request[0].data).to.include.any.keys('schain'); expect(request[0].data.schain).to.eq(serializedForm); }); + + it('should set the bidfloor if getFloor module is undefined but static bidfloor is present', function () { + const request = { ...bidRequests[0], params: { bidfloor: 2.5 } } + const req = spec.buildRequests([request], bidderRequest)[0]; + expect(req.data).to.include.any.keys('bidfloor'); + expect(req.data.bidfloor).to.equal(2.5); + }); + + describe('floorModule', function () { + const getFloordata = { + 'currency': 'USD', + 'floor': 1.60 + }; + bidRequests[0].getFloor = _ => { + return getFloordata; + }; + it('should return getFloor.floor if present', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.bidfloor).to.equal(getFloordata.floor); + }); + it('should return the getFloor.floor if it is greater than static bidfloor', function () { + const bidfloor = 1.40; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data.bidfloor).to.equal(getFloordata.floor); + }); + it('should return the static bidfloor if it is greater than getFloor.floor', function () { + const bidfloor = 1.90; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data.bidfloor).to.equal(bidfloor); + }); + }); }); describe('interpretResponse', function () { From dd611c3859311b7b04266beaed3ba24e55a91e22 Mon Sep 17 00:00:00 2001 From: JulieLorin Date: Tue, 11 Apr 2023 13:00:04 +0200 Subject: [PATCH 294/375] Prebid core: fix image assets in converted legacy response (#9752) --- src/native.js | 6 +++++- test/spec/auctionmanager_spec.js | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/native.js b/src/native.js index c4413a1a6de..79a972371da 100644 --- a/src/native.js +++ b/src/native.js @@ -750,7 +750,11 @@ export function toLegacyResponse(ortbResponse, ortbRequest) { if (asset.title) { legacyResponse.title = asset.title.text; } else if (asset.img) { - legacyResponse[requestAsset.img.type === NATIVE_IMAGE_TYPES.MAIN ? 'image' : 'icon'] = asset.img.url; + legacyResponse[requestAsset.img.type === NATIVE_IMAGE_TYPES.MAIN ? 'image' : 'icon'] = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; } else if (asset.data) { legacyResponse[PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE[NATIVE_ASSET_TYPES_INVERSE[requestAsset.data.type]]] = asset.data.value; } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 03d0650effe..3e5a39e5ee5 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -1403,8 +1403,8 @@ describe('auctionmanager.js', function () { assert.equal(addedBid.native.title, 'Sample title') assert.equal(addedBid.native.sponsoredBy, 'Sample sponsoredBy') assert.equal(addedBid.native.clickUrl, 'http://www.click.com') - assert.equal(addedBid.native.image, 'https://www.example.com/image.png') - assert.equal(addedBid.native.icon, 'https://www.example.com/icon.png') + assert.equal(addedBid.native.image.url, 'https://www.example.com/image.png') + assert.equal(addedBid.native.icon.url, 'https://www.example.com/icon.png') assert.equal(addedBid.native.impressionTrackers[0], 'http://www.imptracker.com') assert.equal(addedBid.native.javascriptTrackers, '') }); From 5fdf321f91b8f0212c3aefd41637df7a54f59e6e Mon Sep 17 00:00:00 2001 From: Gaudeamus Date: Tue, 11 Apr 2023 17:14:48 +0300 Subject: [PATCH 295/375] Mgid Adapter: update & refactor (#9751) * upd - better support ortb2 - implement getUserSyncs - some refactoring * upd - better support ortb2 - implement getUserSyncs - some refactoring * upd * fix eids import --------- Co-authored-by: gaudeamus --- modules/mgidBidAdapter.js | 204 ++++++++++++++++++--- test/spec/modules/mgidBidAdapter_spec.js | 220 +++++++++++++++++++---- 2 files changed, 362 insertions(+), 62 deletions(-) diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 3691c5e60f0..0079936d803 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -1,9 +1,26 @@ -import { _each, deepAccess, isPlainObject, isArray, isStr, logInfo, parseUrl, isEmpty, triggerPixel, logWarn, getBidIdParameter, isFn, isNumber } from '../src/utils.js'; +import { + _each, + deepAccess, + isPlainObject, + isArray, + isStr, + logInfo, + parseUrl, + isEmpty, + triggerPixel, + logWarn, + getBidIdParameter, + isFn, + isNumber, + isBoolean, + isInteger, deepSetValue, +} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {USERSYNC_DEFAULT_CONFIG} from '../src/userSync.js'; const GVLID = 358; const DEFAULT_CUR = 'USD'; @@ -64,7 +81,7 @@ _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAs _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); export const spec = { - VERSION: '1.5', + VERSION: '1.6', code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, NATIVE], @@ -115,22 +132,19 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {BidRequest[]} validBidRequests A non-empty list of bid requests which should be sent to the Server. + * @param bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - + const [bidRequest] = validBidRequests; logInfo(LOG_INFO_PREFIX + `buildRequests`); if (validBidRequests.length === 0) { return; } const info = pageInfo(); - // TODO: the fallback seems to never be used here, and probably in the wrong order - const page = info.location || deepAccess(bidderRequest, 'refererInfo.page') - const hostname = parseUrl(page).hostname; - let domain = extractDomainFromHost(hostname) || hostname; const accountId = setOnAny(validBidRequests, 'params.accountId'); const muid = getLocalStorageSafely('mgMuidn'); let url = (setOnAny(validBidRequests, 'params.bidUrl') || ENDPOINT_URL) + accountId; @@ -182,31 +196,108 @@ export const spec = { let request = { id: deepAccess(bidderRequest, 'bidderRequestId'), - site: {domain, page}, + site: ortb2Data?.site || {}, cur: [cur], geo: {utcoffset: info.timeOffset}, - device: { - ua: navigator.userAgent, - js: 1, - dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, - h: screen.height, - w: screen.width, - language: getLanguage() - }, + device: ortb2Data?.device || {}, ext: { mgid_ver: spec.VERSION, prebid_ver: '$prebid.version$', - ...ortb2Data }, - imp + imp, + tmax: bidderRequest?.timeout || config.getConfig('bidderTimeout') || 500, }; - if (bidderRequest && bidderRequest.gdprConsent) { - request.user = {ext: {consent: bidderRequest.gdprConsent.consentString}}; - request.regs = {ext: {gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)}} + // request level + const bcat = ortb2Data?.bcat || bidRequest?.params?.bcat || []; + const badv = ortb2Data?.badv || bidRequest?.params?.badv || []; + const wlang = ortb2Data?.wlang || bidRequest?.params?.wlang || []; + if (bcat.length > 0) { + request.bcat = bcat; + } + if (badv.length > 0) { + request.badv = badv; + } + if (wlang.length > 0) { + request.wlang = wlang; + } + // site level + const page = deepAccess(bidderRequest, 'refererInfo.page') || info.location + if (!isStr(deepAccess(request.site, 'domain'))) { + const hostname = parseUrl(page).hostname; + request.site.domain = extractDomainFromHost(hostname) || hostname + } + if (!isStr(deepAccess(request.site, 'page'))) { + request.site.page = page + } + if (!isStr(deepAccess(request.site, 'ref'))) { + const ref = deepAccess(bidderRequest, 'refererInfo.ref') || info.referrer; + if (ref) { + request.site.ref = ref + } + } + // device level + if (!isStr(deepAccess(request.device, 'ua'))) { + request.device.ua = navigator.userAgent; + } + request.device.js = 1; + if (!isInteger(deepAccess(request.device, 'dnt'))) { + request.device.dnt = (navigator?.doNotTrack === 'yes' || navigator?.doNotTrack === '1' || navigator?.msDoNotTrack === '1') ? 1 : 0; + } + if (!isInteger(deepAccess(request.device, 'h'))) { + request.device.h = screen.height; } - if (info.referrer) { - request.site.ref = info.referrer + if (!isInteger(deepAccess(request.device, 'w'))) { + request.device.w = screen.width; + } + if (!isStr(deepAccess(request.device, 'language'))) { + request.device.language = getLanguage(); + } + // user & regs & privacy + if (isPlainObject(ortb2Data?.user)) { + request.user = ortb2Data.user; + } + if (isPlainObject(ortb2Data?.regs)) { + request.regs = ortb2Data.regs; + } + if (bidderRequest && isPlainObject(bidderRequest.gdprConsent)) { + if (!isStr(deepAccess(request.user, 'ext.consent'))) { + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent?.consentString); + } + if (!isBoolean(deepAccess(request.regs, 'ext.gdpr'))) { + deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent?.gdprApplies ? 1 : 0); + } + } + const userId = deepAccess(bidderRequest, 'userId') + if (isStr(userId)) { + deepSetValue(request, 'user.id', userId); + } + const eids = setOnAny(validBidRequests, 'userIdAsEids') + if (eids && eids.length > 0) { + deepSetValue(request, 'user.ext.eids', eids); + } + if (bidderRequest && isStr(bidderRequest.uspConsent)) { + if (!isBoolean(deepAccess(request.regs, 'ext.us_privacy'))) { + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } } + if (bidderRequest && isPlainObject(bidderRequest.gppConsent)) { + if (!isStr(deepAccess(request.regs, 'gpp'))) { + deepSetValue(request, 'regs.gpp', bidderRequest.gppConsent?.gppString); + } + if (!isArray(deepAccess(request.regs, 'gpp_sid'))) { + deepSetValue(request, 'regs.gpp_sid', bidderRequest.gppConsent?.applicableSections); + } + } + if (config.getConfig('coppa')) { + if (!isInteger(deepAccess(request.regs, 'coppa'))) { + deepSetValue(request, 'regs.coppa', 1); + } + } + const schain = setOnAny(validBidRequests, 'schain'); + if (schain) { + deepSetValue(request, 'source.ext.schain', schain); + } + logInfo(LOG_INFO_PREFIX + `buildRequest:`, request); return { method: 'POST', @@ -218,6 +309,7 @@ export const spec = { * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequests * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: (serverResponse, bidRequests) => { @@ -268,8 +360,66 @@ export const spec = { } logInfo(LOG_INFO_PREFIX + `onBidWon`); }, - getUserSyncs: (syncOptions, serverResponses) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { logInfo(LOG_INFO_PREFIX + `getUserSyncs`); + const spb = isPlainObject(config.getConfig('userSync')) && + isNumber(config.getConfig('userSync').syncsPerBidder) + ? config.getConfig('userSync').syncsPerBidder : USERSYNC_DEFAULT_CONFIG.syncsPerBidder; + + if (spb > 0 && isPlainObject(syncOptions) && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + let pixels = []; + if (serverResponses && + isArray(serverResponses) && + serverResponses.length > 0 && + isPlainObject(serverResponses[0].body) && + isPlainObject(serverResponses[0].body.ext) && + isArray(serverResponses[0].body.ext.cm) && + serverResponses[0].body.ext.cm.length > 0) { + pixels = serverResponses[0].body.ext.cm; + } + + const syncs = []; + const query = []; + query.push('cbuster=' + Math.round(new Date().getTime())); + query.push('consentData=' + encodeURIComponent(isPlainObject(gdprConsent) && isStr(gdprConsent?.consentString) ? gdprConsent.consentString : '')); + if (isPlainObject(gdprConsent) && typeof gdprConsent?.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { + query.push('gdprApplies=1'); + } else { + query.push('gdprApplies=0'); + } + if (isPlainObject(uspConsent) && uspConsent?.consentString) { + query.push(`uspString=${encodeURIComponent(uspConsent?.consentString)}`); + } + if (isPlainObject(gppConsent) && gppConsent?.gppString) { + query.push(`gppString=${encodeURIComponent(gppConsent?.gppString)}`); + } + if (config.getConfig('coppa')) { + query.push('coppa=1') + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://cm.mgid.com/i.html?' + query.join('&') + }); + } else if (syncOptions.pixelEnabled) { + if (pixels.length === 0) { + for (let i = 0; i < spb; i++) { + syncs.push({ + type: 'image', + url: 'https://cm.mgid.com/i.gif?' + query.join('&') // randomly selects partner if sync required + }); + } + } else { + for (let i = 0; i < spb && i < pixels.length; i++) { + syncs.push({ + type: 'image', + url: pixels[i] + (pixels[i].indexOf('?') > 0 ? '&' : '?') + query.join('&') + }); + } + } + } + return syncs; + } } }; @@ -287,6 +437,7 @@ function setOnAny(collection, key) { /** * Unpack the Server's Bid into a Prebid-compatible one. * @param serverBid + * @param cur * @return Bid */ function prebidBid(serverBid, cur) { @@ -330,7 +481,7 @@ function setMediaType(bid, newBid) { } function extractDomainFromHost(pageHost) { - if (pageHost == 'localhost') { + if (pageHost === 'localhost') { return 'localhost' } let domain = null; @@ -600,6 +751,7 @@ function pageInfo() { * Get the floor price from bid.params for backward compatibility. * If not found, then check floor module. * @param bid A valid bid object + * @param cur * @returns {*|number} floor price */ function getBidFloor(bid, cur) { diff --git a/test/spec/modules/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js index c79cad6245d..28530c4c4b4 100644 --- a/test/spec/modules/mgidBidAdapter_spec.js +++ b/test/spec/modules/mgidBidAdapter_spec.js @@ -1,7 +1,9 @@ -import {assert, expect} from 'chai'; +import {expect} from 'chai'; import { spec, storage } from 'modules/mgidBidAdapter.js'; import { version } from 'package.json'; import * as utils from '../../../src/utils.js'; +import {USERSYNC_DEFAULT_CONFIG} from '../../../src/userSync'; +import {config} from '../../../src/config'; describe('Mgid bid adapter', function () { let sandbox; @@ -21,10 +23,10 @@ describe('Mgid bid adapter', function () { const ua = navigator.userAgent; const screenHeight = screen.height; const screenWidth = screen.width; - const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; + const dnt = (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0; const language = navigator.language ? 'language' : 'userLanguage'; let lang = navigator[language].split('-')[0]; - if (lang.length != 2 && lang.length != 3) { + if (lang.length !== 2 && lang.length !== 3) { lang = ''; } const secure = window.location.protocol === 'https:' ? 1 : 0; @@ -36,7 +38,7 @@ describe('Mgid bid adapter', function () { }); describe('isBidRequestValid', function () { - let bid = { + let sbid = { 'adUnitCode': 'div', 'bidder': 'mgid', 'params': { @@ -46,26 +48,26 @@ describe('Mgid bid adapter', function () { }; it('should not accept bid without required params', function () { - let isValid = spec.isBidRequestValid(bid); + let isValid = spec.isBidRequestValid(sbid); expect(isValid).to.equal(false); }); it('should return false when params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when valid params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '', placementId: ''}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when valid params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = ''; bid.mediaTypes = { @@ -78,7 +80,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when adUnitCode not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = ''; bid.mediaTypes = { @@ -91,7 +93,7 @@ describe('Mgid bid adapter', function () { }); it('should return true when valid params are passed as nums', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = 'div'; bid.mediaTypes = { @@ -104,7 +106,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.mediaTypes = { native: { @@ -116,14 +118,14 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when valid mediaTypes.banner are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -132,7 +134,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes.banner.sizes are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -142,7 +144,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes.banner.sizes are not valid', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -152,7 +154,7 @@ describe('Mgid bid adapter', function () { }); it('should return true when valid params are passed as strings', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = 'div'; bid.params = {accountId: '1', placementId: '1'}; @@ -165,7 +167,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes.native is not object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { native: [] @@ -174,7 +176,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when mediaTypes.native is empty object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -184,7 +186,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when mediaTypes.native is invalid object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -198,7 +200,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when mediaTypes.native has unsupported required asset', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = {accountId: '2', placementId: '1'}; bid.mediaTypes = { native: { @@ -217,7 +219,7 @@ describe('Mgid bid adapter', function () { }); it('should return true when mediaTypes.native all assets needed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.adUnitCode = 'div'; bid.params = {accountId: '2', placementId: '1'}; bid.mediaTypes = { @@ -237,7 +239,7 @@ describe('Mgid bid adapter', function () { }); describe('override defaults', function () { - let bid = { + let sbid = { bidder: 'mgid', params: { accountId: '1', @@ -245,7 +247,7 @@ describe('Mgid bid adapter', function () { }, }; it('should return object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.mediaTypes = { banner: { sizes: [[300, 250]] @@ -257,7 +259,7 @@ describe('Mgid bid adapter', function () { }); it('should return overwrite default bidurl', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = { bidUrl: 'https://newbidurl.com/', accountId: '1', @@ -273,7 +275,7 @@ describe('Mgid bid adapter', function () { expect(request.url).to.include('https://newbidurl.com/1'); }); it('should return overwrite default bidFloor', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = { bidFloor: 1.1, accountId: '1', @@ -294,7 +296,7 @@ describe('Mgid bid adapter', function () { expect(data.imp[0].bidfloor).to.deep.equal(1.1); }); it('should return overwrite default currency', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = { cur: 'GBP', accountId: '1', @@ -323,6 +325,9 @@ describe('Mgid bid adapter', function () { placementId: '2', }, }; + afterEach(function () { + config.setConfig({coppa: undefined}) + }) it('should return undefined if no validBidRequests passed', function () { expect(spec.buildRequests([])).to.be.undefined; @@ -344,6 +349,7 @@ describe('Mgid bid adapter', function () { getDataFromLocalStorageStub.restore(); }); it('should proper handle gdpr', function () { + config.setConfig({coppa: 1}) let bid = Object.assign({}, abid); bid.mediaTypes = { banner: { @@ -351,12 +357,72 @@ describe('Mgid bid adapter', function () { } }; let bidRequests = [bid]; - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'gdpr', gdprApplies: true}}); + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'gdpr', gdprApplies: true}, uspConsent: 'usp', gppConsent: {gppString: 'gpp'}}); expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); expect(request.method).deep.equal('POST'); const data = JSON.parse(request.data); expect(data.user).deep.equal({ext: {consent: 'gdpr'}}); - expect(data.regs).deep.equal({ext: {gdpr: 1}}); + expect(data.regs).deep.equal({ext: {gdpr: 1, us_privacy: 'usp'}, gpp: 'gpp', coppa: 1}); + }); + it('should handle refererInfo', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const domain = 'site.com' + const page = `http://${domain}/site.html` + const ref = 'http://ref.com/ref.html' + const request = spec.buildRequests(bidRequests, {refererInfo: {page, ref}}); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.site.domain).to.deep.equal(domain); + expect(data.site.page).to.deep.equal(page); + expect(data.site.ref).to.deep.equal(ref); + }); + it('should handle schain', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.schain = ['schain1', 'schain2']; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + const data = JSON.parse(request.data); + expect(data.source).to.deep.equal({ext: {schain: bid.schain}}); + }); + it('should handle userId', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const bidderRequest = {userId: 'userid'}; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.user.id).to.deep.equal(bidderRequest.userId); + }); + it('should handle eids', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.userIdAsEids = ['eid1', 'eid2'] + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + const data = JSON.parse(request.data); + expect(data.user.ext.eids).to.deep.equal(bid.userIdAsEids); }); it('should return proper banner imp', function () { let bid = Object.assign({}, abid); @@ -386,7 +452,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"banner":{"w":300,"h":250}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"banner":{"w":300,"h":250}}],"tmax":3000}`, }); }); it('should not return native imp if minimum asset list not requested', function () { @@ -435,7 +501,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":11,"required":0,"data":{"type":1}}]}}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":11,"required":0,"data":{"type":1}}]}}}],"tmax":3000}`, }); }); it('should return proper native imp with image altered', function () { @@ -472,7 +538,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":492,"h":328,"wmin":50,"hmin":50}},{"id":3,"required":0,"img":{"type":1,"w":50,"h":50}},{"id":11,"required":0,"data":{"type":1}}]}}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":492,"h":328,"wmin":50,"hmin":50}},{"id":3,"required":0,"img":{"type":1,"w":50,"h":50}},{"id":11,"required":0,"data":{"type":1}}]}}}],"tmax":3000}`, }); }); it('should return proper native imp with sponsoredBy', function () { @@ -508,7 +574,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":4,"required":0,"data":{"type":1}}]}}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":4,"required":0,"data":{"type":1}}]}}}],"tmax":3000}`, }); }); it('should return proper banner request', function () { @@ -542,7 +608,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}],"tmax":3000}`, }); }); it('should proper handle ortb2 data', function () { @@ -555,7 +621,14 @@ describe('Mgid bid adapter', function () { let bidRequests = [bid]; let bidderRequest = { + gdprConsent: { + consentString: 'consent1', + gdprApplies: false, + }, ortb2: { + bcat: ['bcat1', 'bcat2'], + badv: ['badv1.com', 'badv2.com'], + wlang: ['l1', 'l2'], site: { content: { data: [{ @@ -571,6 +644,9 @@ describe('Mgid bid adapter', function () { } }, user: { + ext: { + consent: 'consent2 ', + }, data: [{ name: 'mgid.com', ext: { @@ -581,6 +657,11 @@ describe('Mgid bid adapter', function () { {'id': '987'}, ], }] + }, + regs: { + ext: { + gdpr: 1, + } } } }; @@ -589,7 +670,13 @@ describe('Mgid bid adapter', function () { expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); expect(request.method).deep.equal('POST'); const data = JSON.parse(request.data); - expect(data.ext).deep.include(bidderRequest.ortb2); + expect(data.bcat).deep.equal(bidderRequest.ortb2.bcat); + expect(data.badv).deep.equal(bidderRequest.ortb2.badv); + expect(data.wlang).deep.equal(bidderRequest.ortb2.wlang); + expect(data.site.content).deep.equal(bidderRequest.ortb2.site.content); + expect(data.regs).deep.equal(bidderRequest.ortb2.regs); + expect(data.user.data).deep.equal(bidderRequest.ortb2.user.data); + expect(data.user.ext).deep.equal(bidderRequest.ortb2.user.ext); }); }); @@ -727,8 +814,69 @@ describe('Mgid bid adapter', function () { }); describe('getUserSyncs', function () { - it('should do nothing on getUserSyncs', function () { - spec.getUserSyncs() + afterEach(function() { + config.setConfig({userSync: {syncsPerBidder: USERSYNC_DEFAULT_CONFIG.syncsPerBidder}}); + }); + it('should do nothing on getUserSyncs without inputs', function () { + expect(spec.getUserSyncs()).to.equal(undefined) + }); + it('should return frame object with empty consents', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&consentData=&gdprApplies=0/) + }); + it('should return frame object with gdpr consent', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}, undefined, {consentString: 'consent', gdprApplies: true}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&consentData=consent&gdprApplies=1/) + }); + it('should return frame object with gdpr + usp', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + }); + it('should return img object with gdpr + usp', function () { + config.setConfig({userSync: {syncsPerBidder: undefined}}); + const sync = spec.getUserSyncs({pixelEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(USERSYNC_DEFAULT_CONFIG.syncsPerBidder) + for (let i = 0; i < USERSYNC_DEFAULT_CONFIG.syncsPerBidder; i++) { + expect(sync[i]).to.have.property('type', 'image') + expect(sync[i]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + } + }); + it('should return frame object with gdpr + usp', function () { + const sync = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + }); + it('should return img (pixels) objects with gdpr + usp', function () { + const response = [{body: {ext: {cm: ['http://cm.mgid.com/i.gif?cdsp=1111', 'http://cm.mgid.com/i.gif']}}}] + const sync = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, response, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(2) + expect(sync[0]).to.have.property('type', 'image') + expect(sync[0]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cdsp=1111&cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + expect(sync[1]).to.have.property('type', 'image') + expect(sync[1]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2/) + }); + }); + + describe('getUserSyncs with img from ext.cm and gdpr + usp + coppa + gpp', function () { + afterEach(function() { + config.setConfig({coppa: undefined}) + }); + it('should return img (pixels) objects with gdpr + usp + coppa + gpp', function () { + config.setConfig({coppa: 1}); + const response = [{body: {ext: {cm: ['http://cm.mgid.com/i.gif?cdsp=1111', 'http://cm.mgid.com/i.gif']}}}] + const sync = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, response, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}, {gppString: 'gpp'}) + expect(sync).to.have.length(2) + expect(sync[0]).to.have.property('type', 'image') + expect(sync[0]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cdsp=1111&cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2&gppString=gpp&coppa=1/) + expect(sync[1]).to.have.property('type', 'image') + expect(sync[1]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&consentData=consent1&gdprApplies=1&uspString=consent2&gppString=gpp&coppa=1/) }); }); From 60bd4ed88ff306ee215ab98865ae93db6f587ca4 Mon Sep 17 00:00:00 2001 From: Justin Quinn <42960493+Justin-Quinn51@users.noreply.github.com> Date: Tue, 11 Apr 2023 10:53:10 -0400 Subject: [PATCH 296/375] Various Bid Adapters: point tmax at request object (#9770) * Removed 'CreateEidsArray' import statements, as well as references to it within corresponding functions. Added references to 'userIdAsEids' property as an alternative * Revert "Removed 'CreateEidsArray' import statements, as well as references to it within corresponding functions. Added references to 'userIdAsEids' property as an alternative" This reverts commit f654a5b75f70485e98d8e630d6302f258baa7624. * passed all tests * passed tests with bluebillywigAdapter * Impactify bid adapter passed tests * Mediakeys bid adapter passing tests * Sharethrough bid adapter passed tests * Connectad bid adapter passed tests * Added tests back to expectedEids object, and adjusted userIdAsEids array to reflect changes * Yieldmo bid adapter passed tests * Smartadserver bid adapter passed tests * Removed unnecessary reassignment of bidUserId to eids * Removed unnecessary reassignment of bidUserId to eids * Improveddigital bid adapter passed tests * Yieldmo bid adapter passed tests * Sovrn bid adapter passed tests * Ttd bid adapter passed tests * Refactored adapters to draw tmax values from bidderRequest object and ensured they were passing tests * Update nexx360BidAdapter.js * Deleted unrelated ttsBidAdapter file from PR * Deleted unrelated ttdBidAdapter_spec file from PR * add back ttd adapter * add ttd spec flle back * add new line --------- Co-authored-by: Patrick McCann Co-authored-by: Chris Huie --- modules/acuityAdsBidAdapter.js | 2 +- modules/amxBidAdapter.js | 2 +- modules/appushBidAdapter.js | 2 +- modules/beyondmediaBidAdapter.js | 2 +- modules/braveBidAdapter.js | 2 +- modules/brightcomBidAdapter.js | 2 +- modules/brightcomSSPBidAdapter.js | 2 +- modules/coinzillaBidAdapter.js | 3 +-- modules/compassBidAdapter.js | 2 +- modules/contentexchangeBidAdapter.js | 2 +- modules/glimpseBidAdapter.js | 3 +-- modules/globalsunBidAdapter.js | 2 +- modules/gridBidAdapter.js | 2 +- modules/gridNMBidAdapter.js | 2 +- modules/gumgumBidAdapter.js | 2 +- modules/iqzoneBidAdapter.js | 2 +- modules/koblerBidAdapter.js | 3 +-- modules/kueezBidAdapter.js | 2 +- modules/luponmediaBidAdapter.js | 2 +- modules/mathildeadsBidAdapter.js | 2 +- modules/medianetBidAdapter.js | 2 +- modules/minutemediaBidAdapter.js | 2 +- modules/onomagicBidAdapter.js | 3 +-- modules/operaadsBidAdapter.js | 2 +- modules/pubmaticBidAdapter.js | 2 +- modules/riseBidAdapter.js | 2 +- modules/sharethroughBidAdapter.js | 2 +- modules/shinezBidAdapter.js | 2 +- modules/smarthubBidAdapter.js | 2 +- modules/synacormediaBidAdapter.js | 4 +-- modules/truereachBidAdapter.js | 3 +-- modules/videoheroesBidAdapter.js | 2 +- modules/visiblemeasuresBidAdapter.js | 2 +- modules/vrtcalBidAdapter.js | 2 +- modules/yieldoneBidAdapter.js | 2 +- test/spec/modules/acuityAdsBidAdapter_spec.js | 3 ++- test/spec/modules/appushBidAdapter_spec.js | 3 ++- .../modules/beyondmediaBidAdapter_spec.js | 3 ++- test/spec/modules/compassBidAdapter_spec.js | 3 ++- .../modules/contentexchangeBidAdapter_spec.js | 3 ++- test/spec/modules/globalsunBidAdapter_spec.js | 3 ++- test/spec/modules/iqzoneBidAdapter_spec.js | 3 ++- test/spec/modules/koblerBidAdapter_spec.js | 20 -------------- .../spec/modules/luponmediaBidAdapter_spec.js | 2 +- .../modules/mathildeadsBidAdapter_spec.js | 3 ++- .../modules/sharethroughBidAdapter_spec.js | 3 ++- test/spec/modules/smarthubBidAdapter_spec.js | 3 ++- .../modules/synacormediaBidAdapter_spec.js | 26 ------------------- .../modules/visiblemeasuresBidAdapter_spec.js | 3 ++- test/spec/modules/vrtcalBidAdapter_spec.js | 5 ++-- 50 files changed, 61 insertions(+), 102 deletions(-) diff --git a/modules/acuityAdsBidAdapter.js b/modules/acuityAdsBidAdapter.js index f469fe48c60..b0bb132ddae 100644 --- a/modules/acuityAdsBidAdapter.js +++ b/modules/acuityAdsBidAdapter.js @@ -150,7 +150,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index cc2cd120779..68a3a370c01 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -332,7 +332,7 @@ export const spec = { m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, fpd2: bidderRequest.ortb2, - tmax: config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, amp: refInfo(bidderRequest, 'isAmp', null), ri: buildReferrerInfo(bidderRequest), sync: getSyncSettings(), diff --git a/modules/appushBidAdapter.js b/modules/appushBidAdapter.js index 1ad8fe27e42..97772b65e45 100644 --- a/modules/appushBidAdapter.js +++ b/modules/appushBidAdapter.js @@ -154,7 +154,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/beyondmediaBidAdapter.js b/modules/beyondmediaBidAdapter.js index 57c141dc2aa..bbcd972470c 100644 --- a/modules/beyondmediaBidAdapter.js +++ b/modules/beyondmediaBidAdapter.js @@ -145,7 +145,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js index 2e087b41868..ea8b4af690c 100644 --- a/modules/braveBidAdapter.js +++ b/modules/braveBidAdapter.js @@ -83,7 +83,7 @@ export const spec = { domain: parseUrl(page).hostname, page: page, }, - tmax: bidderRequest.timeout || config.getConfig('bidderTimeout') || 500, + tmax: bidderRequest.timeout, imp }; diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index 6c7223a562c..c4cc5394a03 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -68,7 +68,7 @@ function buildRequests(bidReqs, bidderRequest) { w: screen.width, h: screen.height }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest?.timeout }; if (bidderRequest && bidderRequest.gdprConsent) { diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js index 8ace326e3b0..b85a01c8fc7 100644 --- a/modules/brightcomSSPBidAdapter.js +++ b/modules/brightcomSSPBidAdapter.js @@ -84,7 +84,7 @@ function buildRequests(bidReqs, bidderRequest) { w: screen.width, h: screen.height }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest?.timeout }; if (bidderRequest?.gdprConsent) { diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js index 7e9fb964a87..15731423c49 100644 --- a/modules/coinzillaBidAdapter.js +++ b/modules/coinzillaBidAdapter.js @@ -1,5 +1,4 @@ import { parseSizesInput } from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'coinzilla'; @@ -78,7 +77,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: bidRequest.timeout, referrer: referrer, ad: response.ad, mediaType: response.mediaType, diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js index a89c4c617b6..addcdfebb27 100644 --- a/modules/compassBidAdapter.js +++ b/modules/compassBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js index becc21555e3..be5900407ea 100644 --- a/modules/contentexchangeBidAdapter.js +++ b/modules/contentexchangeBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js index 3847a72368a..53dc0bd3e1a 100644 --- a/modules/glimpseBidAdapter.js +++ b/modules/glimpseBidAdapter.js @@ -1,5 +1,4 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { @@ -99,7 +98,7 @@ function getReferer(bidderRequest) { function buildQuery(bidderRequest) { let url = appendQueryParam(ENDPOINT, 'ver', '$prebid.version$'); - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; url = appendQueryParam(url, 'tmax', timeout); if (gdprApplies(bidderRequest)) { diff --git a/modules/globalsunBidAdapter.js b/modules/globalsunBidAdapter.js index ae3407bbbdd..5b5d97c2cac 100644 --- a/modules/globalsunBidAdapter.js +++ b/modules/globalsunBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 36cfb4bedc3..792dc3b15b0 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -93,7 +93,7 @@ export const spec = { let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo, gppConsent} = bidderRequest || {}; const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; - const tmax = timeout || config.getConfig('bidderTimeout'); + const tmax = timeout; const imp = []; const bidsMap = {}; const requests = []; diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index 41689aeeb55..36038d521bd 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -135,7 +135,7 @@ export const spec = { reqSource.ext.schain = schain; } - const bidderTimeout = config.getConfig('bidderTimeout') || timeout; + const bidderTimeout = timeout; const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; const request = { diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 5203f190c09..8a6c4efa7fc 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -284,7 +284,7 @@ function buildRequests(validBidRequests, bidderRequest) { const gdprConsent = bidderRequest && bidderRequest.gdprConsent; const uspConsent = bidderRequest && bidderRequest.uspConsent; const gppConsent = bidderRequest && bidderRequest.gppConsent; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest && bidderRequest.timeout const coppa = config.getConfig('coppa') === true ? 1 : 0; const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page; _each(validBidRequests, bidRequest => { diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index f2de447d6a7..52f3be7e4b4 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -154,7 +154,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 1dc22d0099a..4eef99024f9 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -17,7 +17,6 @@ const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; const DEV_BIDDER_ENDPOINT = 'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'; const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout'; const SUPPORTED_CURRENCY = 'USD'; -const DEFAULT_TIMEOUT = 1000; const TIME_TO_LIVE_IN_SECONDS = 10 * 60; export const isBidRequestValid = function (bid) { @@ -124,7 +123,7 @@ function getPageUrlFromRefererInfo() { function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { const imps = validBidRequests.map(buildOpenRtbImpObject); - const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; + const timeout = bidderRequest.timeout; const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) const request = { id: bidderRequest.auctionId, diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js index 24d339d1938..6efb67ddec8 100644 --- a/modules/kueezBidAdapter.js +++ b/modules/kueezBidAdapter.js @@ -293,7 +293,7 @@ function generateSharedParams(sharedParams, bidderRequest) { const generalBidParams = getBidIdParameter('params', sharedParams); const userIds = getBidIdParameter('userId', sharedParams); const ortb2Metadata = bidderRequest.ortb2 || {}; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; const params = { adapter_version: VERSION, diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 835c04ba074..059a07b9999 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -292,7 +292,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { source: { tid: bidRequest.transactionId }, - tmax: config.getConfig('timeout') || 1500, + tmax: bidderRequest.timeout, imp: currentImps.concat([{ id: bidRequest.bidId, secure: 1, diff --git a/modules/mathildeadsBidAdapter.js b/modules/mathildeadsBidAdapter.js index 6b886b72955..929cee8f3c0 100644 --- a/modules/mathildeadsBidAdapter.js +++ b/modules/mathildeadsBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 5f54b2e3ff3..c398d8fd5db 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -334,7 +334,7 @@ function generatePayload(bidRequests, bidderRequests) { id: bidRequests[0].auctionId, imp: bidRequests.map(request => slotParams(request)), ortb2: bidderRequests.ortb2, - tmax: bidderRequests.timeout || config.getConfig('bidderTimeout') + tmax: bidderRequests.timeout } } diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index d953558bf31..a80a37f5ead 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -366,7 +366,7 @@ function generateGeneralParams(generalObject, bidderRequest) { const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; const {bidderCode} = bidderRequest; const generalBidParams = generalObject.params; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; // these params are snake_case instead of camelCase to allow backwards compatability on the server. // in the future, these will be converted to camelCase to match our convention. diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index d99455f3f73..edab625e541 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -13,7 +13,6 @@ import { } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; const BIDDER_CODE = 'onomagic'; const URL = 'https://bidder.onomagic.com/hb'; @@ -80,7 +79,7 @@ function buildRequests(bidReqs, bidderRequest) { w: screen.width, h: screen.height }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest?.timeout }; return { diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index aa548debf32..48c6246ce6b 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -228,7 +228,7 @@ function buildOpenRtbBidRequest(bidRequest, bidderRequest) { // build OpenRTB request body const payload = { id: bidderRequest.auctionId, - tmax: bidderRequest.timeout || config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, test: config.getConfig('debug') ? 1 : 0, imp: createImp(bidRequest), device: getDevice(), diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 67f8f183783..43a688f20e9 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1211,7 +1211,7 @@ export const spec = { // bidderRequest has timeout property if publisher sets during calling requestBids function from page // if not bidderRequest contains global value set by Prebid if (bidderRequest?.timeout) { - payload.tmax = bidderRequest.timeout || config.getConfig('bidderTimeout'); + payload.tmax = bidderRequest.timeout; } else { payload.tmax = window?.PWT?.versionDetails?.timeout; } diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 0c4d6148f2b..89e4e85c627 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -384,7 +384,7 @@ function generateGeneralParams(generalObject, bidderRequest) { const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; const {bidderCode} = bidderRequest; const generalBidParams = generalObject.params; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; // these params are snake_case instead of camelCase to allow backwards compatability on the server. // in the future, these will be converted to camelCase to match our convention. diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 07dc065a419..9c91af8b130 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -21,7 +21,7 @@ export const sharethroughAdapterSpec = { isBidRequestValid: bid => !!bid.params.pkey && bid.bidder === BIDDER_CODE, buildRequests: (bidRequests, bidderRequest) => { - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; const firstPartyData = bidderRequest.ortb2 || {}; const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 0ce2eed6479..f93736894f5 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -364,7 +364,7 @@ function generateGeneralParams(generalObject, bidderRequest) { const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; const {bidderCode} = bidderRequest; const generalBidParams = generalObject.params; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; // these params are snake_case instead of camelCase to allow backwards compatability on the server. // in the future, these will be converted to camelCase to match our convention. diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 6aab6a8b57e..2889bd5358b 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -125,7 +125,7 @@ function buildRequestParams(bidderRequest = {}, placements = []) { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; } diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 4ebed12cb05..7165f778e5f 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -49,9 +49,7 @@ export const spec = { imp: [] }; - const callbackTimeout = bidderRequest.timeout; - const globalTimeout = config.getConfig('bidderTimeout'); - const tmax = globalTimeout ? Math.min(globalTimeout, callbackTimeout) : callbackTimeout; + const tmax = bidderRequest.timeout; if (tmax) { openRtbBidRequest.tmax = tmax; } diff --git a/modules/truereachBidAdapter.js b/modules/truereachBidAdapter.js index e343842543d..13bb558fbf6 100755 --- a/modules/truereachBidAdapter.js +++ b/modules/truereachBidAdapter.js @@ -1,6 +1,5 @@ import { deepAccess, getUniqueIdentifierStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; const SUPPORTED_AD_TYPES = [BANNER]; @@ -140,7 +139,7 @@ function buildCommonQueryParamsFromBids(validBidRequests, bidderRequest) { device: { ua: window.navigator.userAgent }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; return defaultParams; diff --git a/modules/videoheroesBidAdapter.js b/modules/videoheroesBidAdapter.js index 4818d9e1c58..e992fae1d06 100644 --- a/modules/videoheroesBidAdapter.js +++ b/modules/videoheroesBidAdapter.js @@ -80,7 +80,7 @@ export const spec = { domain: parseUrl(page).hostname, page: page, }, - tmax: bidderRequest.timeout || config.getConfig('bidderTimeout') || 500, + tmax: bidderRequest.timeout, imp }; diff --git a/modules/visiblemeasuresBidAdapter.js b/modules/visiblemeasuresBidAdapter.js index 89dcf36917f..e77477c812b 100644 --- a/modules/visiblemeasuresBidAdapter.js +++ b/modules/visiblemeasuresBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/vrtcalBidAdapter.js b/modules/vrtcalBidAdapter.js index 301d278493b..e41a5da50cd 100644 --- a/modules/vrtcalBidAdapter.js +++ b/modules/vrtcalBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { coppa = 1; } - tmax = config.getConfig('bidderTimeout'); + tmax = bid.timeout; const params = { prebidJS: 1, diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index d408a0595f9..fd804eed2e7 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { const bidId = bidRequest.bidId; const transactionId = bidRequest.transactionId; const unitCode = bidRequest.adUnitCode; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; const language = window.navigator.language; const screenSize = window.screen.width + 'x' + window.screen.height; const payload = { diff --git a/test/spec/modules/acuityAdsBidAdapter_spec.js b/test/spec/modules/acuityAdsBidAdapter_spec.js index 18ea574c1ce..05c59036ff3 100644 --- a/test/spec/modules/acuityAdsBidAdapter_spec.js +++ b/test/spec/modules/acuityAdsBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('AcuityAdsBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/appushBidAdapter_spec.js b/test/spec/modules/appushBidAdapter_spec.js index 91c50cc3dd0..e6af98c0f33 100644 --- a/test/spec/modules/appushBidAdapter_spec.js +++ b/test/spec/modules/appushBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('AppushBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index a4c1125fa5f..751b3ae1098 100644 --- a/test/spec/modules/beyondmediaBidAdapter_spec.js +++ b/test/spec/modules/beyondmediaBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('AndBeyondMediaBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js index 28021c4f7c0..6a761e63ea1 100644 --- a/test/spec/modules/compassBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('CompassBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js index 368ca8d9e3f..1b3dc4f19c9 100644 --- a/test/spec/modules/contentexchangeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -79,7 +79,8 @@ describe('ContentexchangeBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/globalsunBidAdapter_spec.js b/test/spec/modules/globalsunBidAdapter_spec.js index 3795f3038a7..0d17c25363d 100644 --- a/test/spec/modules/globalsunBidAdapter_spec.js +++ b/test/spec/modules/globalsunBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('GlobalsunBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index de0459f4714..2e920d3b769 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('IQZoneBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 5bcf62ff95d..5fb0bef726e 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -366,26 +366,6 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.imp[1].pmp.deals[1].id).to.be.equal(dealIds2[1]); }); - it('should read timeout from config', function () { - const timeout = 4000; - const validBidRequests = [createValidBidRequest()]; - // No timeout field - const bidderRequest = { - auctionId: 'c1243d83-0bed-4fdb-8c76-42b456be17d0', - refererInfo: { - page: 'example.com' - } - }; - config.setConfig({ - bidderTimeout: timeout - }); - - const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); - - expect(openRtbRequest.tmax).to.be.equal(timeout); - }); - it('should read floor price using floors module', function () { const floorPriceFor580x400 = 6.5148; const floorPriceForAnySize = 4.2343; diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 9f109ff1892..c8d4c18c407 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -133,7 +133,7 @@ describe('luponmediaBidAdapter', function () { } ], 'auctionStart': 1587413920820, - 'timeout': 2000, + 'timeout': 1500, 'refererInfo': { 'page': 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines', 'reachedTop': true, diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index 0f0da6032eb..107906ec83d 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -74,7 +74,8 @@ describe('MathildeAdsBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index f635791aeed..fc4fbc86018 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -248,7 +248,8 @@ describe('sharethrough adapter spec', function () { refererInfo: { ref: 'https://referer.com', }, - auctionId: 'auction-id' + auctionId: 'auction-id', + timeout: 242 }; }); diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index e1787dfe880..e01d0c72f6b 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -91,7 +91,8 @@ describe('SmartHubBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { page: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index 0773d29789d..747dc4edc63 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -313,32 +313,6 @@ describe('synacormediaBidAdapter ', function () { expect(req.data.tmax).to.eql(bidderRequestWithTimeout.timeout); }); - it('should return tmax equal to smaller global timeout', function () { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'bidderTimeout': bidderRequestWithTimeout.timeout - 100 - }; - return config[key]; - }); - let req = spec.buildRequests([validBidRequest], bidderRequestWithTimeout); - sandbox.restore(); - expect(req.data.tmax).to.eql(bidderRequestWithTimeout.timeout - 100); - }); - - it('should return tmax equal to smaller callback timeout', function () { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'bidderTimeout': bidderRequestWithTimeout.timeout + 100 - }; - return config[key]; - }); - let req = spec.buildRequests([validBidRequest], bidderRequestWithTimeout); - sandbox.restore(); - expect(req.data.tmax).to.eql(bidderRequestWithTimeout.timeout); - }); - it('should return multiple bids when multiple valid requests with the same seatId are used', function () { let secondBidRequest = { bidId: 'foobar', diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js index 93eeb7b556c..ad75e17699f 100644 --- a/test/spec/modules/visiblemeasuresBidAdapter_spec.js +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -78,7 +78,8 @@ describe('VisibleMeasuresBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/vrtcalBidAdapter_spec.js b/test/spec/modules/vrtcalBidAdapter_spec.js index d911745d378..491c96df5e2 100644 --- a/test/spec/modules/vrtcalBidAdapter_spec.js +++ b/test/spec/modules/vrtcalBidAdapter_spec.js @@ -28,7 +28,8 @@ describe('vrtcalBidAdapter', function () { 'bidId': 'bidID0001', 'bidderRequestId': 'br0001', 'auctionId': 'auction0001', - 'userIdAsEids': {} + 'userIdAsEids': {}, + timeout: 435 } ]; @@ -95,7 +96,7 @@ describe('vrtcalBidAdapter', function () { w: 300, h: 250, crid: 'v2_1064_vrt_vrtcaltestdisplay2_300_250', - adomain: ['vrtcal.com'] + adomain: ['vrtcal.com'], }], seat: '16' }], From 20fdfcc45988a706dc9fde6940dceff0833b7745 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 11 Apr 2023 07:56:26 -0700 Subject: [PATCH 297/375] OpenXOrtb Bid Adapter: fix multiformat requests (#9790) * OpenXOrtb: fix multiformat requests * pay attention to feature tags * refactor & cleanup --- modules/openxOrtbBidAdapter.js | 24 ++++++++++--------- test/spec/modules/openxOrtbBidAdapter_spec.js | 12 ++++++++++ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/modules/openxOrtbBidAdapter.js b/modules/openxOrtbBidAdapter.js index fbdf96fb72a..5afee034d5f 100644 --- a/modules/openxOrtbBidAdapter.js +++ b/modules/openxOrtbBidAdapter.js @@ -29,9 +29,6 @@ const converter = ortbConverter({ }, imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); - if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') { - imp.video.placement = imp.video.placement || 4; - } mergeDeep(imp, { tagid: bidRequest.params.unit, ext: { @@ -132,15 +129,20 @@ const converter = ortbConverter({ } }, video(orig, imp, bidRequest, context) { - // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] - // to populate imp.video - // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` - let videoParams = bidRequest.mediaTypes[VIDEO]; - if (videoParams) { - videoParams = Object.assign({}, videoParams, bidRequest.params.video); - bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + if (FEATURES.VIDEO) { + // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] + // to populate imp.video + // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + if (imp.video && videoParams?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; + } } - orig(imp, bidRequest, context); } } } diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index efa205808c3..951399457c3 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -300,6 +300,18 @@ describe('OpenxRtbAdapter', function () { }); context('common requests checks', function() { + it('should be able to handle multiformat requests', () => { + const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] + } + const requests = spec.buildRequests([multiformat], mockBidderRequest); + const outgoingFormats = requests.flatMap(rq => rq.data.imp.flatMap(imp => ['banner', 'video'].filter(k => imp[k] != null))); + const expected = FEATURES.VIDEO ? ['banner', 'video'] : ['banner'] + expect(outgoingFormats).to.have.members(expected); + }) + it('should send bid request to openx url via POST', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].url).to.equal(REQUEST_URL); From 44f52c4fa8d78535d2ddabb5476550514524097b Mon Sep 17 00:00:00 2001 From: xwang202 <57196235+xwang202@users.noreply.github.com> Date: Wed, 12 Apr 2023 00:54:58 +0800 Subject: [PATCH 298/375] FreeWheel SSP Bid Adapter: support video context and placement (#9792) * 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 * FreeWheel SSP Bid Adapter: support video context and placement * update test --- modules/freewheel-sspBidAdapter.js | 8 ++++ .../modules/freewheel-sspBidAdapter_spec.js | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index c5fb813540e..8f5af48d16b 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -424,6 +424,14 @@ export const spec = { requestParams.playerSize = playerSize[0] + 'x' + playerSize[1]; } + // Add video context and placement in requestParams + if (currentBidRequest.mediaTypes.video) { + var videoContext = currentBidRequest.mediaTypes.video.context ? currentBidRequest.mediaTypes.video.context : 'instream'; + var videoPlacement = currentBidRequest.mediaTypes.video.placement ? currentBidRequest.mediaTypes.video.placement : 1; + requestParams.video_context = videoContext; + requestParams.video_placement = videoPlacement; + } + return { method: 'GET', url: FREEWHEEL_ADSSETUP, diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index fe07dc1e719..123981825dc 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -252,6 +252,13 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; + it('should return context and placement with default values', () => { + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload.video_context).to.equal('instream'); ; + expect(payload.video_placement).to.equal(1); + }); + it('should add parameters to the tag', () => { const request = spec.buildRequests(bidRequests); const payload = request[0].data; @@ -319,6 +326,36 @@ describe('freewheelSSP BidAdapter Test', () => { }); }) + describe('buildRequestsForVideoWithContextAndPlacement', () => { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'placement': 2, + 'playerSize': [300, 600], + } + }, + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should return input context and placement', () => { + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload.video_context).to.equal('outstream'); ; + expect(payload.video_placement).to.equal(2); + }); + }) + describe('interpretResponseForBanner', () => { let bidRequests = [ { From f39dcf2684ae073b7a96970b763d0723e205edb3 Mon Sep 17 00:00:00 2001 From: shahinrahbariasl <56240400+shahinrahbariasl@users.noreply.github.com> Date: Tue, 11 Apr 2023 22:24:01 -0400 Subject: [PATCH 299/375] IX Bid Adapter: refactor build request method and ft improves (#9793) Co-authored-by: shahin.rahbariasl --- modules/ixBidAdapter.js | 147 +++++++---------------- test/spec/modules/ixBidAdapter_spec.js | 155 ++++++++++++++----------- 2 files changed, 126 insertions(+), 176 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index ee07c34fb95..e791d933352 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -13,7 +13,6 @@ import { logError, logWarn, mergeDeep, - parseQueryStringParameters, safeJSONParse } from '../src/utils.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; @@ -104,9 +103,12 @@ export const LOCAL_STORAGE_FEATURE_TOGGLES_KEY = `${BIDDER_CODE}_features`; let hasRegisteredHandler = false; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const FEATURE_TOGGLES = { + // Update with list of CFTs to be requested from Exchange + REQUESTED_FEATURE_TOGGLES: [], + featureToggles: {}, isFeatureEnabled: function (ft) { - return deepAccess(this.featureToggles, `features.${ft}.activated`) + return deepAccess(this.featureToggles, `features.${ft}.activated`, false) }, getFeatureToggles: function () { if (storage.localStorageIsEnabled()) { @@ -152,11 +154,6 @@ const MEDIA_TYPES = { Native: 4 }; -let baseRequestSize = 0; -let currentRequestSize = 0; -let wasAdUnitImpressionsTrimmed = false; -let currentImpressionSize = 0; - /** * Transform valid bid request config object to banner impression object that will be sent to ad server. * @@ -598,19 +595,12 @@ function getEidInfo(allEids) { * */ function buildRequest(validBidRequests, bidderRequest, impressions, version) { - baseRequestSize = 0; - currentRequestSize = 0; - wasAdUnitImpressionsTrimmed = false; - currentImpressionSize = 0; - // Always use secure HTTPS protocol. let baseUrl = SECURE_BID_URL; // Get ids from Prebid User ID Modules let eidInfo = getEidInfo(deepAccess(validBidRequests, '0.userIdAsEids')); let userEids = eidInfo.toSend; - let MAX_REQUEST_SIZE = 8000; - // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist if (window.headertag && typeof window.headertag.getIdentityInfo === 'function') { @@ -625,6 +615,9 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const requests = []; let r = createRequest(validBidRequests); + // Add FTs to be requested from Exchange + r = addRequestedFeatureToggles(r, FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES) + // getting ixdiags for adunits of the video, outstream & multi format (MF) style let ixdiag = buildIXDiag(validBidRequests); for (var key in ixdiag) { @@ -636,25 +629,17 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r = applyRegulations(r, bidderRequest); let payload = {}; - createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload, MAX_REQUEST_SIZE); + createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload); - let requestSequenceNumber = 0; const transactionIds = Object.keys(impressions); let isFpdAdded = false; for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { - // buildRequestV2 does not have request spliting logic. - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { - if (currentRequestSize >= MAX_REQUEST_SIZE) { - break; - } - } if (requests.length >= MAX_REQUEST_LIMIT) { break; } - r = addImpressions(impressions, transactionIds, r, adUnitIndex, MAX_REQUEST_SIZE); - currentRequestSize += currentImpressionSize; + r = addImpressions(impressions, transactionIds, r, adUnitIndex); const fpd = deepAccess(bidderRequest, 'ortb2') || {}; const site = { ...(fpd.site || fpd.context) }; @@ -667,31 +652,17 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { clonedRObject.site = mergeDeep({}, clonedRObject.site, site); clonedRObject.user = mergeDeep({}, clonedRObject.user, user); - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; - - if (requestSize < MAX_REQUEST_SIZE) { - r.site = mergeDeep({}, r.site, site); - r.user = mergeDeep({}, r.user, user); - isFpdAdded = true; - const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; - currentRequestSize += fpdRequestSize; - } else { - logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE }); - } + r.site = mergeDeep({}, r.site, site); + r.user = mergeDeep({}, r.user, user); + isFpdAdded = true; } // add identifiers info to ixDiag - r = addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl, MAX_REQUEST_SIZE); + r = addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl); const isLastAdUnit = adUnitIndex === transactionIds.length - 1; - if (wasAdUnitImpressionsTrimmed || isLastAdUnit) { - if (!isLastAdUnit || requestSequenceNumber) { - r.ext.ixdiag.sn = requestSequenceNumber; - } - - requestSequenceNumber++; - + if (isLastAdUnit) { requests.push({ method: 'POST', url: baseUrl + '?s=' + siteID, @@ -702,7 +673,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { validBidRequests }); - currentRequestSize = baseRequestSize; r.imp = []; isFpdAdded = false; } @@ -751,6 +721,24 @@ function createRequest(validBidRequests) { return r } +/** + * Adds requested feature toggles to the provided request object to be sent to Exchange. + * @param {object} r - The request object to add feature toggles to. + * @param {Array} requestedFeatureToggles - The list of feature toggles to add. + * @returns {object} The updated request object with the added feature toggles. + */ +function addRequestedFeatureToggles(r, requestedFeatureToggles) { + if (requestedFeatureToggles.length > 0) { + r.ext.features = {}; + // Loop through each feature toggle and add it to the features object. + // Add current activation status as well. + requestedFeatureToggles.forEach(toggle => { + r.ext.features[toggle] = { activated: FEATURE_TOGGLES.isFeatureEnabled(toggle) }; + }); + } + return r; +} + /** * enrichRequest adds userSync configs, source, and referer info to request and ixDiag objects. * @@ -871,9 +859,8 @@ function applyRegulations(r, bidderRequest) { * @param {string} baseUrl Base exchagne URL. * @param {array} requests List of request obejcts. * @param {object} payload Request payload object. - * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). */ -function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload, MAX_REQUEST_SIZE) { +function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload) { // Use the siteId in the first bid request as the main siteId. siteID = validBidRequests[0].params.siteId; payload.s = siteID; @@ -882,16 +869,6 @@ function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, pa const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix'; const otherIxConfig = config.getConfig(bidderCode); - baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; - - if (baseRequestSize > MAX_REQUEST_SIZE) { - logError('IX Bid Adapter: Base request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.EXCEEDS_MAX_SIZE }); - return requests; - } - - currentRequestSize = baseRequestSize; - let fpdRequestSize = 0; - if (otherIxConfig) { // Append firstPartyData to r.site.page if firstPartyData exists. if (typeof otherIxConfig.firstPartyData === 'object') { @@ -904,25 +881,10 @@ function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, pa } firstPartyString = firstPartyString.slice(0, -1); - fpdRequestSize = encodeURIComponent(firstPartyString).length; - - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { - if (fpdRequestSize < MAX_REQUEST_SIZE) { - if ('page' in r.site) { - r.site.page += firstPartyString; - } else { - r.site.page = firstPartyString; - } - currentRequestSize += fpdRequestSize; - } else { - logError('IX Bid Adapter: IX config FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE }); - } + if ('page' in r.site) { + r.site.page += firstPartyString; } else { - if ('page' in r.site) { - r.site.page += firstPartyString; - } else { - r.site.page = firstPartyString; - } + r.site.page = firstPartyString; } } } @@ -935,30 +897,17 @@ function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, pa * @param {array} transactionIds List of transaction Ids. * @param {object} r Reuqest object. * @param {int} adUnitIndex Index of the current add unit - * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). * @return {object} Reqyest object with added impressions describing the request to the server. */ -function addImpressions(impressions, transactionIds, r, adUnitIndex, MAX_REQUEST_SIZE) { +function addImpressions(impressions, transactionIds, r, adUnitIndex) { const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; const { missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; - - let remainingRequestSize = MAX_REQUEST_SIZE - currentRequestSize; const sourceImpressions = { ixImps, missingBannerImpressions }; const impressionObjects = Object.keys(sourceImpressions) .map((key) => sourceImpressions[key]) .filter(item => Array.isArray(item)) .reduce((acc, curr) => acc.concat(...curr), []); - currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; - - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { - while (impressionObjects.length && currentImpressionSize > remainingRequestSize) { - wasAdUnitImpressionsTrimmed = true; - impressionObjects.pop(); - currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; - } - } - const gpid = impressions[transactionIds[adUnitIndex]].gpid; const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; const tid = impressions[transactionIds[adUnitIndex]].tid; @@ -1071,30 +1020,18 @@ function addFPD(bidderRequest, r, fpd, site, user) { * @param {int} adUnitIndex Index of the current add unit * @param {object} payload Request payload object. * @param {string} baseUrl Base exchagne URL. - * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). * @return {object} Reqyest object with added indentigfier info to ixDiag. */ -function addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl, MAX_REQUEST_SIZE) { +function addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl) { const pbaAdSlot = impressions[transactionIds[adUnitIndex]].pbadslot; const tagId = impressions[transactionIds[adUnitIndex]].tagId; const adUnitCode = impressions[transactionIds[adUnitIndex]].adUnitCode; const divId = impressions[transactionIds[adUnitIndex]].divId; if (pbaAdSlot || tagId || adUnitCode || divId) { - const clonedRObject = deepClone(r); - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { - if (requestSize < MAX_REQUEST_SIZE) { - r.ext.ixdiag.pbadslot = pbaAdSlot; - r.ext.ixdiag.tagid = tagId; - r.ext.ixdiag.adunitcode = adUnitCode; - r.ext.ixdiag.divId = divId; - } - } else { - r.ext.ixdiag.pbadslot = pbaAdSlot; - r.ext.ixdiag.tagid = tagId; - r.ext.ixdiag.adunitcode = adUnitCode; - r.ext.ixdiag.divId = divId; - } + r.ext.ixdiag.pbadslot = pbaAdSlot; + r.ext.ixdiag.tagid = tagId; + r.ext.ixdiag.adunitcode = adUnitCode; + r.ext.ixdiag.divId = divId; } return r; diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index d0b2d7344b5..8ea3b9dc522 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -2,14 +2,14 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { spec, storage, ERROR_CODES, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY } from '../../../modules/ixBidAdapter.js'; +import { spec, storage, ERROR_CODES, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES } from '../../../modules/ixBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; import { deepAccess, deepClone } from '../../../src/utils.js'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/openrtb/pbjs'; - const VIDEO_ENDPOINT_VERSION = 8.1; - const BANNER_ENDPOINT_VERSION = 7.2; + + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = ['test1', 'test2']; const SAMPLE_SCHAIN = { 'ver': '1.0', @@ -1669,27 +1669,6 @@ describe('IndexexchangeAdapter', function () { expect(r.user.testProperty).to.be.undefined; }); - it('should not add fpd data to r object if it exceeds maximum request', function () { - const ortb2 = { - site: { - keywords: 'power tools, drills', - search: 'drill', - }, - user: { - keywords: Array(1200).join('#'), - } - }; - - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; - - const request = spec.buildRequests([bid], { ortb2 })[0]; - const r = extractPayload(request); - - expect(r.site.ref).to.exist; - expect(r.site.keywords).to.be.undefined; - expect(r.user).to.be.undefined; - }); it('should set gpp and gpp_sid field when defined', function () { const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: {gpp: 'gpp', gpp_sid: [1]}} })[0]; const r = extractPayload(request); @@ -2273,7 +2252,7 @@ describe('IndexexchangeAdapter', function () { expect(requests[0].data.sn).to.be.undefined; }); - it('2 requests due to 2 ad units, one larger than url size', function () { + it('1 request, one larger than url size, no splitting', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; bid.params.siteId = '124'; @@ -2283,10 +2262,10 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); expect(requests).to.be.an('array'); - expect(requests).to.have.lengthOf(2); + expect(requests).to.have.lengthOf(1); }); - it('6 ad units should generate only 4 requests', function () { + it('6 ad units should generate only 1 requests', function () { const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid1.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; bid1.params.siteId = '121'; @@ -2312,7 +2291,7 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid1, bid2, bid3, bid4, bid5, bid6], DEFAULT_OPTION); expect(requests).to.be.an('array'); - expect(requests).to.have.lengthOf(4); + expect(requests).to.have.lengthOf(1); for (var i = 0; i < requests.length; i++) { const reqSize = `${requests[i].url}?${utils.parseQueryStringParameters(requests[i].data)}`.length; @@ -3611,7 +3590,7 @@ describe('IndexexchangeAdapter', function () { } } FEATURE_TOGGLES.getFeatureToggles(LOCAL_STORAGE_FEATURE_TOGGLES_KEY); - expect(FEATURE_TOGGLES.isFeatureEnabled('test')).to.be.undefined; + expect(FEATURE_TOGGLES.isFeatureEnabled('test')).to.be.false; expect(FEATURE_TOGGLES.featureToggles).to.deep.equal({}); }); @@ -3672,6 +3651,82 @@ describe('IndexexchangeAdapter', function () { expect(requests).to.be.an('array'); expect(requests).to.have.lengthOf(1); }); + + it('request should have requested feature toggles when local storage is enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.exist; + expect(Object.keys(r.ext.features).length).to.equal(FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES.length); + }); + + it('request should have requested feature toggles when local storage is not enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.exist; + expect(Object.keys(r.ext.features).length).to.equal(FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES.length); + }); + + it('request should not have any feature toggles when there is no requested feature toggle', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = [] + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.be.undefined; + }); + + it('request should not have any feature toggles when there is no requested feature toggle and local storage not enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = [] + expect(r.ext.features).to.be.undefined; + }); + + it('correct activation status of requested feature toggles when local storage not enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = ['test1'] + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.deep.equal({ + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { activated: false } + }); + }); + + it('correct activation status of requested feature toggles', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + serverResponse.body.ext.features = { + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { + activated: true + } + } + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + let bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + let requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + let r = extractPayload(requests[0]); + expect(r.ext.features).to.deep.equal({ + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { activated: true } + }); + + serverResponse.body.ext.features = { + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { + activated: false + } + } + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + r = extractPayload(requests[0]); + expect(r.ext.features).to.deep.equal({ + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { activated: false } + }); + }); }); describe('LocalStorage error codes', () => { @@ -3751,48 +3806,6 @@ describe('IndexexchangeAdapter', function () { expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_FLOOR_INVALID_FORMAT]: 1 } }); }); - it('should log ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - - config.setConfig({ - ix: { - firstPartyData: { - cd: Array(1700).join('#') - } - } - }); - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], {}); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE]: 2 } }); - }); - - it('should log ERROR_CODES.EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.bidderRequestId = Array(8000).join('#'); - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], {}); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.EXCEEDS_MAX_SIZE]: 2 } }); - }); - - it('should log ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - const ortb2 = { - site: { - ext: { - data: { - pageType: Array(5700).join('#') - } - } - } - }; - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], { ortb2 }); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE]: 2 } }); - }); - it('should log ERROR_CODES.VIDEO_DURATION_INVALID in LocalStorage when there is logError called.', () => { const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); bid.params.video.minduration = 1; From dbc64853db32c3e9f2211b44285492f71aff1e83 Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Wed, 12 Apr 2023 08:43:24 -0400 Subject: [PATCH 300/375] AMX ID System: allow cookie storage (#9761) * Update AMXIdSystem logic, allow non-html5 storage, refactor sharedId domainOverride function into library * Fix failing test, bad invocation of getStorageManager --- libraries/domainOverrideToRootDomain/index.js | 39 ++++++++++ modules/amxIdSystem.js | 43 ++++++----- modules/amxIdSystem.md | 14 ++-- modules/sharedIdSystem.js | 31 +------- .../domainOverrideToRootDomain/index_spec.js | 77 +++++++++++++++++++ test/spec/modules/amxIdSystem_spec.js | 39 ++++------ test/spec/modules/sharedIdSystem_spec.js | 46 ----------- 7 files changed, 163 insertions(+), 126 deletions(-) create mode 100644 libraries/domainOverrideToRootDomain/index.js create mode 100644 test/spec/libraries/domainOverrideToRootDomain/index_spec.js diff --git a/libraries/domainOverrideToRootDomain/index.js b/libraries/domainOverrideToRootDomain/index.js new file mode 100644 index 00000000000..95a334755d1 --- /dev/null +++ b/libraries/domainOverrideToRootDomain/index.js @@ -0,0 +1,39 @@ +/** + * Create a domainOverride callback for an ID module, closing over + * an instance of StorageManager. + * + * The domainOverride function, given document.domain, will return + * the topmost domain we are able to set a cookie on. For example, + * given subdomain.example.com, it would return example.com. + * + * @param {StorageManager} storage e.g. from getStorageManager() + * @param {string} moduleName the name of the module using this function + * @returns {function(): string} + */ +export function domainOverrideToRootDomain(storage, moduleName) { + return function() { + const domainElements = document.domain.split('.'); + const cookieName = `_gd${Date.now()}_${moduleName}`; + + for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { + const nextDomain = domainElements.slice(i).join('.'); + + // write test cookie + storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); + + // read test cookie to verify domain was valid + testCookie = storage.getCookie(cookieName); + + // delete test cookie + storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + + if (testCookie === '1') { + // cookie was written successfully using test domain so the topDomain is updated + topDomain = nextDomain; + } else { + // cookie failed to write using test domain so exit by returning the topDomain + return topDomain; + } + } + } +} diff --git a/modules/amxIdSystem.js b/modules/amxIdSystem.js index 9dbab496f2c..5deffa00dfe 100644 --- a/modules/amxIdSystem.js +++ b/modules/amxIdSystem.js @@ -10,28 +10,25 @@ import {ajaxBuilder} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {deepAccess, logError} from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; const NAME = 'amxId'; const GVL_ID = 737; const ID_KEY = NAME; -const version = '1.0'; +const version = '2.0'; const SYNC_URL = 'https://id.a-mx.com/sync/'; const AJAX_TIMEOUT = 300; +const AJAX_OPTIONS = {method: 'GET', withCredentials: true, contentType: 'text/plain'}; -function validateConfig(config) { - if (config == null || config.storage == null) { - logError(`${NAME}: config.storage is required.`); - return false; - } - - if (config.storage.type !== 'html5') { - logError( - `${NAME} only supports storage.type "html5". ${config.storage.type} was provided` - ); - return false; - } +export const storage = getStorageManager({moduleName: NAME, moduleType: MODULE_TYPE_UID}); +const AMUID_KEY = '__amuidpb'; +const getBidAdapterID = () => storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(AMUID_KEY) : null; +function validateConfig(config) { if ( + config.storage != null && typeof config.storage.expires === 'number' && config.storage.expires > 30 ) { @@ -44,7 +41,7 @@ function validateConfig(config) { return true; } -function handleSyncResponse(client, response, callback) { +function handleSyncResponse(client, response, params, callback) { if (response.id != null && response.id.length > 0) { callback(response.id); return; @@ -72,7 +69,7 @@ function handleSyncResponse(client, response, callback) { logError(`${NAME} invalid value`, complete); callback(null); }, - }); + }, params, AJAX_OPTIONS); } export const amxIdSubmodule = { @@ -97,6 +94,8 @@ export const amxIdSubmodule = { ? { [ID_KEY]: value } : undefined, + domainOverride: domainOverrideToRootDomain(storage, NAME), + getId(config, consentData, _extant) { if (!validateConfig(config)) { return undefined; @@ -109,12 +108,18 @@ export const amxIdSubmodule = { const params = { tagId: deepAccess(config, 'params.tagId', ''), - // TODO: are these referer values correct? + ref: ref.ref, u: ref.location, + tl: ref.topmostLocation, + nf: ref.numIframes, + rt: ref.reachedTop, + v: '$prebid.version$', + av: version, vg: '$$PREBID_GLOBAL$$', us_privacy: usp, + am: getBidAdapterID(), gdpr: consent.gdprApplies ? 1 : 0, gdpr_consent: consent.consentString, }; @@ -131,7 +136,7 @@ export const amxIdSubmodule = { if (responseText != null && responseText.length > 0) { try { const parsed = JSON.parse(responseText); - handleSyncResponse(client, parsed, done); + handleSyncResponse(client, parsed, params, done); return; } catch (e) { logError(`${NAME} invalid response`, responseText); @@ -142,9 +147,7 @@ export const amxIdSubmodule = { }, }, params, - { - method: 'GET' - } + AJAX_OPTIONS ); return { callback }; diff --git a/modules/amxIdSystem.md b/modules/amxIdSystem.md index f67fefe261e..5d2b0c48478 100644 --- a/modules/amxIdSystem.md +++ b/modules/amxIdSystem.md @@ -29,15 +29,15 @@ pbjs.setConfig({ | Param under `userSync.userIds[]` | Scope | Type | Description | Example | | -------------------------------- | -------- | ------ | --------------------------- | ----------------------------------------- | | name | Required | string | ID for the amxId module | `"amxId"` | -| storage | Required | Object | Settings for amxId storage | See [storage settings](#storage-settings) | +| storage | Optional | Object | Settings for amxId storage | See [storage settings](#storage-settings) | | params | Optional | Object | Parameters for amxId module | See [params](#params) | ### Storage Settings -The following settings are available for the `storage` property in the `userSync.userIds[]` object: +The following settings are suggested for the `storage` property in the `userSync.userIds[]` object: -| Param under `storage` | Scope | Type | Description | Example | -| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | -| name | Required | String | Where the ID will be stored | `"amxId"` | -| type | Required | String | This must be `"html5"` | `"html5"` | -| expires | Required | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` | +| Param under `storage` | Type | Description | Example | +| --------------------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| name | String | Where the ID will be stored | `"amxId"` | +| type | String | For best performance, this should be `"html5"` | `"html5"` | +| expires | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` | diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 69c19929975..ef56e10870b 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -5,12 +5,13 @@ * @requires module:modules/userId */ -import { parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID } from '../src/utils.js'; +import {parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID} from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import { coppaDataHandler } from '../src/adapterManager.js'; +import {coppaDataHandler} from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {VENDORLESS_GVLID} from '../src/consentHandler.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'pubCommonId'}); const COOKIE = 'cookie'; @@ -172,31 +173,7 @@ export const sharedIdSystemSubmodule = { } }, - domainOverride: function () { - const domainElements = document.domain.split('.'); - const cookieName = `_gd${Date.now()}`; - for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { - const nextDomain = domainElements.slice(i).join('.'); - - // write test cookie - storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); - - // read test cookie to verify domain was valid - testCookie = storage.getCookie(cookieName); - - // delete test cookie - storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); - - if (testCookie === '1') { - // cookie was written successfully using test domain so the topDomain is updated - topDomain = nextDomain; - } else { - // cookie failed to write using test domain so exit by returning the topDomain - return topDomain; - } - } - } - + domainOverride: domainOverrideToRootDomain(storage, 'sharedId'), }; submodule('userId', sharedIdSystemSubmodule); diff --git a/test/spec/libraries/domainOverrideToRootDomain/index_spec.js b/test/spec/libraries/domainOverrideToRootDomain/index_spec.js new file mode 100644 index 00000000000..b490d80fd40 --- /dev/null +++ b/test/spec/libraries/domainOverrideToRootDomain/index_spec.js @@ -0,0 +1,77 @@ +import {domainOverrideToRootDomain} from 'libraries/domainOverrideToRootDomain/index.js'; +import {getStorageManager} from 'src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../../../src/activities/modules'; + +const storage = getStorageManager({ moduleName: 'test', moduleType: MODULE_TYPE_UID }); +const domainOverride = domainOverrideToRootDomain(storage, 'test'); + +describe('domainOverride', () => { + let sandbox, domain, cookies, rejectCookiesFor; + let setCookieStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(document, 'domain').get(() => domain); + cookies = {}; + sandbox.stub(storage, 'getCookie').callsFake((key) => cookies[key]); + rejectCookiesFor = null; + setCookieStub = sandbox.stub(storage, 'setCookie').callsFake((key, value, expires, sameSite, domain) => { + if (domain !== rejectCookiesFor) { + if (expires != null) { + expires = new Date(expires); + } + if (expires == null || expires > Date.now()) { + cookies[key] = value; + } else { + delete cookies[key]; + } + } + }); + }); + + afterEach(() => sandbox.restore()) + + it('test cookies include the module name', () => { + domain = 'greatpublisher.com' + rejectCookiesFor = 'greatpublisher.com' + + // stub Date.now() to return a constant value + sandbox.stub(Date, 'now').returns(1234567890) + + const randomName = `adapterV${(Math.random() * 1e8).toString(16)}` + const localDomainOverride = domainOverrideToRootDomain(storage, randomName) + + const time = Date.now(); + localDomainOverride(); + + sandbox.assert.callCount(setCookieStub, 2) + sandbox.assert.calledWith(setCookieStub, `_gd${time}_${randomName}`, '1', undefined, undefined, 'greatpublisher.com') + }); + + it('will return the root domain when given a subdomain', () => { + const test_domains = [ + 'deeply.nested.subdomain.for.greatpublisher.com', + 'greatpublisher.com', + 'subdomain.greatpublisher.com', + 'a-subdomain.greatpublisher.com', + ]; + + test_domains.forEach((testDomain) => { + domain = testDomain + rejectCookiesFor = 'com' + expect(domainOverride()).to.equal('greatpublisher.com'); + }); + }); + + it(`If we can't set cookies on the root domain, we'll return the subdomain`, () => { + domain = 'subdomain.greatpublisher.com' + rejectCookiesFor = 'greatpublisher.com' + expect(domainOverride()).to.equal('subdomain.greatpublisher.com'); + }); + + it('Will return undefined if we can\'t set cookies on the root domain or the subdomain', () => { + domain = 'subdomain.greatpublisher.com' + rejectCookiesFor = 'subdomain.greatpublisher.com' + expect(domainOverride()).to.equal(undefined); + }); +}); diff --git a/test/spec/modules/amxIdSystem_spec.js b/test/spec/modules/amxIdSystem_spec.js index dea79e87baa..c1ae2c791d5 100644 --- a/test/spec/modules/amxIdSystem_spec.js +++ b/test/spec/modules/amxIdSystem_spec.js @@ -1,4 +1,4 @@ -import { amxIdSubmodule } from 'modules/amxIdSystem.js'; +import { amxIdSubmodule, storage } from 'modules/amxIdSystem.js'; import { server } from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; @@ -48,38 +48,17 @@ describe('validateConfig', () => { logErrorSpy.restore(); }); - it('should return undefined if config.storage is not present', () => { + it('should allow configuration with no storage', () => { expect( amxIdSubmodule.getId( { ...config, - storage: null, + storage: undefined }, null, null ) - ).to.equal(undefined); - - expect(logErrorSpy.calledOnce).to.be.true; - expect(logErrorSpy.lastCall.lastArg).to.contain('storage is required'); - }); - - it('should return undefined if config.storage.type !== "html5"', () => { - expect( - amxIdSubmodule.getId( - { - ...config, - storage: { - type: 'cookie', - }, - }, - null, - null - ) - ).to.equal(undefined); - - expect(logErrorSpy.calledOnce).to.be.true; - expect(logErrorSpy.lastCall.lastArg).to.contain('cookie'); + ).to.not.equal(undefined); }); it('should return undefined if expires > 30', () => { @@ -111,10 +90,18 @@ describe('getId', () => { }); it('should call the sync endpoint and accept a valid response', () => { + storage.setDataInLocalStorage('__amuidpb', TEST_ID); + const { callback } = amxIdSubmodule.getId(config, null, null); callback(spy); const [request] = server.requests; + expect(request.withCredentials).to.be.true + expect(request.requestHeaders['Content-Type']).to.match(/text\/plain/) + + const { search } = utils.parseUrl(request.url); + expect(search.av).to.equal(amxIdSubmodule.version); + expect(search.am).to.equal(TEST_ID); expect(request.method).to.equal('GET'); request.respond( @@ -187,7 +174,7 @@ describe('getId', () => { ); const [, secondRequest] = server.requests; - expect(secondRequest.url).to.be.equal(intermediateValue); + expect(secondRequest.url).to.match(new RegExp(`^${intermediateValue}\?`)); secondRequest.respond( 200, {}, diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 8ef34a1599e..fcfbe5f7c3f 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -91,50 +91,4 @@ describe('SharedId System', function () { expect(result).to.be.undefined; }); }); - - describe('SharedID System domainOverride', () => { - let sandbox, domain, cookies, rejectCookiesFor; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - sandbox.stub(document, 'domain').get(() => domain); - cookies = {}; - sandbox.stub(storage, 'getCookie').callsFake((key) => cookies[key]); - rejectCookiesFor = null; - sandbox.stub(storage, 'setCookie').callsFake((key, value, expires, sameSite, domain) => { - if (domain !== rejectCookiesFor) { - if (expires != null) { - expires = new Date(expires); - } - if (expires == null || expires > Date.now()) { - cookies[key] = value; - } else { - delete cookies[key]; - } - } - }); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return TLD if cookies can be set there', () => { - domain = 'sub.domain.com'; - rejectCookiesFor = 'com'; - expect(sharedIdSystemSubmodule.domainOverride()).to.equal('domain.com'); - }); - - it('should return undefined when cookies cannot be set', () => { - domain = 'sub.domain.com'; - rejectCookiesFor = 'sub.domain.com'; - expect(sharedIdSystemSubmodule.domainOverride()).to.be.undefined; - }); - - it('should return half-way domain if parent domain rejects cookies', () => { - domain = 'inner.outer.domain.com'; - rejectCookiesFor = 'domain.com'; - expect(sharedIdSystemSubmodule.domainOverride()).to.equal('outer.domain.com'); - }); - }); }); From f26f7db42c7c83d45278c45afc7ed4dbc16f559c Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Wed, 12 Apr 2023 14:09:37 +0100 Subject: [PATCH 301/375] Yahoo ConnectId UserID Module: explicit storage management (#9716) * Explicitly manage storage of ConnectID * Addressed initial PR feedback * Documentation update * Address consent logic issues * Prevent storage of empty object response from UPS * Change storage key to match module name. --------- Co-authored-by: slimkrazy --- modules/connectIdSystem.js | 116 +++- modules/connectIdSystem.md | 11 +- test/spec/modules/connectIdSystem_spec.js | 662 ++++++++++++++-------- 3 files changed, 521 insertions(+), 268 deletions(-) diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 38c9c00b0ed..569b99124e1 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -8,14 +8,85 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {includes} from '../src/polyfill.js'; -import {formatQS, logError} from '../src/utils.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {formatQS, isPlainObject, logError, parseUrl} from '../src/utils.js'; +import {uspDataHandler} from '../src/adapterManager.js'; const MODULE_NAME = 'connectId'; +const STORAGE_EXPIRY_DAYS = 14; const VENDOR_ID = 25; const PLACEHOLDER = '__PIXEL_ID__'; const UPS_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; const OVERRIDE_OPT_OUT_KEY = 'connectIdOptOut'; const INPUT_PARAM_KEYS = ['pixelId', 'he', 'puid']; +export const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME}); + +/** + * @function + * @param {Object} obj + */ +function storeObject(obj) { + const expires = Date.now() + (60 * 60 * 24 * 1000 * STORAGE_EXPIRY_DAYS); + if (storage.cookiesAreEnabled()) { + setEtldPlusOneCookie(MODULE_NAME, JSON.stringify(obj), new Date(expires), getSiteHostname()); + } else if (storage.localStorageIsEnabled()) { + obj.__expires = expires; + storage.setDataInLocalStorage(MODULE_NAME, obj); + } +} + +/** + * Attempts to store a cookie on eTLD + 1 + * + * @function + * @param {String} key + * @param {String} value + * @param {Date} expirationDate + * @param {String} hostname + */ +function setEtldPlusOneCookie(key, value, expirationDate, hostname) { + const subDomains = hostname.split('.'); + for (let i = 0; i < subDomains.length; ++i) { + const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.'); + try { + storage.setCookie(key, value, expirationDate.toUTCString(), null, '.' + domain); + const storedCookie = storage.getCookie(key); + if (storedCookie && storedCookie === value) { + break; + } + } catch (error) {} + } +} + +function getIdFromCookie() { + if (storage.cookiesAreEnabled()) { + try { + return JSON.parse(storage.getCookie(MODULE_NAME)); + } catch {} + } + return null; +} + +function getIdFromLocalStorage() { + if (storage.localStorageIsEnabled()) { + const storedIdData = storage.getDataFromLocalStorage(MODULE_NAME); + if (storedIdData) { + if (isPlainObject(storedIdData) && storedIdData.__expires && + storedIdData.__expires <= Date.now()) { + storage.removeDataFromLocalStorage(MODULE_NAME); + return null; + } + return storedIdData; + } + } + return null; +} + +function getSiteHostname() { + const pageInfo = parseUrl(getRefererInfo().page); + return pageInfo.hostname; +} /** @type {Submodule} */ export const connectIdSubmodule = { @@ -37,8 +108,8 @@ export const connectIdSubmodule = { if (connectIdSubmodule.userHasOptedOut()) { return undefined; } - return (typeof value === 'object' && value.connectid) - ? {connectId: value.connectid} : undefined; + return (isPlainObject(value) && (value.connectId || value.connectid)) + ? {connectId: value.connectId || value.connectid} : undefined; }, /** * Gets the Yahoo ConnectID @@ -54,21 +125,28 @@ export const connectIdSubmodule = { const params = config.params || {}; if (!params || (typeof params.he !== 'string' && typeof params.puid !== 'string') || (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) { - logError('The connectId submodule requires the \'pixelId\' and at least one of the \'he\' ' + - 'or \'puid\' parameters to be defined.'); + logError(`${MODULE_NAME} module: configurataion requires the 'pixelId' and at ` + + `least one of the 'he' or 'puid' parameters to be defined.`); return; } + const storedId = getIdFromCookie() || getIdFromLocalStorage(); + if (storedId) { + return {id: storedId}; + } + + const uspString = uspDataHandler.getConsentData() || ''; const data = { + v: '1', '1p': includes([1, '1', true], params['1p']) ? '1' : '0', gdpr: connectIdSubmodule.isEUConsentRequired(consentData) ? '1' : '0', - gdpr_consent: connectIdSubmodule.isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', - us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' + gdpr_consent: connectIdSubmodule.isEUConsentRequired(consentData) ? consentData.consentString : '', + us_privacy: uspString }; - if (connectIdSubmodule.isUnderGPPJurisdiction(consentData)) { - data.gpp = consentData.gppConsent.gppString; - data.gpp_sid = encodeURIComponent(consentData.gppConsent.applicableSections.join(',')); + let topmostLocation = getRefererInfo().topmostLocation; + if (typeof topmostLocation === 'string') { + data.url = topmostLocation.split('?')[0]; } INPUT_PARAM_KEYS.forEach(key => { @@ -84,6 +162,11 @@ export const connectIdSubmodule = { if (response) { try { responseObj = JSON.parse(response); + if (isPlainObject(responseObj) && Object.keys(responseObj).length > 0) { + storeObject(responseObj); + } else { + logError(`${MODULE_NAME} module: UPS response returned an invalid payload ${response}`); + } } catch (error) { logError(error); } @@ -91,7 +174,7 @@ export const connectIdSubmodule = { callback(responseObj); }, error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + logError(`${MODULE_NAME} module: ID fetch encountered an error`, error); callback(); } }; @@ -108,16 +191,7 @@ export const connectIdSubmodule = { * @returns {Boolean} */ isEUConsentRequired(consentData) { - return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); - }, - - /** - * Utility function that returns a boolean flag indicating if the opportunity - * is subject to GPP jurisdiction. - * @returns {Boolean} - */ - isUnderGPPJurisdiction(consentData) { - return !!(consentData && consentData.gppConsent && consentData.gppConsent.gppString); + return !!(consentData?.gdprApplies); }, /** diff --git a/modules/connectIdSystem.md b/modules/connectIdSystem.md index c671c036b77..a3b69a0082c 100644 --- a/modules/connectIdSystem.md +++ b/modules/connectIdSystem.md @@ -27,8 +27,9 @@ The below parameters apply only to the Yahoo ConnectID user ID Module. | Param under usersync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | -| name | Required | String | ID value for the Yahoo ConnectID module - `"connectId"` | `"connectId"` | -| params | Required | Object | Data for Yahoo ConnectID initialization. | | -| params.pixelId | Required | Number | The Yahoo supplied publisher specific pixel Id. | `8976` | -| params.he | Optional | String | The SHA-256 hashed user email address. One of either the `he` parameter or the `puid` parameter must be supplied. | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | -| params.puid | Optional | String | The publisher-supplied user identifier. One of either the `he` parameter or the `puid` parameter must be supplied. | `"P-975484817"` | +| name | Required | String | The name of this module. | `"connectId"` | +| params | Required | Object | Container of all module params. || +| params.pixelId | Required | Number | +The Yahoo-supplied publisher-specific pixel ID. | `"0000"` | +| params.he | Optional | String | The SHA-256 hashed user email address which has been lowercased prior to hashing. Pass both `he` and `puid` params if present, otherwise pass either of the two that is available. |`"ed8ddbf5a171981db8ef938596ca297d5e3f84bcc280041c5880dba3baf9c1d4"`| +| params.puid | Optional | String | The publisher supplied user identifier such as a first-party cookie. Pass both `he` and `puid` params if present, otherwise pass either of the two that is available. | `"ab9iibf5a231ii1db8ef911596ca297d5e3f84biii00041c5880dba3baf9c1da"` | diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 52639c39baf..72acfa38e0a 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -1,6 +1,10 @@ import {expect} from 'chai'; -import {parseQS} from 'src/utils.js'; -import {connectIdSubmodule} from 'modules/connectIdSystem.js'; +import {connectIdSubmodule, storage} from 'modules/connectIdSystem.js'; +import {server} from '../../mocks/xhr'; +import {parseQS, parseUrl} from 'src/utils.js'; +import {uspDataHandler} from 'src/adapterManager.js'; + +const TEST_SERVER_URL = 'http://localhost:9876/'; describe('Yahoo ConnectID Submodule', () => { const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; @@ -8,6 +12,8 @@ describe('Yahoo ConnectID Submodule', () => { const PIXEL_ID = '1234'; const PROD_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PIXEL_ID}/fed`; const OVERRIDE_ENDPOINT = 'https://foo/bar'; + const STORAGE_KEY = 'connectId'; + const USP_DATA = '1YYY'; it('should have the correct module name declared', () => { expect(connectIdSubmodule.name).to.equal('connectId'); @@ -20,271 +26,440 @@ describe('Yahoo ConnectID Submodule', () => { describe('getId()', () => { let ajaxStub; let getAjaxFnStub; + let getCookieStub; + let setCookieStub; + let getLocalStorageStub; + let setLocalStorageStub; + let cookiesEnabledStub; + let localStorageEnabledStub; + let removeLocalStorageDataStub; + let uspConsentDataStub; + let consentData; beforeEach(() => { ajaxStub = sinon.stub(); getAjaxFnStub = sinon.stub(connectIdSubmodule, 'getAjaxFn'); getAjaxFnStub.returns(ajaxStub); + getCookieStub = sinon.stub(storage, 'getCookie'); + setCookieStub = sinon.stub(storage, 'setCookie'); + cookiesEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + setLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); + localStorageEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + removeLocalStorageDataStub = sinon.stub(storage, 'removeDataFromLocalStorage'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + + cookiesEnabledStub.returns(true); + localStorageEnabledStub.returns(true); + uspConsentDataStub.returns(USP_DATA); consentData = { - gdpr: { - gdprApplies: 1, - consentString: 'GDPR_CONSENT_STRING' - }, - uspConsent: 'USP_CONSENT_STRING', - gppConsent: { - gppString: 'header~section6~section7', - applicableSections: [6, 7] - } + gdprApplies: 1, + consentString: 'GDPR_CONSENT_STRING' }; }); afterEach(() => { getAjaxFnStub.restore(); + getCookieStub.restore(); + setCookieStub.restore(); + getLocalStorageStub.restore(); + setLocalStorageStub.restore(); + cookiesEnabledStub.restore(); + localStorageEnabledStub.restore(); + removeLocalStorageDataStub.restore(); + uspConsentDataStub.restore(); }); function invokeGetIdAPI(configParams, consentData) { let result = connectIdSubmodule.getId({ params: configParams }, consentData); - if (typeof result === 'object') { + if (typeof result === 'object' && result.callback) { result.callback(sinon.stub()); } return result; } - it('returns undefined if he, pixelId and puid params are not passed', () => { - expect(invokeGetIdAPI({}, consentData)).to.be.undefined; - expect(ajaxStub.callCount).to.equal(0); - }); - - it('returns undefined if the pixelId param is not passed', () => { - expect(invokeGetIdAPI({ - he: HASHED_EMAIL, - puid: PUBLISHER_USER_ID - }, consentData)).to.be.undefined; - expect(ajaxStub.callCount).to.equal(0); - }); - - it('returns undefined if the pixelId param is passed, but the he and puid param are not passed', () => { - expect(invokeGetIdAPI({ - pixelId: PIXEL_ID - }, consentData)).to.be.undefined; - expect(ajaxStub.callCount).to.equal(0); - }); - - it('returns an object with the callback function if the endpoint override param and the he params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the endpoint override param and the puid params are passed', () => { - let result = invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the endpoint override param and the puid and he params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - puid: PUBLISHER_USER_ID, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the pixelId and he params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the pixelId and puid params are passed', () => { - let result = invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the pixelId, he and puid params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an undefined if the Yahoo specific opt-out key is present in local storage', () => { - localStorage.setItem('connectIdOptOut', '1'); - expect(invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData)).to.be.undefined; - localStorage.removeItem('connectIdOptOut'); - }); - - it('returns an object with the callback function if the correct params are passed and Yahoo opt-out value is not "1"', () => { - localStorage.setItem('connectIdOptOut', 'true'); - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - localStorage.removeItem('connectIdOptOut'); - }); - - it('Makes an ajax GET request to the production API endpoint with pixelId and he query params', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - - const expectedParams = { - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('Makes an ajax GET request to the production API endpoint with pixelId and puid query params', () => { - invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID - }, consentData); - - const expectedParams = { - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('Makes an ajax GET request to the production API endpoint with pixelId, puid and he query params', () => { - invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - - const expectedParams = { - puid: PUBLISHER_USER_ID, - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('Makes an ajax GET request to the specified override API endpoint with query params', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - - const expectedParams = { - he: HASHED_EMAIL, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('sets the callbacks param of the ajax function call correctly', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + describe('Low level storage functionality', () => { + const storageTestCases = [ + { + detail: 'cookie data over local storage data', + cookie: '{"connectId":"foo"}', + localStorage: {connectId: 'bar'}, + expected: {connectId: 'foo'} + }, + { + detail: 'cookie data if only cookie data exists', + cookie: '{"connectId":"foo"}', + localStorage: undefined, + expected: {connectId: 'foo'} + }, + { + detail: 'local storage data if only it local storage data exists', + cookie: undefined, + localStorage: {connectId: 'bar'}, + expected: {connectId: 'bar'} + }, + { + detail: 'undefined when both cookie and local storage are empty', + cookie: undefined, + localStorage: undefined, + expected: undefined + } + ] - expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); - }); + storageTestCases.forEach(testCase => it(`getId() should return ${testCase.detail}`, function () { + getCookieStub.withArgs(STORAGE_KEY).returns(testCase.cookie); + getLocalStorageStub.withArgs(STORAGE_KEY).returns(testCase.localStorage); - it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + const result = invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams.gdpr).to.equal('1'); - expect(requestQueryParams.gdpr_consent).to.equal(consentData.gdpr.consentString); - }); + expect(result.id).to.be.deep.equal(testCase.expected ? testCase.expected : undefined); + })); + }) - it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { - consentData.gdpr.gdprApplies = false; + describe('with invalid module configuration', () => { + it('returns undefined if he, pixelId and puid params are not passed', () => { + expect(invokeGetIdAPI({}, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + it('returns undefined if the pixelId param is not passed', () => { + expect(invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams.gdpr).to.equal('0'); - expect(requestQueryParams.gdpr_consent).to.equal(''); + it('returns undefined if the pixelId param is passed, but the he and puid param are not passed', () => { + expect(invokeGetIdAPI({ + pixelId: PIXEL_ID + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); }); - [1, '1', true].forEach(firstPartyParamValue => { - it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { - invokeGetIdAPI({ - '1p': firstPartyParamValue, - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + describe('with valid module configuration', () => { + describe('when data is in client storage', () => { + it('returns an object with the stored ID from cookies for valid module configuration', () => { + const cookieData = {connectId: 'foobar'}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('id'); + expect(result.id).to.deep.equal(cookieData); + }); + + it('returns an object with the stored ID from localStorage for valid module configuration', () => { + const localStorageData = {connectId: 'foobarbaz'}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('id'); + expect(result.id).to.deep.equal(localStorageData); + }); + + it('deletes local storage data when expiry has passed', () => { + const localStorageData = {connectId: 'foobarbaz', __expires: Date.now() - 10000}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(removeLocalStorageDataStub.calledOnce).to.be.true; + expect(removeLocalStorageDataStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(result.id).to.equal(undefined); + expect(result.callback).to.be.a('function'); + }); + + it('will not delete local storage data when expiry has not passed', () => { + const localStorageData = {connectId: 'foobarbaz', __expires: Date.now() + 10000}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(removeLocalStorageDataStub.calledOnce).to.be.false; + expect(result.id).to.deep.equal(localStorageData); + }); + }); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams['1p']).to.equal('1'); + describe('when no data in client storage', () => { + it('returns an object with the callback function if the endpoint override param and the he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the endpoint override param and the puid params are passed', () => { + let result = invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the endpoint override param and the puid and he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId and he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId and puid params are passed', () => { + let result = invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId, he and puid params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an undefined if the Yahoo specific opt-out key is present in local storage', () => { + localStorage.setItem('connectIdOptOut', '1'); + expect(invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData)).to.be.undefined; + localStorage.removeItem('connectIdOptOut'); + }); + + it('returns an object with the callback function if the correct params are passed and Yahoo opt-out value is not "1"', () => { + localStorage.setItem('connectIdOptOut', 'true'); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + localStorage.removeItem('connectIdOptOut'); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId and he query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId and puid query params', () => { + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + v: '1', + '1p': '0', + gdpr: '1', + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID, + gdpr_consent: consentData.consentString, + url: TEST_SERVER_URL, + us_privacy: USP_DATA + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId, puid and he query params', () => { + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + puid: PUBLISHER_USER_ID, + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the specified override API endpoint with query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('sets the callbacks param of the ajax function call correctly', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); + }); + + it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('1'); + expect(requestQueryParams.gdpr_consent).to.equal(consentData.consentString); + }); + + it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { + consentData.gdprApplies = false; + + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('0'); + expect(requestQueryParams.gdpr_consent).to.equal(''); + }); + + [1, '1', true].forEach(firstPartyParamValue => { + it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { + invokeGetIdAPI({ + '1p': firstPartyParamValue, + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams['1p']).to.equal('1'); + }); + }); + + it('stores the result in client cookie storage', () => { + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + getAjaxFnStub.restore(); + const upsResponse = {connectid: 'foobarbaz'}; + const expiryDelta = new Date(60 * 60 * 24 * 14 * 1000); + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + let request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(upsResponse) + ); + expect(setCookieStub.calledOnce).to.be.true; + expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(upsResponse)); + expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString()); + expect(setCookieStub.firstCall.args[3]).to.equal(null); + const cookieDomain = parseUrl(TEST_SERVER_URL).hostname; + expect(setCookieStub.firstCall.args[4]).to.equal(`.${cookieDomain}`); + dateNowStub.restore(); + }); + + it('stores the result in localStorage if cookies are not permitted', () => { + getAjaxFnStub.restore(); + cookiesEnabledStub.returns(false); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + const upsResponse = {connectid: 'barfoo'}; + const expectedStoredData = { + connectid: 'barfoo', + __expires: 60 * 60 * 24 * 14 * 1000 + }; + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + let request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(upsResponse) + ); + expect(setLocalStorageStub.calledOnce).to.be.true; + expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(expectedStoredData); + dateNowStub.restore(); + }); }); }); }); @@ -306,6 +481,13 @@ describe('Yahoo ConnectID Submodule', () => { payload: { connectid: '4567' } + }, + { + key: 'connectId', + expected: '9924', + payload: { + connectId: '9924' + } }]; VALID_API_RESPONSES.forEach(responseData => { it('should return a newly constructed object with the connect ID for a payload with ${responseData.key} key(s)', () => { @@ -348,19 +530,15 @@ describe('Yahoo ConnectID Submodule', () => { })).to.be.false; }); - it('should return false if consent data.gdpr.applies is false', () => { + it('should return false if consent consentData.applies is false', () => { expect(connectIdSubmodule.isEUConsentRequired({ - gdpr: { - gdprApplies: false - } + gdprApplies: false })).to.be.false; }); it('should return true if consent data.gdpr.applies is true', () => { expect(connectIdSubmodule.isEUConsentRequired({ - gdpr: { - gdprApplies: true - } + gdprApplies: true })).to.be.true; }); }); From f6dd26796715afebaf96554cf26b067c95158514 Mon Sep 17 00:00:00 2001 From: Jonathan Nadarajah <50102657+jogury@users.noreply.github.com> Date: Wed, 12 Apr 2023 15:25:16 +0200 Subject: [PATCH 302/375] Ogury Adapter add device density in bid request (#9796) --- modules/oguryBidAdapter.js | 5 +++-- test/spec/modules/oguryBidAdapter_spec.js | 23 +++++++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index da62ce5c0a1..d09320c00fe 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -11,7 +11,7 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.4.0'; +const ADAPTER_VERSION = '1.4.1'; function getClientWidth() { const documentElementClientWidth = window.top.document.documentElement.clientWidth @@ -90,7 +90,8 @@ function buildRequests(validBidRequests, bidderRequest) { }, device: { w: getClientWidth(), - h: getClientHeight() + h: getClientHeight(), + pxratio: window.devicePixelRatio } }; diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index f7cdcc0d11a..2d48dca5ebb 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -245,6 +245,7 @@ describe('OguryBidAdapter', function () { const stubbedWidth = 200 const stubbedHeight = 600 const stubbedCurrentTime = 1234567890 + const stubbedDevicePixelRatio = 1 const stubbedWidthMethod = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { return stubbedWidth; }); @@ -255,6 +256,10 @@ describe('OguryBidAdapter', function () { return stubbedCurrentTime; }); + const stubbedDevicePixelMethod = sinon.stub(window, 'devicePixelRatio').get(function() { + return stubbedDevicePixelRatio; + }); + const defaultTimeout = 1000; const expectedRequestObject = { id: bidRequests[0].auctionId, @@ -305,11 +310,12 @@ describe('OguryBidAdapter', function () { }, ext: { prebidversion: '$prebid.version$', - adapterversion: '1.4.0' + adapterversion: '1.4.1' }, device: { w: stubbedWidth, - h: stubbedHeight + h: stubbedHeight, + pxratio: stubbedDevicePixelRatio, } }; @@ -317,6 +323,7 @@ describe('OguryBidAdapter', function () { stubbedWidthMethod.restore(); stubbedHeightMethod.restore(); stubbedCurrentTimeMethod.restore(); + stubbedDevicePixelMethod.restore(); }); it('sends bid request to ENDPOINT via POST', function () { @@ -338,6 +345,14 @@ describe('OguryBidAdapter', function () { stubbedTimelineMethod.restore(); }); + it('send device pixel ratio in bid request', function() { + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestObject); + expect(request.data.device.pxratio).to.be.a('number'); + }) + it('bid request object should be conform', function () { const validBidRequests = utils.deepClone(bidRequests) @@ -697,7 +712,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.4.0', + adapterVersion: '1.4.1', prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, @@ -714,7 +729,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.4.0', + adapterVersion: '1.4.1', prebidVersion: '$prebid.version$' }] From 0b21503fec292b3bd23a55a315ac6304988ad157 Mon Sep 17 00:00:00 2001 From: southern-growthcode <79725079+southern-growthcode@users.noreply.github.com> Date: Wed, 12 Apr 2023 10:18:01 -0400 Subject: [PATCH 303/375] Growthcode UserId: Bug fixes & Better Error Catching (#9785) * Remove the cookie storage for data. * Clean up error checking on return JSON data. * Code cleanup --- modules/growthCodeIdSystem.js | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/modules/growthCodeIdSystem.js b/modules/growthCodeIdSystem.js index 739cea07489..fae022f1a56 100644 --- a/modules/growthCodeIdSystem.js +++ b/modules/growthCodeIdSystem.js @@ -11,9 +11,9 @@ import { submodule } from '../src/hook.js' import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -const GCID_EXPIRY = 7; const MODULE_NAME = 'growthCodeId'; const GC_DATA_KEY = '_gc_data'; +const GCID_KEY = 'gcid'; const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb?' export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); @@ -26,14 +26,16 @@ export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleNa export function readData(key) { try { let payload - if (storage.cookiesAreEnabled()) { - payload = tryParse(storage.getCookie(key)) + if (storage.cookiesAreEnabled(null)) { + payload = tryParse(storage.getCookie(key, null)) } if (storage.hasLocalStorage()) { - payload = tryParse(storage.getDataFromLocalStorage(key)) + payload = tryParse(storage.getDataFromLocalStorage(key, null)) } - if ((payload.expire_at !== undefined) && (payload.expire_at > (Date.now() / 1000))) { - return payload + if (payload !== undefined) { + if (payload.expire_at > (Date.now() / 1000)) { + return payload + } } } catch (error) { logError(error); @@ -51,12 +53,8 @@ function storeData(key, value) { logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); if (value) { - if (storage.hasLocalStorage()) { - storage.setDataInLocalStorage(key, value); - } - const expiresStr = (new Date(Date.now() + (GCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); - if (storage.cookiesAreEnabled()) { - storage.setCookie(key, value, expiresStr, 'LAX'); + if (storage.hasLocalStorage(null)) { + storage.setDataInLocalStorage(key, value, null); } } } catch (error) { @@ -70,11 +68,15 @@ function storeData(key, value) { * @param {object|null} */ function tryParse(data) { + let payload; try { - return JSON.parse(data); + payload = JSON.parse(data); + if (payload == null) { + return undefined + } + return payload } catch (err) { - logError(err); - return null; + return undefined; } } @@ -150,6 +152,7 @@ export const growthCodeIdSubmodule = { // If response is a valid json and should save is true if (respJson) { storeData(GC_DATA_KEY, JSON.stringify(respJson)) + storeData(GCID_KEY, respJson.gc_id); callback(respJson); } else { callback(); From 9890c52aad2db668d23fc25434b3f2545c9948c8 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:58:00 +0200 Subject: [PATCH 304/375] LiveIntent UserId module: Add support for bidswitch and medianet ids (#9703) * Add support for bidswitch id * Add test * Update liveIntentIdSystem_spec.js * Fix test * Add medianet cookie * Change atype from 508 to 3 * Revert version change in package-lock.json * Add bidswitch.com and media.net examples * Customer request: change bidswitch.com to bidswitch.net --------- Co-authored-by: Viktor Dreiling Co-authored-by: Viktor Dreiling --- modules/liveIntentIdSystem.js | 8 ++++++ modules/userId/eids.js | 18 ++++++++++++ modules/userId/eids.md | 16 +++++++++++ test/spec/modules/eids_spec.js | 30 ++++++++++++++++++++ test/spec/modules/liveIntentIdSystem_spec.js | 10 +++++++ 5 files changed, 82 insertions(+) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index b594c717c8b..3ecd061085c 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -188,6 +188,14 @@ export const liveIntentIdSubmodule = { result.uid2 = { 'id': value.uid2 } } + if (value.bidswitch) { + result.bidswitch = { 'id': value.bidswitch } + } + + if (value.medianet) { + result.medianet = { 'id': value.medianet } + } + return result } diff --git a/modules/userId/eids.js b/modules/userId/eids.js index a96dbaee4af..2f2d1ca7514 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -146,6 +146,24 @@ export const USER_IDS_CONFIG = { } }, + // bidswitchId + 'bidswitch': { + source: 'bidswitch.net', + atype: 3, + getValue: function(data) { + return data.id; + } + }, + + // medianetId + 'medianet': { + source: 'media.net', + atype: 3, + getValue: function(data) { + return data.id; + } + }, + // britepoolId 'britepoolid': { source: 'britepool.com', diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 83414cbe295..f3d487126a9 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -105,6 +105,22 @@ userIdAsEids = [ segments: ['s1', 's2'] } }, + + { + source: 'bidswitch.net', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, + + { + source: 'media.net', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + }, { source: 'merkleinc.com', diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index f076866a080..e5e779479cc 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -172,6 +172,36 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('bidswitch', function() { + const userId = { + bidswitch: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'bidswitch.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('medianet', function() { + const userId = { + medianet: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'media.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + it('liveIntentId; getValue call and NO ext', function() { const userId = { lipb: { diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index af34c209a1d..afbd1566438 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -313,6 +313,16 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar'}}); }); + it('should decode a bidswitch id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar'}}); + }); + + it('should decode a medianet id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar'}}); + }); + it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); expect(result).to.eql({'uid2': {'id': 'bar'}}); From db9bc3c98bb661f7fdba1e835a423f0dfa36cf90 Mon Sep 17 00:00:00 2001 From: Jeremy Sadwith Date: Wed, 12 Apr 2023 15:38:02 -0400 Subject: [PATCH 305/375] Yahoo ConnectId UserID Module: Resolving getStorageManager invocation issue (#9798) * Resolving getStorageManager invocation issue * Lint * MODULE_TYPE -> moduleType --- modules/connectIdSystem.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 569b99124e1..82669f12623 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -12,6 +12,7 @@ import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; import {formatQS, isPlainObject, logError, parseUrl} from '../src/utils.js'; import {uspDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'connectId'; const STORAGE_EXPIRY_DAYS = 14; @@ -20,7 +21,7 @@ const PLACEHOLDER = '__PIXEL_ID__'; const UPS_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; const OVERRIDE_OPT_OUT_KEY = 'connectIdOptOut'; const INPUT_PARAM_KEYS = ['pixelId', 'he', 'puid']; -export const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** * @function From f7c2c340a78f4308a003b29a7a082860ad0358ca Mon Sep 17 00:00:00 2001 From: olafbuitelaar Date: Thu, 13 Apr 2023 03:42:37 +0200 Subject: [PATCH 306/375] CORE: disable the requirement for having a url, when enabling renderNow in Renderer (#9769) * disable the requirement for having a url, when enabling renderNow in the Renderer * kick off circleci --------- Co-authored-by: Chris Huie --- src/Renderer.js | 3 ++- test/spec/renderer_spec.js | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Renderer.js b/src/Renderer.js index cdee9c79e63..2f9b2e025cb 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -22,6 +22,7 @@ export function Renderer(options) { this.config = config; this.handlers = {}; this.id = id; + this.renderNow = renderNow; // a renderer may push to the command queue to delay rendering until the // render function is loaded by loadExternalScript, at which point the the command @@ -110,7 +111,7 @@ Renderer.prototype.process = function() { * @returns {Boolean} */ export function isRendererRequired(renderer) { - return !!(renderer && renderer.url); + return !!(renderer && (renderer.url || renderer.renderNow)); } /** diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index c41334f916a..fb1e25d6009 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -52,7 +52,7 @@ describe('Renderer', function () { expect(testRenderer2.getConfig()).to.deep.equal({ test: 'config2' }); }); - it('sets a render function with setRender method', function () { + it('sets a render function with the setRender method', function () { testRenderer1.setRender(spyRenderFn); expect(typeof testRenderer1.render).to.equal('function'); testRenderer1.render(); @@ -110,7 +110,6 @@ describe('Renderer', function () { it('renders immediately when requested', function () { const testRenderer3 = Renderer.install({ - url: 'https://httpbin.org/post', config: { test: 'config2' }, id: 2, renderNow: true From 66dfdef44618ac38be2936e79e3df03b58f7c57e Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Thu, 13 Apr 2023 04:37:06 -0300 Subject: [PATCH 307/375] JW Player Video Adapter: Support multiple setup listeners (#9791) * supports multiple setup listeners * adds length check --- modules/jwplayerVideoProvider.js | 57 ++++++++----------- .../submodules/jwplayerVideoProvider_spec.js | 21 +++++-- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js index 6264522ad83..ed6b69d756a 100644 --- a/modules/jwplayerVideoProvider.js +++ b/modules/jwplayerVideoProvider.js @@ -47,31 +47,29 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba function init() { if (!jwplayer) { - triggerSetupFailure(-1); // TODO: come up with error code schema- player is absent + triggerSetupFailure({ code: -1 }); // TODO: come up with error code schema- player is absent return; } playerVersion = jwplayer.version; if (playerVersion < minimumSupportedPlayerVersion) { - triggerSetupFailure(-2); // TODO: come up with error code schema - version not supported + triggerSetupFailure({ code: -2 }); // TODO: come up with error code schema - version not supported return; } if (!document.getElementById(divId)) { - triggerSetupFailure(-3); // TODO: come up with error code schema - missing div id + triggerSetupFailure({ code: -3 }); // TODO: come up with error code schema - missing div id return; } player = jwplayer(divId); if (!player || !player.getState) { - triggerSetupFailure(-4); // TODO: come up with error code schema - factory function failure + triggerSetupFailure({ code: -4 }); // TODO: come up with error code schema - factory function failure } else if (player.getState() === undefined) { setupPlayer(playerConfig); } else { - const payload = getSetupCompletePayload(); - setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); - setupCompleteCallbacks = []; + triggerSetupComplete(); } } @@ -192,8 +190,12 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba function onEvent(externalEventName, callback, basePayload) { if (externalEventName === SETUP_COMPLETE) { setupCompleteCallbacks.push(callback); - } else if (externalEventName === SETUP_FAILED) { + return; + } + + if (externalEventName === SETUP_FAILED) { setupFailedCallbacks.push(callback); + return; } if (!player) { @@ -203,25 +205,6 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba let getEventPayload; switch (externalEventName) { - case SETUP_COMPLETE: - getEventPayload = () => { - setupCompleteCallbacks = []; - return getSetupCompletePayload(); - }; - break; - - case SETUP_FAILED: - getEventPayload = e => { - setupFailedCallbacks = []; - return { - playerVersion, - errorCode: e.code, - errorMessage: e.message, - sourceError: e.sourceError - }; - }; - break; - case AD_REQUEST: case AD_PLAY: case AD_PAUSE: @@ -496,7 +479,17 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba if (!config) { return; } - player.setup(utils.getJwConfig(config)); + player.setup(utils.getJwConfig(config)).on('ready', triggerSetupComplete).on('setupError', triggerSetupFailure); + } + + function triggerSetupComplete() { + if (!setupCompleteCallbacks.length) { + return; + } + + const payload = getSetupCompletePayload(); + setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); + setupCompleteCallbacks = []; } function getSetupCompletePayload() { @@ -511,7 +504,7 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba }; } - function triggerSetupFailure(errorCode) { + function triggerSetupFailure(e) { if (!setupFailedCallbacks.length) { return; } @@ -520,9 +513,9 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba divId, playerVersion, type: SETUP_FAILED, - errorCode, - errorMessage: '', - sourceError: null + errorCode: e.code, + errorMessage: e.message, + sourceError: e.sourceError }; setupFailedCallbacks.forEach(callback => callback(SETUP_FAILED, payload)); diff --git a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js index e08353f5b8d..3cede6c8eda 100644 --- a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js @@ -19,7 +19,7 @@ import { PLAYBACK_MODE } from 'libraries/video/constants/constants.js'; function getPlayerMock() { return makePlayerFactoryMock({ getState: function () {}, - setup: function () {}, + setup: function () { return this; }, getViewable: function () {}, getPercentViewable: function () {}, getMute: function () {}, @@ -30,8 +30,8 @@ function getPlayerMock() { getFullscreen: function () {}, getPlaylistItem: function () {}, playAd: function () {}, - on: function () {}, - off: function () {}, + on: function () { return this; }, + off: function () { return this; }, remove: function () {}, getAudioTracks: function () {}, getCurrentAudioTrack: function () {}, @@ -142,7 +142,7 @@ describe('JWPlayerProvider', function () { it('should instantiate the player when uninstantiated', function () { const player = getPlayerMock(); config.playerConfig = {}; - const setupSpy = player.setup = sinon.spy(); + const setupSpy = player.setup = sinon.spy(player.setup); const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); provider.init(); expect(setupSpy.calledOnce).to.be.true; @@ -158,6 +158,19 @@ describe('JWPlayerProvider', function () { expect(setupComplete.calledOnce).to.be.true; }); + it('should support multiple setup complete event handlers', function () { + const player = getPlayerMock(); + player.getState = () => 'idle'; + const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); + const setupComplete = sinon.spy(); + const setupComplete2 = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.onEvent(SETUP_COMPLETE, setupComplete2, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + expect(setupComplete2.calledOnce).to.be.true; + }); + it('should not reinstantiate player', function () { const player = getPlayerMock(); player.getState = () => 'idle'; From 584af5557de2f5427a81fc3102a10af730176d21 Mon Sep 17 00:00:00 2001 From: yuki Date: Thu, 13 Apr 2023 21:04:32 +0900 Subject: [PATCH 308/375] Pub-X Bid Adapter: adding page url support (#9746) * Pub-X Bid Adapter: adding page url support * Refactored to use site.page * Update pubxBidAdapter.js getting circleci to run --------- Co-authored-by: Patrick McCann --- modules/pubxBidAdapter.js | 7 +++++-- test/spec/modules/pubxBidAdapter_spec.js | 12 ++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/modules/pubxBidAdapter.js b/modules/pubxBidAdapter.js index 17c4ba3848e..ee28d549475 100644 --- a/modules/pubxBidAdapter.js +++ b/modules/pubxBidAdapter.js @@ -1,4 +1,4 @@ -import { deepSetValue } from '../src/utils.js'; +import { deepSetValue, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'pubx'; @@ -16,8 +16,11 @@ export const spec = { const bidId = bidRequest.bidId; const params = bidRequest.params; const sid = params.sid; + const pageUrl = deepAccess(bidRequest, 'ortb2.site.page').replace(/\?.*$/, ''); + const pageEnc = encodeURIComponent(pageUrl); const payload = { - sid: sid + sid: sid, + pu: pageEnc, }; return { id: bidId, diff --git a/test/spec/modules/pubxBidAdapter_spec.js b/test/spec/modules/pubxBidAdapter_spec.js index 06bb5b5f638..b387264bf91 100644 --- a/test/spec/modules/pubxBidAdapter_spec.js +++ b/test/spec/modules/pubxBidAdapter_spec.js @@ -39,14 +39,22 @@ describe('pubxAdapter', function () { id: '26c1ee0038ac11', params: { sid: '12345abc' + }, + ortb2: { + site: { + page: `${location.href}?test=1` + } } } ]; const data = { banner: { - sid: '12345abc' - } + sid: '12345abc', + pu: encodeURIComponent( + utils.deepAccess(bidRequests[0], 'ortb2.site.page').replace(/\?.*$/, '') + ), + }, }; it('sends bid request to ENDPOINT via GET', function () { From 7f4355684d87e299db7adbc592867e394802db47 Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Thu, 13 Apr 2023 06:07:39 -0600 Subject: [PATCH 309/375] Magnite Analytics Adapter : add seat non bid handling (#9696) * Return all bids * Adjust findMatch function * Return all buds unit testing * Responds to review comments * Unit test adjustments * Remove extra line for lint * minor changes * doh --------- Co-authored-by: Robert Ray Martinez III --- modules/magniteAnalyticsAdapter.js | 79 ++++++++++- .../modules/magniteAnalyticsAdapter_spec.js | 130 +++++++++++++++++- 2 files changed, 205 insertions(+), 4 deletions(-) diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index afe73c097fb..7cede6af38d 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -56,7 +56,8 @@ const { BIDDER_DONE, BID_TIMEOUT, BID_WON, - BILLABLE_EVENT + BILLABLE_EVENT, + SEAT_NON_BID }, STATUS: { GOOD, @@ -483,7 +484,7 @@ const findMatchingAdUnitFromAuctions = (matchesFunction, returnFirstMatch) => { } } return matches; -} +}; const getRenderingIds = bidWonData => { // if bid caching off -> return the bidWon auction id @@ -840,7 +841,16 @@ magniteAdapter.track = ({ eventType, args }) => { auctionEntry.floors.dealsEnforced = args.floorData.enforcements.floorDeals; } - // Log error if no matching bid! + // no-bid from server. report it! + if (!bid && args.seatBidId) { + bid = adUnit.bids[args.seatBidId] = { + bidder: args.bidderCode, + source: 'server', + bidId: args.seatBidId, + unknownBid: true + }; + } + if (!bid) { logError(`${MODULE_NAME}: Could not find associated bid request for bid response with requestId: `, args.requestId); break; @@ -871,6 +881,9 @@ magniteAdapter.track = ({ eventType, args }) => { bid.pbsBidId = pbsBidId; } break; + case SEAT_NON_BID: + handleNonBidEvent(args); + break; case BIDDER_DONE: const serverError = deepAccess(args, 'serverErrors.0'); const serverResponseTimeMs = args.serverResponseTimeMs; @@ -958,6 +971,66 @@ magniteAdapter.track = ({ eventType, args }) => { } }; +const handleNonBidEvent = function(args) { + const {seatnonbid, auctionId} = args; + const auction = deepAccess(cache, `auctions.${auctionId}.auction`); + // if no auction just bail + if (!auction) { + logWarn(`Unable to match nonbid to auction`); + return; + } + const adUnits = auction.adUnits; + seatnonbid.forEach(seatnonbid => { + let {seat} = seatnonbid; + seatnonbid.nonbid.forEach(nonbid => { + try { + const {status, impid} = nonbid; + const matchingTid = Object.keys(adUnits).find(tid => adUnits[tid].adUnitCode === impid); + const adUnit = adUnits[matchingTid]; + const statusInfo = statusMap[status] || { status: 'no-bid' }; + adUnit.bids[generateUUID()] = { + bidder: seat, + source: 'server', + isSeatNonBid: true, + clientLatencyMillis: Date.now() - auction.auctionStart, + ...statusInfo + }; + } catch (error) { + logWarn(`Unable to match nonbid to adUnit`); + } + }); + }); +}; + +const statusMap = { + 0: { + status: 'no-bid' + }, + 100: { + status: 'error', + error: { + code: 'request-error', + description: 'general error' + } + }, + 101: { + status: 'error', + error: { + code: 'timeout-error', + description: 'prebid server timeout' + } + }, + 200: { + status: 'rejected' + }, + 202: { + status: 'rejected' + }, + 301: { + status: 'rejected-ipf' + } +}; + adapterManager.registerAnalyticsAdapter({ adapter: magniteAdapter, code: 'magnite', diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index b27e8f30881..bf9c3050bf6 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -28,7 +28,8 @@ const { BIDDER_DONE, BID_WON, BID_TIMEOUT, - BILLABLE_EVENT + BILLABLE_EVENT, + SEAT_NON_BID } } = CONSTANTS; @@ -160,6 +161,16 @@ const MOCK = { 'status': 'rendered', getStatusCode: () => 1, }, + SEAT_NON_BID: { + auctionId: '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + seatnonbid: [{ + seat: 'rubicon', + nonbid: [{ + status: 1, + impid: 'box' + }] + }] + }, AUCTION_END: { 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', 'auctionEnd': 1658868384019, @@ -2039,4 +2050,121 @@ describe('magnite analytics adapter', function () { } }) }); + + describe('BID_RESPONSE events', () => { + beforeEach(() => { + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + config.setConfig({ rubicon: { updatePageView: true } }); + }); + + it('should add a no-bid bid to the add unit if it recieves one from the server', () => { + const bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + const auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + + bidResponse.requestId = 'fakeId'; + bidResponse.seatBidId = 'fakeId'; + + bidResponse.requestId = 'fakeId'; + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, bidResponse) + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(utils.generateUUID.called).to.equal(true); + + expect(message.auctions[0].adUnits[0].bids[1]).to.deep.equal( + { + bidder: 'rubicon', + source: 'server', + status: 'success', + bidResponse: { + 'bidPriceUSD': 3.4, + 'dimensions': { + 'height': 250, + 'width': 300 + }, + 'mediaType': 'banner' + }, + oldBidId: 'fakeId', + unknownBid: true, + bidId: 'fakeId', + clientLatencyMillis: 271 + } + ); + }); + }); + + describe('SEAT_NON_BID events', () => { + let seatnonbid; + + const runNonBidAuction = () => { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(SEAT_NON_BID, seatnonbid) + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + }; + const checkStatusAgainstCode = (status, code, error, index) => { + seatnonbid.seatnonbid[0].nonbid[0].status = code; + runNonBidAuction(); + let message = JSON.parse(server.requests[index].requestBody); + let bid = message.auctions[0].adUnits[0].bids[1]; + + if (error) { + expect(bid.error).to.deep.equal(error); + } else { + expect(bid.error).to.equal(undefined); + } + expect(bid.source).to.equal('server'); + expect(bid.status).to.equal(status); + expect(bid.isSeatNonBid).to.equal(true); + }; + beforeEach(() => { + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + seatnonbid = utils.deepClone(MOCK.SEAT_NON_BID); + }); + + it('adds seatnonbid info to bids array', () => { + runNonBidAuction(); + let message = JSON.parse(server.requests[0].requestBody); + + expect(message.auctions[0].adUnits[0].bids[1]).to.deep.equal( + { + bidder: 'rubicon', + source: 'server', + status: 'no-bid', + isSeatNonBid: true, + clientLatencyMillis: -139101369960 + } + ); + }); + + it('adjusts the status according to the status map', () => { + const statuses = [ + {code: 0, status: 'no-bid'}, + {code: 100, status: 'error', error: {code: 'request-error', description: 'general error'}}, + {code: 101, status: 'error', error: {code: 'timeout-error', description: 'prebid server timeout'}}, + {code: 200, status: 'rejected'}, + {code: 202, status: 'rejected'}, + {code: 301, status: 'rejected-ipf'} + ]; + statuses.forEach((info, index) => { + checkStatusAgainstCode(info.status, info.code, info.error, index); + }); + }); + }); }); From d94c1a963e0554e87d343d7a71eed0604ffb6e04 Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Thu, 13 Apr 2023 09:44:21 -0700 Subject: [PATCH 310/375] openxBidAdapter: update to OpenRTB adapter (#9794) * openxBidAdapter: update to OpenRTB adapter * remove skipped tests --- modules/openxBidAdapter.js | 755 +--- modules/openxBidAdapter.md | 42 +- modules/openxOrtbBidAdapter.md | 8 +- test/spec/modules/openxBidAdapter_spec.js | 3093 ++++++----------- test/spec/modules/openxOrtbBidAdapter_spec.js | 39 - 5 files changed, 1376 insertions(+), 2561 deletions(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index b6f4523bb50..03423a028b4 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,609 +1,238 @@ -import { - _each, - _map, - convertTypes, - deepAccess, - deepSetValue, - inIframe, - isArray, - parseSizesInput, - parseUrl -} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {mergeDeep} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {includes} from '../src/polyfill.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', - 'startdelay', 'skippable', 'playbackmethod', 'api', 'protocols', 'boxingallowed', - 'linearity', 'delivery', 'protocol', 'placement', 'minbitrate', 'maxbitrate']; -const BIDDER_CODE = 'openx'; -const BIDDER_CONFIG = 'hb_pb'; -const BIDDER_VERSION = '3.0.3'; - -const DEFAULT_CURRENCY = 'USD'; - -export const USER_ID_CODE_TO_QUERY_ARG = { - britepoolid: 'britepoolid', // BritePool ID - criteoId: 'criteoid', // CriteoID - fabrickId: 'nuestarid', // Fabrick ID by Nuestar - hadronId: 'audigentid', // Hadron ID from Audigent - id5id: 'id5id', // ID5 ID - idl_env: 'lre', // LiveRamp IdentityLink - IDP: 'zeotapid', // zeotapIdPlus ID+ - idxId: 'idxid', // idIDx, - intentIqId: 'intentiqid', // IntentIQ ID - lipb: 'lipbid', // LiveIntent ID - lotamePanoramaId: 'lotameid', // Lotame Panorama ID - merkleId: 'merkleid', // Merkle ID - netId: 'netid', // netID - parrableId: 'parrableid', // Parrable ID - pubcid: 'pubcid', // PubCommon ID - quantcastId: 'quantcastid', // Quantcast ID - tapadId: 'tapadid', // Tapad Id - tdid: 'ttduuid', // The Trade Desk Unified ID - uid2: 'uid2', // Unified ID 2.0 - admixerId: 'admixerid', // AdMixer ID - deepintentId: 'deepintentid', // DeepIntent ID - dmdId: 'dmdid', // DMD Marketing Corp ID - nextrollId: 'nextrollid', // NextRoll ID - novatiq: 'novatiqid', // Novatiq ID - mwOpenLinkId: 'mwopenlinkid', // MediaWallah OpenLink ID - dapId: 'dapid', // Akamai DAP ID - amxId: 'amxid', // AMX RTB ID - kpuid: 'kpuid', // Kinesso ID - publinkId: 'publinkid', // Publisher Link - naveggId: 'naveggid', // Navegg ID - imuid: 'imuid', // IM-UID by Intimate Merger - adtelligentId: 'adtelligentid' // Adtelligent ID +const bidderConfig = 'hb_pb_ortb'; +const bidderVersion = '2.0'; +export const REQUEST_URL = 'https://rtb.openx.net/openrtbb/prebidjs'; +export const SYNC_URL = 'https://u.openx.net/w/1.0/pd'; +export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; +export const spec = { + code: 'openx', + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + transformBidParams }; -export const spec = { - code: BIDDER_CODE, - gvlid: 69, - supportedMediaTypes: SUPPORTED_AD_TYPES, - isBidRequestValid: function (bidRequest) { - const hasDelDomainOrPlatform = bidRequest.params.delDomain || bidRequest.params.platform; - if (deepAccess(bidRequest, 'mediaTypes.banner') && hasDelDomainOrPlatform) { - return !!bidRequest.params.unit || deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; - } +registerBidder(spec); - return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300 }, - buildRequests: function (bidRequests, bidderRequest) { - if (bidRequests.length === 0) { - return []; - } - - let requests = []; - let [videoBids, bannerBids] = partitionByVideoBids(bidRequests); - - // build banner requests - if (bannerBids.length > 0) { - requests.push(buildOXBannerRequest(bannerBids, bidderRequest)); - } - // build video requests - if (videoBids.length > 0) { - videoBids.forEach(videoBid => { - requests.push(buildOXVideoRequest(videoBid, bidderRequest)) - }); + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + mergeDeep(imp, { + tagid: bidRequest.params.unit, + ext: { + divid: bidRequest.adUnitCode + } + }); + if (bidRequest.params.customParams) { + utils.deepSetValue(imp, 'ext.customParams', bidRequest.params.customParams); } - - return requests; - }, - interpretResponse: function ({body: oxResponseObj}, serverRequest) { - let mediaType = getMediaTypeFromRequest(serverRequest); - - return mediaType === VIDEO ? createVideoBidResponses(oxResponseObj, serverRequest.payload) - : createBannerBidResponses(oxResponseObj, serverRequest.payload); - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { - let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let url = deepAccess(responses, '0.body.ads.pixels') || - deepAccess(responses, '0.body.pixels') || - generateDefaultSyncUrl(gdprConsent, uspConsent); - - return [{ - type: pixelType, - url: url - }]; + if (bidRequest.params.customFloor && !imp.bidfloor) { + imp.bidfloor = bidRequest.params.customFloor; } + return imp; }, - transformBidParams: function(params, isOpenRtb) { - return convertTypes({ - 'unit': 'string', - 'customFloor': 'number' - }, params); - } -}; - -function generateDefaultSyncUrl(gdprConsent, uspConsent) { - let url = 'https://u.openx.net/w/1.0/pd'; - let queryParamStrings = []; - - if (gdprConsent) { - queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); - queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); - } - - // CCPA - if (uspConsent) { - queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); - } - - return `${url}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}`; -} - -function isVideoRequest(bidRequest) { - return (deepAccess(bidRequest, 'mediaTypes.video') && !deepAccess(bidRequest, 'mediaTypes.banner')) || bidRequest.mediaType === VIDEO; -} - -function createBannerBidResponses(oxResponseObj, {bids, startTime}) { - let adUnits = oxResponseObj.ads.ad; - let bidResponses = []; - for (let i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - let adUnitIdx = parseInt(adUnit.idx, 10); - let bidResponse = {}; - - bidResponse.requestId = bids[adUnitIdx].bidId; - - if (adUnit.pub_rev) { - bidResponse.cpm = Number(adUnit.pub_rev) / 1000; - } else { - // No fill, do not add the bidresponse - continue; + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + at: 1, + ext: { + bc: `${bidderConfig}_${bidderVersion}` + } + }) + const bid = context.bidRequests[0]; + if (bid.params.coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); } - let creative = adUnit.creative[0]; - if (creative) { - bidResponse.width = creative.width; - bidResponse.height = creative.height; + if (bid.params.doNotTrack) { + utils.deepSetValue(req, 'device.dnt', 1); } - bidResponse.creativeId = creative.id; - bidResponse.ad = adUnit.html; - if (adUnit.deal_id) { - bidResponse.dealId = adUnit.deal_id; + if (bid.params.platform) { + utils.deepSetValue(req, 'ext.platform', bid.params.platform); } - // default 5 mins - bidResponse.ttl = 300; - // true is net, false is gross - bidResponse.netRevenue = true; - bidResponse.currency = adUnit.currency; - - // additional fields to add - if (adUnit.tbd) { - bidResponse.tbd = adUnit.tbd; + if (bid.params.delDomain) { + utils.deepSetValue(req, 'ext.delDomain', bid.params.delDomain); } - bidResponse.ts = adUnit.ts; - - bidResponse.meta = {}; - if (adUnit.brand_id) { - bidResponse.meta.brandId = adUnit.brand_id; + if (bid.params.response_template_name) { + utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); } - - if (adUnit.adomain && length(adUnit.adomain) > 0) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } else { - bidResponse.meta.advertiserDomains = []; + if (bid.params.test) { + req.test = 1 } - - if (adUnit.adv_id) { - bidResponse.meta.dspid = adUnit.adv_id; - } - - bidResponses.push(bidResponse); - } - return bidResponses; -} - -function getViewportDimensions(isIfr) { - let width; - let height; - let tWin = window; - let tDoc = document; - let docEl = tDoc.documentElement; - let body; - - if (isIfr) { - try { - tWin = window.top; - tDoc = window.top.document; - } catch (e) { - return; + return req; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + if (bid.ext) { + bidResponse.meta.networkId = bid.ext.dsp_id; + bidResponse.meta.advertiserId = bid.ext.buyer_id; + bidResponse.meta.brandId = bid.ext.brand_id; + } + const {ortbResponse} = context; + if (ortbResponse.ext && ortbResponse.ext.paf) { + bidResponse.meta.paf = Object.assign({}, ortbResponse.ext.paf); + bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); + } + return bidResponse; + }, + response(buildResponse, bidResponses, ortbResponse, context) { + // pass these from request to the responses for use in userSync + const {ortbRequest} = context; + if (ortbRequest.ext) { + if (ortbRequest.ext.delDomain) { + utils.deepSetValue(ortbResponse, 'ext.delDomain', ortbRequest.ext.delDomain); + } + if (ortbRequest.ext.platform) { + utils.deepSetValue(ortbResponse, 'ext.platform', ortbRequest.ext.platform); + } } - body = tDoc.body; - - width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; - height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; - } else { - width = tWin.innerWidth || docEl.clientWidth; - height = tWin.innerHeight || docEl.clientHeight; - } - - return `${width}x${height}`; -} - -function formatCustomParms(customKey, customParams) { - let value = customParams[customKey]; - if (isArray(value)) { - // if value is an array, join them with commas first - value = value.join(','); - } - // return customKey=customValue format, escaping + to . and / to _ - return (customKey.toLowerCase() + '=' + value.toLowerCase()).replace('+', '.').replace('/', '_') -} - -function partitionByVideoBids(bidRequests) { - return bidRequests.reduce(function (acc, bid) { - // Fallback to banner ads if nothing specified - if (isVideoRequest(bid)) { - acc[0].push(bid); + const response = buildResponse(bidResponses, ortbResponse, context); + // TODO: we may want to standardize this and move fledge logic to ortbConverter + let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return { + bidId, + config: Object.assign({ + auctionSignals: {}, + }, cfg) + } + }); + return { + bids: response.bids, + fledgeAuctionConfigs, + } } else { - acc[1].push(bid); - } - return acc; - }, [[], []]); -} - -function getMediaTypeFromRequest(serverRequest) { - return /avjp$/.test(serverRequest.url) ? VIDEO : BANNER; -} - -function buildCommonQueryParamsFromBids(bids, bidderRequest) { - const isInIframe = inIframe(); - let defaultParams; - - defaultParams = { - ju: bidderRequest.refererInfo.page, - ch: document.charSet || document.characterSet, - res: `${screen.width}x${screen.height}x${screen.colorDepth}`, - ifr: isInIframe, - tz: new Date().getTimezoneOffset(), - tws: getViewportDimensions(isInIframe), - be: 1, - bc: bids[0].params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, - dddid: _map(bids, bid => bid.transactionId).join(','), - nocache: new Date().getTime() - }; - - const userAgentClientHints = deepAccess(bidderRequest, 'ortb2.device.sua'); - if (userAgentClientHints) { - defaultParams.sua = JSON.stringify(userAgentClientHints); - } - - const userDataSegments = buildFpdQueryParams('user.data', bidderRequest.ortb2); - if (userDataSegments.length > 0) { - defaultParams.sm = userDataSegments; - } - - const siteContentDataSegments = buildFpdQueryParams('site.content.data', bidderRequest.ortb2); - if (siteContentDataSegments.length > 0) { - defaultParams.scsm = siteContentDataSegments; - } - - if (bids[0].params.platform) { - defaultParams.ph = bids[0].params.platform; - } - - if (bidderRequest.gdprConsent) { - let gdprConsentConfig = bidderRequest.gdprConsent; - - if (gdprConsentConfig.consentString !== undefined) { - defaultParams.gdpr_consent = gdprConsentConfig.consentString; - } - - if (gdprConsentConfig.gdprApplies !== undefined) { - defaultParams.gdpr = gdprConsentConfig.gdprApplies ? 1 : 0; + return response.bids } - - if (config.getConfig('consentManagement.cmpApi') === 'iab') { - defaultParams.x_gdpr_f = 1; - } - } - - if (bidderRequest && bidderRequest.uspConsent) { - defaultParams.us_privacy = bidderRequest.uspConsent; - } - - // normalize publisher common id - if (deepAccess(bids[0], 'crumbs.pubcid')) { - deepSetValue(bids[0], 'userId.pubcid', deepAccess(bids[0], 'crumbs.pubcid')); - } - defaultParams = appendUserIdsToQueryParams(defaultParams, bids[0].userId); - - // supply chain support - if (bids[0].schain) { - defaultParams.schain = serializeSupplyChain(bids[0].schain); - } - - return defaultParams; -} - -function buildFpdQueryParams(fpdPath, ortb2) { - const firstPartyData = deepAccess(ortb2, fpdPath); - if (!Array.isArray(firstPartyData) || !firstPartyData.length) { - return ''; - } - const fpd = firstPartyData - .filter( - data => (Array.isArray(data.segment) && - data.segment.length > 0 && - data.name !== undefined && - data.name.length > 0) - ) - .reduce((acc, data) => { - const name = typeof data.ext === 'object' && data.ext.segtax ? `${data.name}/${data.ext.segtax}` : data.name; - acc[name] = (acc[name] || []).concat(data.segment.map(seg => seg.id)); - return acc; - }, {}) - return Object.keys(fpd) - .map((name, _) => name + ':' + fpd[name].join('|')) - .join(',') -} - -function appendUserIdsToQueryParams(queryParams, userIds) { - _each(userIds, (userIdObjectOrValue, userIdProviderKey) => { - const key = USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]; - - if (USER_ID_CODE_TO_QUERY_ARG.hasOwnProperty(userIdProviderKey)) { - switch (userIdProviderKey) { - case 'merkleId': - queryParams[key] = userIdObjectOrValue.id; - break; - case 'uid2': - queryParams[key] = userIdObjectOrValue.id; - break; - case 'lipb': - queryParams[key] = userIdObjectOrValue.lipbid; - if (Array.isArray(userIdObjectOrValue.segments) && userIdObjectOrValue.segments.length > 0) { - const liveIntentSegments = 'liveintent:' + userIdObjectOrValue.segments.join('|'); - queryParams.sm = `${queryParams.sm ? queryParams.sm + ',' : ''}${liveIntentSegments}`; + }, + overrides: { + imp: { + bidfloor(setBidFloor, imp, bidRequest, context) { + // enforce floors should always be in USD + // TODO: does it make sense that request.cur can be any currency, but request.imp[].bidfloorcur must be USD? + const floor = {}; + setBidFloor(floor, bidRequest, {...context, currency: 'USD'}); + if (floor.bidfloorcur === 'USD') { + Object.assign(imp, floor); + } + }, + video(orig, imp, bidRequest, context) { + if (FEATURES.VIDEO) { + // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] + // to populate imp.video + // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + if (imp.video && videoParams?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; } - break; - case 'parrableId': - queryParams[key] = userIdObjectOrValue.eid; - break; - case 'id5id': - queryParams[key] = userIdObjectOrValue.uid; - break; - case 'novatiq': - queryParams[key] = userIdObjectOrValue.snowflake; - break; - default: - queryParams[key] = userIdObjectOrValue; + } } } - }); - - return queryParams; -} - -function serializeSupplyChain(supplyChain) { - return `${supplyChain.ver},${supplyChain.complete}!${serializeSupplyChainNodes(supplyChain.nodes)}`; -} - -function serializeSupplyChainNodes(supplyChainNodes) { - const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + } +}); - return supplyChainNodes.map(supplyChainNode => { - return supplyChainNodePropertyOrder.map(property => supplyChainNode[property] || '') - .join(','); - }).join('!'); +function transformBidParams(params, isOpenRtb) { + return utils.convertTypes({ + 'unit': 'string', + 'customFloor': 'number' + }, params); } -function buildOXBannerRequest(bids, bidderRequest) { - let customParamsForAllBids = []; - let hasCustomParam = false; - let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); - let auids = _map(bids, bid => bid.params.unit); +function isBidRequestValid(bidRequest) { + const hasDelDomainOrPlatform = bidRequest.params.delDomain || + bidRequest.params.platform; - queryParams.aus = _map(bids, bid => parseSizesInput(bid.mediaTypes.banner.sizes).join(',')).join('|'); - queryParams.divids = _map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); - // gpid - queryParams.aucs = _map(bids, function (bid) { - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - return encodeURIComponent(gpid || '') - }).join(','); - - if (auids.some(auid => auid)) { - queryParams.auid = auids.join(','); - } - - if (bids.some(bid => bid.params.doNotTrack)) { - queryParams.ns = 1; + if (utils.deepAccess(bidRequest, 'mediaTypes.banner') && + hasDelDomainOrPlatform) { + return !!bidRequest.params.unit || + utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; } - if (config.getConfig('coppa') === true || bids.some(bid => bid.params.coppa)) { - queryParams.tfcd = 1; - } + return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +} - bids.forEach(function (bid) { - if (bid.params.customParams) { - let customParamsForBid = _map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); - let formattedCustomParams = window.btoa(customParamsForBid.join('&')); - hasCustomParam = true; - customParamsForAllBids.push(formattedCustomParams); - } else { - customParamsForAllBids.push(''); - } +function buildRequests(bids, bidderRequest) { + let videoBids = bids.filter(bid => isVideoBid(bid)); + let bannerBids = bids.filter(bid => isBannerBid(bid)); + let requests = bannerBids.length ? [createRequest(bannerBids, bidderRequest, BANNER)] : []; + videoBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); }); - if (hasCustomParam) { - queryParams.tps = customParamsForAllBids.join(','); - } - - enrichQueryWithFloors(queryParams, BANNER, bids); - - let url = queryParams.ph - ? `https://u.openx.net/w/1.0/arj` - : `https://${bids[0].params.delDomain}/w/1.0/arj`; - - return { - method: 'GET', - url: url, - data: queryParams, - payload: {'bids': bids, 'startTime': new Date()} - }; + return requests; } -function buildOXVideoRequest(bid, bidderRequest) { - let oxVideoParams = generateVideoParameters(bid, bidderRequest); - let url = oxVideoParams.ph - ? `https://u.openx.net/v/1.0/avjp` - : `https://${bid.params.delDomain}/v/1.0/avjp`; +function createRequest(bidRequests, bidderRequest, mediaType) { return { - method: 'GET', - url: url, - data: oxVideoParams, - payload: {'bid': bid, 'startTime': new Date()} - }; -} - -function generateVideoParameters(bid, bidderRequest) { - const videoMediaType = deepAccess(bid, `mediaTypes.video`); - let queryParams = buildCommonQueryParamsFromBids([bid], bidderRequest); - let oxVideoConfig = deepAccess(bid, 'params.video') || {}; - let context = deepAccess(bid, 'mediaTypes.video.context'); - let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - let width; - let height; - - // normalize config for video size - if (isArray(bid.sizes) && bid.sizes.length === 2 && !isArray(bid.sizes[0])) { - width = parseInt(bid.sizes[0], 10); - height = parseInt(bid.sizes[1], 10); - } else if (isArray(bid.sizes) && 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 (isArray(playerSize) && playerSize.length === 2) { - width = parseInt(playerSize[0], 10); - height = parseInt(playerSize[1], 10); - } - - let openRtbParams = {w: width, h: height}; - - // legacy openrtb params could be in video, openrtb, or video.openrtb - let legacyParams = bid.params.video || bid.params.openrtb || {}; - if (legacyParams.openrtb) { - legacyParams = legacyParams.openrtb; - } - // support for video object or full openrtb object - if (isArray(legacyParams.imp)) { - legacyParams = legacyParams.imp[0].video; + method: 'POST', + url: config.getConfig('openxOrtbUrl') || REQUEST_URL, + data: converter.toORTB({bidRequests, bidderRequest, context: {mediaType}}) } - Object.keys(legacyParams) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => openRtbParams[param] = legacyParams[param]); - - // 5.0 openrtb video params - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => openRtbParams[param] = videoMediaType[param]); - - let openRtbReq = { - imp: [ - { - video: openRtbParams - } - ] - }; - - queryParams['openrtb'] = JSON.stringify(openRtbReq); - - queryParams.auid = bid.params.unit; - // override prebid config with openx config if available - queryParams.vwd = width || oxVideoConfig.vwd; - queryParams.vht = height || oxVideoConfig.vht; - - if (context === 'outstream') { - queryParams.vos = '101'; - } - - if (oxVideoConfig.mimes) { - queryParams.vmimes = oxVideoConfig.mimes; - } - - if (bid.params.test) { - queryParams.vtest = 1; - } - - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - if (gpid) { - queryParams.aucs = encodeURIComponent(gpid); - } - - // each video bid makes a separate request - enrichQueryWithFloors(queryParams, VIDEO, [bid]); - - return queryParams; } -function createVideoBidResponses(response, {bid, startTime}) { - let bidResponses = []; - - if (response !== undefined && response.vastUrl !== '' && response.pub_rev > 0) { - let vastQueryParams = parseUrl(response.vastUrl).search || {}; - let bidResponse = {}; - bidResponse.requestId = bid.bidId; - if (response.deal_id) { - bidResponse.dealId = response.deal_id; - } - // default 5 mins - bidResponse.ttl = 300; - // true is net, false is gross - bidResponse.netRevenue = true; - bidResponse.currency = response.currency; - bidResponse.cpm = parseInt(response.pub_rev, 10) / 1000; - bidResponse.width = parseInt(response.width, 10); - bidResponse.height = parseInt(response.height, 10); - bidResponse.creativeId = response.adid; - bidResponse.vastUrl = response.vastUrl; - bidResponse.mediaType = VIDEO; +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} - // enrich adunit with vast parameters - response.ph = vastQueryParams.ph; - response.colo = vastQueryParams.colo; - response.ts = vastQueryParams.ts; +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} - bidResponses.push(bidResponse); +function interpretResponse(resp, req) { + if (!resp.body) { + resp.body = {nbr: 0}; } - - return bidResponses; + return converter.fromORTB({request: req.data, response: resp.body}); } -function enrichQueryWithFloors(queryParams, mediaType, bids) { - let customFloorsForAllBids = []; - let hasCustomFloor = false; - bids.forEach(function (bid) { - let floor = getBidFloor(bid, mediaType); - - if (floor) { - customFloorsForAllBids.push(floor); - hasCustomFloor = true; +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let queryParamStrings = []; + let syncUrl = SYNC_URL; + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + if (responses.length > 0 && responses[0].body && responses[0].body.ext) { + const ext = responses[0].body.ext; + if (ext.delDomain) { + syncUrl = `https://${ext.delDomain}/w/1.0/pd` + } else if (ext.platform) { + queryParamStrings.push('ph=' + ext.platform) + } } else { - customFloorsForAllBids.push(0); + queryParamStrings.push('ph=' + DEFAULT_PH) } - }); - if (hasCustomFloor) { - queryParams.aumfs = customFloorsForAllBids.join(','); + return [{ + type: pixelType, + url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` + }]; } } - -function getBidFloor(bidRequest, mediaType) { - let floorInfo = {}; - const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; - - if (typeof bidRequest.getFloor === 'function') { - floorInfo = bidRequest.getFloor({ - currency: currency, - mediaType: mediaType, - size: '*' - }); - } - let floor = floorInfo.floor || bidRequest.params.customFloor || 0; - - return Math.round(floor * 1000); // normalize to micro currency -} - -registerBidder(spec); diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 0690bf6b4fc..a39aa1580cd 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -9,30 +9,31 @@ Maintainer: team-openx@openx.com # Description Module that connects to OpenX's demand sources. -Note there is an updated version of the OpenX bid adapter called openxOrtbBidAdapter. -Publishers are welcome to test the other adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. +Note that this adapter mirrors openxOrtbBidAdapter and any updates must be +completed in both adapters. +openxOrtbBidAdapter will be removed in a future release and should not be used. +Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. # Bid Parameters ## Banner -| Name | Scope | Type | Description | Example | -|---------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------| -| `delDomain` ~~or `platform`~~** | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | -| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` | -| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

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

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

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

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

**WARNING:**
Request-level setting. May impact revenue. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. Use of `pbjs.setConfig({coppa: true});` is now preferred. | true ## Video -| Name | Scope | Type | Description | Example | -|-------------|----------|--------------------|--------------------------------------------------------------|----------------------------------------------------------------| -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | -| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | -| `openrtb` | optional | OpenRTB Impression | An OpenRtb Impression with Video subtype properties | `{ imp: [{ video: {mimes: ['video/x-ms-wmv, video/mp4']} }] }` | +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" +| `video` | optional | OpenRTB video subtypes | Use of adUnit.mediaTypes.video is now preferred. | `{ video: {mimes: ['video/mp4']}` + # Example ```javascript @@ -70,7 +71,8 @@ var adUnits = [ mediaTypes: { video: { playerSize: [640, 480], - context: 'instream' + context: 'instream', + mimes: ['video/x-ms-wmv, video/mp4'] } }, bids: [{ @@ -79,10 +81,10 @@ var adUnits = [ unit: '1611023124', delDomain: 'PUBLISHER-d.openx.net', video: { - mimes: ['video/x-ms-wmv, video/mp4'] + mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }] + }]p } ]; ``` diff --git a/modules/openxOrtbBidAdapter.md b/modules/openxOrtbBidAdapter.md index 04ed32608cb..b5e1820021a 100644 --- a/modules/openxOrtbBidAdapter.md +++ b/modules/openxOrtbBidAdapter.md @@ -1,15 +1,17 @@ # Overview ``` -Module Name: OpenX OpenRTB Bidder Adapter +Module Name: OpenX Bidder Adapter Module Type: Bidder Adapter Maintainer: team-openx@openx.com ``` # Description +DEPRECATED. Use openxBidAdapter. -This is an updated version of the OpenX bid adapter which calls our new serving architecture. -Publishers are welcome to test this adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. +This adapter was originally an adapter used to test OpenX serving architecture changes. +This adapter now mirrors openxBidAdapter and this adapter will be removed in a future release. Please use openxBidAdapter. +Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. # Bid Parameters ## Banner diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 8fe220aa202..d8ea79ac698 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,204 +1,81 @@ import {expect} from 'chai'; -import {spec, USER_ID_CODE_TO_QUERY_ARG} from 'modules/openxBidAdapter.js'; +import {spec, REQUEST_URL, SYNC_URL, DEFAULT_PH} from 'modules/openxOrtbBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from 'src/mediaTypes.js'; -import {userSync} from 'src/userSync.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; +// load modules that register ORTB processors +import 'src/prebid.js' +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import {deepClone} from 'src/utils.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {hook} from '../../../src/hook.js'; + +const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; + +const BidRequestBuilder = function BidRequestBuilder(options) { + const defaults = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'openx' + }, + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + sizes: [[300, 250], [300, 600]], + }; -const URLBASE = '/w/1.0/arj'; -const URLBASEVIDEO = '/v/1.0/avjp'; - -describe('OpenxAdapter', function () { - const adapter = newBidder(spec); - - /** - * Type Definitions - */ - - /** - * @typedef {{ - * impression: string, - * inview: string, - * click: string - * }} - */ - let OxArjTracking; - /** - * @typedef {{ - * ads: { - * version: number, - * count: number, - * pixels: string, - * ad: Array - * } - * }} - */ - let OxArjResponse; - /** - * @typedef {{ - * adunitid: number, - * adid:number, - * type: string, - * htmlz: string, - * framed: number, - * is_fallback: number, - * ts: string, - * cpipc: number, - * pub_rev: string, - * tbd: ?string, - * adv_id: string, - * deal_id: string, - * auct_win_is_deal: number, - * brand_id: string, - * currency: string, - * idx: string, - * creative: Array - * }} - */ - let OxArjAdUnit; - /** - * @typedef {{ - * id: string, - * width: string, - * height: string, - * target: string, - * mime: string, - * media: string, - * tracking: OxArjTracking - * }} - */ - let OxArjCreative; - - // HELPER METHODS - /** - * @type {OxArjCreative} - */ - const DEFAULT_TEST_ARJ_CREATIVE = { - id: '0', - width: 'test-width', - height: 'test-height', - target: 'test-target', - mime: 'test-mime', - media: 'test-media', - tracking: { - impression: 'test-impression', - inview: 'test-inview', - click: 'test-click' - } + const request = { + ...defaults.request, + ...options }; - /** - * @type {OxArjAdUnit} - */ - const DEFAULT_TEST_ARJ_AD_UNIT = { - adunitid: 0, - type: 'test-type', - html: 'test-html', - framed: 0, - is_fallback: 0, - ts: 'test-ts', - tbd: 'NaN', - deal_id: undefined, - auct_win_is_deal: undefined, - cpipc: 0, - pub_rev: 'test-pub_rev', - adv_id: 'test-adv_id', - brand_id: 'test-brand_id', - currency: 'test-currency', - idx: '0', - creative: [DEFAULT_TEST_ARJ_CREATIVE] + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; }; - /** - * @type {OxArjResponse} - */ - const DEFAULT_ARJ_RESPONSE = { - ads: { - version: 0, - count: 1, - pixels: 'https://testpixels.net', - ad: [DEFAULT_TEST_ARJ_AD_UNIT] + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'openx', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' } }; - // Sample bid requests + const request = { + ...defaults, + ...options + }; - const BANNER_BID_REQUESTS_WITH_MEDIA_TYPES = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - adUnitCode: '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-1' } } }, - }]; - - const VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES = [{ - bidder: 'openx', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', + this.build = () => request; +}; - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }]; +describe('OpenxRtbAdapter', function () { + before(() => { + hook.ready(); + }); - const MULTI_FORMAT_BID_REQUESTS = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - playerSize: [300, 250] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }]; + const adapter = newBidder(spec); describe('inherited functions', function () { it('exists and is a function', function () { @@ -206,7 +83,7 @@ describe('OpenxAdapter', function () { }); }); - describe('isBidRequestValid', function () { + describe('isBidRequestValid()', function () { describe('when request is for a banner ad', function () { let bannerBid; beforeEach(function () { @@ -259,8 +136,28 @@ describe('OpenxAdapter', function () { describe('when request is for a multiformat ad', function () { describe('and request config uses mediaTypes video and banner', () => { + const multiformatBid = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [300, 250] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(MULTI_FORMAT_BID_REQUESTS[0])).to.equal(true); + expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); }); }); }); @@ -349,1509 +246,1035 @@ describe('OpenxAdapter', function () { expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); }); }); - - describe('and request config uses test', () => { - const videoBidWithTest = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain', - test: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - - let mockBidderRequest = {refererInfo: {}}; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(videoBidWithTest)).to.equal(true); - }); - - it('should send video bid request to openx url via GET, with vtest=1 video parameter', function () { - const request = spec.buildRequests([videoBidWithTest], mockBidderRequest); - expect(request[0].data.vtest).to.equal(1); - }); - }); }); }); - describe('buildRequests for banner ads', function () { - const bidRequestsWithMediaTypes = BANNER_BID_REQUESTS_WITH_MEDIA_TYPES; - - const bidRequestsWithPlatform = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }]; - - const mockBidderRequest = {refererInfo: {}}; - - it('should send bid request to openx url via GET, with mediaTypes specified with banner type', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASE); - expect(request[0].data.ph).to.be.undefined; - expect(request[0].method).to.equal('GET'); - }); - - it('should send bid request to openx platform url via GET, if platform is present', function () { - const request = spec.buildRequests(bidRequestsWithPlatform, mockBidderRequest); - expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); - expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); - expect(request[0].method).to.equal('GET'); - }); - - it('should send bid request to openx platform url via GET, if both params present', function () { - const bidRequestsWithPlatformAndDelDomain = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }]; - - const request = spec.buildRequests(bidRequestsWithPlatformAndDelDomain, mockBidderRequest); - expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); - expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); - expect(request[0].method).to.equal('GET'); - }); + describe('buildRequests()', function () { + let bidRequestsWithMediaTypes; + let bidRequestsWithPlatform; + let mockBidderRequest; - it('should send the adunit codes', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.divids).to.equal(`${encodeURIComponent(bidRequestsWithMediaTypes[0].adUnitCode)},${encodeURIComponent(bidRequestsWithMediaTypes[1].adUnitCode)}`); - }); - - it('should send the gpids', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.aucs).to.equal(`${encodeURIComponent('/12345/my-gpt-tag-0')},${encodeURIComponent('/12345/my-gpt-tag-1')}`); - }); + beforeEach(function () { + mockBidderRequest = {refererInfo: {}}; - it('should send ad unit ids when any are defined', function () { - const bidRequestsWithUnitIds = [{ - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' + bidRequestsWithMediaTypes = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', }, - 'adUnitCode': 'adunit-code', + adUnitCode: '/adunit-code/test-path', mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '22', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); - expect(request[0].data.auid).to.equal(`,${bidRequestsWithUnitIds[1].params.unit}`); - }); - - it('should not send any ad unit ids when none are defined', function () { - const bidRequestsWithoutUnitIds = [{ - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + ortb2Imp: { + ext: { + ae: 2 } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' + } }, { - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' + bidder: 'openx', + params: { + unit: '22', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', }, - 'adUnitCode': 'adunit-code', + adUnitCode: 'adunit-code', mediaTypes: { - banner: { - sizes: [[728, 90]] + video: { + playerSize: [640, 480] } }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + transactionId: 'test-transactionId-2' }]; - const request = spec.buildRequests(bidRequestsWithoutUnitIds, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('auid'); }); - it('should send out custom params on bids that have customParams specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customParams': {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} - } + context('common requests checks', function() { + it('should be able to handle multiformat requests', () => { + const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] } - ); + const requests = spec.buildRequests([multiformat], mockBidderRequest); + const outgoingFormats = requests.flatMap(rq => rq.data.imp.flatMap(imp => ['banner', 'video'].filter(k => imp[k] != null))); + const expected = FEATURES.VIDEO ? ['banner', 'video'] : ['banner'] + expect(outgoingFormats).to.have.members(expected); + }) - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; + it('should send bid request to openx url via POST', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].url).to.equal(REQUEST_URL); + expect(request[0].method).to.equal('POST'); + }); - expect(dataParams.tps).to.exist; - expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); - }); + it('should send delivery domain, if available', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.delDomain).to.equal(bidRequestsWithMediaTypes[0].params.delDomain); + expect(request[0].data.ext.platformId).to.be.undefined; + }); - it('should send out custom bc parameter, if override is present', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'bc': 'hb_override' - } - } - ); + it('should send platform id, if available', function () { + bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; + bidRequestsWithMediaTypes[1].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + }); - expect(dataParams.bc).to.exist; - expect(dataParams.bc).to.equal('hb_override'); - }); + it('should send openx adunit codes', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[0].params.unit); + expect(request[1].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit); + }); - it('should not send any consent management properties', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.gdpr).to.equal(undefined); - expect(request[0].data.gdpr_consent).to.equal(undefined); - expect(request[0].data.x_gdpr_f).to.equal(undefined); - }); + it('should send out custom params on bids that have customParams specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customParams: {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} + } + } + ); - describe('when there is a consent management framework', function () { - let bidRequests; - let mockConfig; - let bidderRequest; - const IAB_CONSENT_FRAMEWORK_CODE = 1; + mockBidderRequest.bids = [bidRequest]; + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].ext.customParams).to.equal(bidRequest.params.customParams); + }) - beforeEach(function () { - bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678-banner', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }, { - 'bidder': 'openx', - 'mediaTypes': { - video: { - playerSize: [640, 480] + describe('floors', function () { + it('should send out custom floors on bids that have customFloors, no currency as account currency is used', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customFloor: 1.500 + } } - }, - 'params': { - 'unit': '12345678-video', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', + ); - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - }); + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(bidRequest.params.customFloor); + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined); + }); - afterEach(function () { - config.getConfig.restore(); - }); + context('with floors module', function () { + let adServerCurrencyStub; - describe('when us_privacy applies', function () { - beforeEach(function () { - bidderRequest = { - uspConsent: '1YYN', - refererInfo: {} - }; + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + afterEach(function () { + config.getConfig.restore(); }); - }); - it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.us_privacy).to.equal('1YYN'); - expect(request[1].data.us_privacy).to.equal('1YYN'); - }); - }); + it('should send out floors on bids in USD', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'USD', + floor: 9.99 + } + } + } + ); - describe('when us_privacy does not applies', function () { - beforeEach(function () { - bidderRequest = { - refererInfo: {} - }; + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(9.99); + expect(request[0].data.imp[0].bidfloorcur).to.equal('USD'); + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('should send not send floors', function () { + adServerCurrencyStub.returns('EUR'); + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'BTC', + floor: 9.99 + } + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(undefined) + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined) }); - }); + }) + }) - it('should not send the consent string, when consent string is undefined', function () { - delete bidderRequest.uspConsent; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('us_privacy'); - expect(request[1].data).to.not.have.property('us_privacy'); - }); - }); + describe('FPD', function() { + let bidRequests; + const mockBidderRequest = {refererInfo: {}}; - describe('when GDPR applies', function () { beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: true + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' }, - refererInfo: {} - }; + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('ortb2.site should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + site: { + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'] + } + } }); + let data = request[0].data; + expect(data.site.domain).to.equal('page.example.com'); + expect(data.site.cat).to.deep.equal(['IAB2']); + expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); }); - it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr).to.equal(1); - expect(request[1].data.gdpr).to.equal(1); + it('ortb2.user should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + user: { + yob: 1985 + } + } + }); + let data = request[0].data; + expect(data.user.yob).to.equal(1985); }); - it('should send the consent string', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - }); + describe('ortb2Imp', function() { + describe('ortb2Imp.ext.data.pbadslot', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - it('should send the consent management framework code', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - }); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - describe('when GDPR does not apply', function () { - beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: false - }, - refererInfo: {} - }; - - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + it('should not send if imp[].ext.data.pbadslot is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('should send if imp[].ext.data.pbadslot is string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abcd' + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data).to.have.property('pbadslot'); + expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); + }); }); - }); - it('should not send a signal to specify that GDPR does not apply to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr).to.equal(0); - expect(request[1].data.gdpr).to.equal(0); - }); + describe('ortb2Imp.ext.data.adserver', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - it('should send the consent string', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - it('should send the consent management framework code', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - }); - }); + it('should not send if imp[].ext.data.adserver is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('adserver'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - describe('when GDPR consent has undefined data', function () { - beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: true - }, - refererInfo: {} - }; + it('should send', function() { + let adSlotValue = 'abc'; + bidRequests[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'GAM', + adslot: adSlotValue + } + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); + expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); + }); + }); - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + describe('ortb2Imp.ext.data.other', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); - }); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - it('should not send a signal to specify whether GDPR applies to this request, when GDPR application is undefined', function () { - delete bidderRequest.gdprConsent.gdprApplies; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('gdpr'); - expect(request[1].data).to.not.have.property('gdpr'); - }); + it('should not send if imp[].ext.data.other is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('other'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - it('should not send the consent string, when consent string is undefined', function () { - delete bidderRequest.gdprConsent.consentString; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('gdpr_consent'); - expect(request[1].data).to.not.have.property('gdpr_consent'); + it('ortb2Imp.ext.data.other', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + other: 1234 + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.other).to.equal(1234); + }); + }); }); - it('should not send the consent management framework code, when format is undefined', function () { - delete mockConfig.consentManagement.cmpApi; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('x_gdpr_f'); - expect(request[1].data).to.not.have.property('x_gdpr_f'); + describe('with user agent client hints', function () { + it('should add device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); + expect(request[0].data.device.sua).to.exist; + expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.device?.sua).to.not.exist; + }); }); }); - }); - - it('should not send a coppa query param when there are no coppa param settings in the bid requests', function () { - const bidRequestsWithoutCoppa = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - coppa: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithoutCoppa, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('tfcd'); - }); - - it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { - const bidRequestsWithCoppa = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - coppa: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain', - coppa: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithCoppa, mockBidderRequest); - expect(request[0].data.tfcd).to.equal(1); - }); - it('should not send a "no segmentation" flag there no DoNotTrack setting that is set to true', function () { - const bidRequestsWithoutDnt = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - doNotTrack: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithoutDnt, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('ns'); - }); + context('when there is a consent management framework', function () { + let bidRequests; + let mockConfig; + let bidderRequest; - it('should send a "no segmentation" flag there is any DoNotTrack setting that is set to true', function () { - const bidRequestsWithDnt = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - doNotTrack: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain', - doNotTrack: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithDnt, mockBidderRequest); - expect(request[0].data.ns).to.equal(1); - }); - - describe('when schain is provided', function () { - let bidRequests; - let schainConfig; - const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; - - beforeEach(function () { - schainConfig = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' - // omitted ext + beforeEach(function () { + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' }, - { - 'asi': 'exchange2.com', - 'sid': 'abcd', - 'hp': 1, - 'rid': 'bid-request-2', - // name field missing - 'domain': 'intermediary.com' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } }, - { - 'asi': 'exchange3.com', - 'sid': '4321', - 'hp': 1, - // request id - // name field missing - 'domain': 'intermediary-2.com' - } - ] - }; + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', - bidRequests = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1', - 'schain': schainConfig - }]; - }); + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); - it('should send a schain parameter with the proper delimiter symbols', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - const numNodes = schainConfig.nodes.length; + describe('us_privacy', function () { + beforeEach(function () { + bidderRequest = { + uspConsent: '1YYN', + refererInfo: {} + }; - // each node will have a ! to denote beginning of a new node - expect(dataParams.schain.match(/!/g).length).to.equal(numNodes); + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); - // 1 comma in the front for version - // 5 commas per node - expect(dataParams.schain.match(/,/g).length).to.equal(numNodes * 5 + 1); - }); + afterEach(function () { + config.getConfig.restore(); + }); - it('should send a schain with the right version', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - let version = serializedSupplyChain.shift().split(',')[0]; + it('should send a signal to specify that US Privacy applies to this request', function () { + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); + expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); + }); - expect(version).to.equal(bidRequests[0].schain.ver); - }); + it('should not send the regs object, when consent string is undefined', function () { + delete bidderRequest.uspConsent; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.us_privacy).to.not.exist; + }); + }); - it('should send a schain with the right complete value', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - let isComplete = serializedSupplyChain.shift().split(',')[1]; + describe('GDPR', function () { + beforeEach(function () { + bidderRequest = { + gdprConsent: { + consentString: 'test-gdpr-consent-string', + addtlConsent: 'test-addtl-consent-string', + gdprApplies: true + }, + refererInfo: {} + }; + + mockConfig = { + consentManagement: { + cmpApi: 'iab', + timeout: 1111, + allowAuctionWithoutConsent: 'cancel' + } + }; - expect(isComplete).to.equal(String(bidRequests[0].schain.complete)); - }); + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); - it('should send all available params in the right order', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - serializedSupplyChain.shift(); + afterEach(function () { + config.getConfig.restore(); + }); - serializedSupplyChain.forEach((serializedNode, nodeIndex) => { - let nodeProperties = serializedNode.split(','); + it('should send a signal to specify that GDPR applies to this request', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(1); + expect(request[1].data.regs.ext.gdpr).to.equal(1); + }); - nodeProperties.forEach((nodeProperty, propertyIndex) => { - let node = schainConfig.nodes[nodeIndex]; - let key = supplyChainNodePropertyOrder[propertyIndex]; + it('should send the consent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); - expect(nodeProperty).to.equal(node[key] ? String(node[key]) : '', - `expected node '${nodeIndex}' property '${nodeProperty}' to key '${key}' to be the same value`) + it('should send the addtlConsent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); + expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); }); - }); - }); - }); - describe('when there are userid providers', function () { - const EXAMPLE_DATA_BY_ATTR = { - britepoolid: '1111-britepoolid', - criteoId: '1111-criteoId', - fabrickId: '1111-fabrickid', - haloId: '1111-haloid', - id5id: {uid: '1111-id5id'}, - idl_env: '1111-idl_env', - IDP: '1111-zeotap-idplusid', - idxId: '1111-idxid', - intentIqId: '1111-intentiqid', - lipb: {lipbid: '1111-lipb'}, - lotamePanoramaId: '1111-lotameid', - merkleId: {id: '1111-merkleid'}, - netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, - pubcid: '1111-pubcid', - quantcastId: '1111-quantcastid', - tapadId: '111-tapadid', - tdid: '1111-tdid', - uid2: {id: '1111-uid2'}, - novatiq: {snowflake: '1111-novatiqid'}, - admixerId: '1111-admixerid', - deepintentId: '1111-deepintentid', - dmdId: '111-dmdid', - nextrollId: '1111-nextrollid', - mwOpenLinkId: '1111-mwopenlinkid', - dapId: '1111-dapId', - amxId: '1111-amxid', - kpuid: '1111-kpuid', - publinkId: '1111-publinkid', - naveggId: '1111-naveggid', - imuid: '1111-imuid', - adtelligentId: '1111-adtelligentid' - }; - - // generates the same set of tests for each id provider - utils._each(USER_ID_CODE_TO_QUERY_ARG, (userIdQueryArg, userIdProviderKey) => { - describe(`with userId attribute: ${userIdProviderKey}`, function () { - it(`should not send a ${userIdQueryArg} query param when there is no userId.${userIdProviderKey} defined in the bid requests`, function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys(userIdQueryArg); + it('should send a signal to specify that GDPR does not apply to this request', function () { + bidderRequest.gdprConsent.gdprApplies = false; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(0); + expect(request[1].data.regs.ext.gdpr).to.equal(0); }); - it(`should send a ${userIdQueryArg} query param when userId.${userIdProviderKey} is defined in the bid requests`, function () { - const bidRequestsWithUserId = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - userId: { - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }]; - // enrich bid request with userId key/value - bidRequestsWithUserId[0].userId[userIdProviderKey] = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; - - const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - - let userIdValue; - // handle cases where userId key refers to an object - switch (userIdProviderKey) { - case 'merkleId': - userIdValue = EXAMPLE_DATA_BY_ATTR.merkleId.id; - break; - case 'uid2': - userIdValue = EXAMPLE_DATA_BY_ATTR.uid2.id; - break; - case 'lipb': - userIdValue = EXAMPLE_DATA_BY_ATTR.lipb.lipbid; - break; - case 'parrableId': - userIdValue = EXAMPLE_DATA_BY_ATTR.parrableId.eid; - break; - case 'id5id': - userIdValue = EXAMPLE_DATA_BY_ATTR.id5id.uid; - break; - case 'novatiq': - userIdValue = EXAMPLE_DATA_BY_ATTR.novatiq.snowflake; - break; - default: - userIdValue = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; - } + it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + + 'but can send consent data, ', function () { + delete bidderRequest.gdprConsent.gdprApplies; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); - expect(request[0].data[USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]]).to.equal(userIdValue); + it('when consent string is undefined, should not send the consent string, ', function () { + delete bidderRequest.gdprConsent.consentString; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.imp[0].ext.consent).to.equal(undefined); + expect(request[1].data.imp[0].ext.consent).to.equal(undefined); }); }); }); - }); - - describe('floors', function () { - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); - context('with floors module', function () { - let adServerCurrencyStub; - - beforeEach(function () { - adServerCurrencyStub = sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') + context('coppa', function() { + it('when there are no coppa param settings, should not send a coppa flag', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs?.coppa).to.be.not.ok; }); - afterEach(function () { - config.getConfig.restore(); - }); - - it('should send out floors on bids', function () { - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return { - currency: 'AUS', - floor: 9.99 - } - } - } - ); - - const bidRequest2 = Object.assign({}, - bidRequestsWithMediaTypes[1], - { - getFloor: () => { - return { - currency: 'AUS', - floor: 18.881 - } - } - } - ); + it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { + let mockConfig = { + coppa: true + }; - const request = spec.buildRequests([bidRequest1, bidRequest2], mockBidderRequest); - const dataParams = request[0].data; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('9990,18881'); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send out floors on bids in the default currency', function () { - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return {}; - } - } - ); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); - expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + it('should send a coppa flag there is when there is coppa param settings in the bid params', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + request.params = {coppa: true}; + expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send out floors on bids in the ad server currency if defined', function () { - adServerCurrencyStub.returns('bitcoin'); - - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return {}; - } - } - ); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); - expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + after(function () { + config.getConfig.restore() }); - }) - }) - }); - - describe('buildRequests for video', function () { - const bidRequestsWithMediaTypes = VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES; - const mockBidderRequest = {refererInfo: {}}; - - it('should send bid request to openx url via GET, with mediaTypes having video parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); - expect(request[0].method).to.equal('GET'); - }); - it('should have the correct parameters', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.auid).to.equal('12345678'); - expect(dataParams.vht).to.equal(480); - expect(dataParams.vwd).to.equal(640); - expect(dataParams.aucs).to.equal(encodeURIComponent('/12345/my-gpt-tag-0')); - }); - - it('shouldn\'t have the test parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.vtest).to.be.undefined; - }); - - it('should send a bc parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.bc).to.have.string('hb_pb'); - }); - - describe('when using the video param', function () { - let videoBidRequest; - let mockBidderRequest = {refererInfo: {}}; - - beforeEach(function () { - videoBidRequest = { - 'bidder': 'openx', - 'mediaTypes': { - video: { - context: 'instream', - playerSize: [640, 480] - } - }, - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - mockBidderRequest = {refererInfo: {}}; - }); - - it('should not allow you to set a url', function () { - videoBidRequest.params.video = { - url: 'test-url' - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.url).to.be.undefined; - }); - - it('should not allow you to override the javascript url', function () { - let myUrl = 'my-url'; - videoBidRequest.params.video = { - ju: myUrl - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.ju).to.not.equal(myUrl); }); - describe('when using the openrtb video params', function () { - it('should parse legacy params.video.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + context('do not track (DNT)', function() { + let doNotTrackStub; - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + beforeEach(function () { + doNotTrackStub = sinon.stub(utils, 'getDNT'); }); - - it('should parse legacy params.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.openrtb = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + afterEach(function() { + doNotTrackStub.restore(); }); - it('should parse legacy params.video', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is a do not track, should send a dnt', function () { + doNotTrackStub.returns(1); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(1); }); - it('should parse legacy params.video as full openrtb', function () { - let myOpenRTBObject = {imp: [{video: {mimes: ['application/javascript']}}]}; - videoBidRequest.params.video = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is not do not track, don\'t send dnt', function () { + doNotTrackStub.returns(0); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); }); - it('should parse legacy video.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is no defined do not track, don\'t send dnt', function () { + doNotTrackStub.returns(null); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); }); + }); - it('should omit filtered values for legacy', function () { - let myOpenRTBObject = {mimes: ['application/javascript'], dont: 'use'}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject + context('supply chain (schain)', function () { + let bidRequests; + let schainConfig; + const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + + beforeEach(function () { + schainConfig = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com' + // omitted ext + }, + { + asi: 'exchange2.com', + sid: 'abcd', + hp: 1, + rid: 'bid-request-2', + // name field missing + domain: 'intermediary.com' + }, + { + asi: 'exchange3.com', + sid: '4321', + hp: 1, + // request id + // name field missing + domain: 'intermediary-2.com' + } + ] }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + bidRequests = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + schain: schainConfig + }]; }); - it('should parse mediatypes.video', function () { - videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] - videoBidRequest.mediaTypes.video.minduration = 15 - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); - expect(openRtbRequestParams.imp[0].video.minduration).to.equal(15); + it('should send a supply chain object', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain).to.equal(schainConfig); }); - it('should filter mediatypes.video', function () { - videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] - videoBidRequest.mediaTypes.video.minnothing = 15 - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); - expect(openRtbRequestParams.imp[0].video.minnothing).to.equal(undefined); + it('should send the supply chain object with the right version', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.ver).to.equal(schainConfig.ver); }); - it("should use the bidRequest's playerSize", function () { - const width = 200; - const height = 100; - const myOpenRTBObject = {v: height, w: width}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - - expect(openRtbRequestParams.imp[0].video.w).to.equal(640); - expect(openRtbRequestParams.imp[0].video.h).to.equal(480); + it('should send the supply chain object with the right complete value', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.complete).to.equal(schainConfig.complete); }); }); - }); - describe('floors', function () { - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], + context('when there are userid providers', function () { + const userIdAsEids = [ { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }, + { + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'sharedid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + third: 'some-random-id-value' + } + }] } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); + ]; - context('with floors module', function () { - let adServerCurrencyStub; - function makeBidWithFloorInfo(floorInfo) { - return Object.assign(utils.deepClone(bidRequestsWithMediaTypes[0]), - { - getFloor: () => { - return floorInfo; + it(`should send the user id under the extended ids`, function () { + const bidRequestsWithUserId = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + userId: { + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] } - }); - } - - beforeEach(function () { - adServerCurrencyStub = sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + userIdAsEids: userIdAsEids + }]; + // enrich bid request with userId key/value + + const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); + expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); }); - afterEach(function () { - config.getConfig.restore(); + it(`when no user ids are available, it should not send any extended ids`, function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data).to.not.have.any.keys('user'); }); + }); - it('should send out floors on bids', function () { - const floors = [9.99, 18.881]; - const bidRequests = floors.map(floor => { - return makeBidWithFloorInfo({ - currency: 'AUS', - floor: floor - }); + context('FLEDGE', function() { + it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, { + ...mockBidderRequest, + fledgeEnabled: true }); - const request = spec.buildRequests(bidRequests, mockBidderRequest); - - expect(request[0].data.aumfs).to.exist; - expect(request[0].data.aumfs).to.equal('9990'); - expect(request[1].data.aumfs).to.exist; - expect(request[1].data.aumfs).to.equal('18881'); + expect(request[0].data.imp[0].ext.ae).to.equal(2); }); + }); + }); - it('should send out floors on bids in the default currency', function () { - const bidRequest1 = makeBidWithFloorInfo({}); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + context('banner', function () { + it('should send bid request with a mediaTypes specified with banner type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0]).to.have.any.keys(BANNER); + }); + }); - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); - expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + if (FEATURES.VIDEO) { + context('video', function () { + it('should send bid request with a mediaTypes specified with video type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); }); - it('should send out floors on bids in the ad server currency if defined', function () { - adServerCurrencyStub.returns('bitcoin'); - - const bidRequest1 = makeBidWithFloorInfo({}); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); - expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + it('Update imp.video with OpenRTB options from mimeTypes and params', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + protocols: [8] + } + }, + }).withParams({ + // options in video, will merge + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30 + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: 4, + protocols: [8], + w: 300, + h: 250 + }; + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests).to.have.lengthOf(2); + expect(requests[1].data.imp[0].video).to.deep.equal(expected); }); - }) - }) + }); + } }); - describe('buildRequest for multi-format ad', function () { - const multiformatBid = MULTI_FORMAT_BID_REQUESTS[0]; - let mockBidderRequest = {refererInfo: {}}; + describe('interpretResponse()', function () { + let bidRequestConfigs; + let bidRequest; + let bidResponse; + let bid; - it('should default to a banner request', function () { - const request = spec.buildRequests([multiformatBid], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.divids).to.have.string(multiformatBid.adUnitCode); - }); - }); - - describe('buildRequests for all kinds of ads', function () { - utils._each({ - banner: BANNER_BID_REQUESTS_WITH_MEDIA_TYPES[0], - video: VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES[0], - multi: MULTI_FORMAT_BID_REQUESTS[0] - }, (bidRequest, name) => { - describe('with segments', function () { - const TESTS = [ - { - name: 'should send proprietary segment data from ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - } - } - }, - expect: {sm: 'dmp1/4:foo|bar,dmp2:baz'}, - }, - { - name: 'should send proprietary segment data from ortb2.site.content.data', - config: { - ortb2: { - site: { - content: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - } - } - } - }, - expect: {scsm: 'dmp1/4:foo|bar,dmp2:baz'}, - }, - { - name: 'should send proprietary segment data from both ortb2.site.content.data and ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - }, - site: { - content: { - data: [ - {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, - {name: 'dmp4', segment: [{id: 'baz2'}]}, - ] - } - } - } - }, - expect: { - sm: 'dmp1/4:foo|bar,dmp2:baz', - scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' - }, - }, - { - name: 'should combine same provider segment data from ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, - ] - } - } - }, - expect: {sm: 'dmp1/4:foo|bar,dmp1:baz'}, - }, - { - name: 'should combine same provider segment data from ortb2.site.content.data', - config: { - ortb2: { - site: { - content: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, - ] - } - } - } - }, - expect: {scsm: 'dmp1/4:foo|bar,dmp1:baz'}, - }, - { - name: 'should not send any segment data if first party config is incomplete', - config: { - ortb2: { - user: { - data: [ - {name: 'provider-with-no-segments'}, - {segment: [{id: 'segments-with-no-provider'}]}, - {}, - ] - } - } - } + context('when there is an nbr response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - { - name: 'should send first party data segments and liveintent segments from request', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - }, - site: { - content: { - data: [ - {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, - {name: 'dmp4', segment: [{id: 'baz2'}]}, - ] - } - } - } - }, - request: { - userId: { - lipb: { - lipbid: 'aaa', - segments: ['l1', 'l2'] - }, - }, - }, - expect: { - sm: 'dmp1:foo|bar,dmp2:baz,liveintent:l1|l2', - scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, }, - { - name: 'should send just liveintent segment from request if no first party config', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - segments: ['l1', 'l2'] - }, - }, - }, - expect: {sm: 'liveintent:l1|l2'}, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {nbr: 0}; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); + }); + + context('when no seatbid in response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - { - name: 'should send nothing if lipb section does not contain segments', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - }, - }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, }, - ]; - utils._each(TESTS, (t) => { - context('in ortb2.user.data', function () { - let bidRequests; - beforeEach(function () { - bidRequests = [{...bidRequest, ...t.request}]; - }); + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - const mockBidderRequest = {refererInfo: {}, ortb2: t.config.ortb2}; - it(`${t.name} for type ${name}`, function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest) - expect(request.length).to.equal(1); - if (t.expect) { - for (const key in t.expect) { - expect(request[0].data[key]).to.exist; - expect(request[0].data[key]).to.equal(t.expect[key]); - } - } else { - expect(request[0].data.sm).to.not.exist; - expect(request[0].data.scsm).to.not.exist; - } - }); - }); - }); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {ext: {}, id: 'test-bid-id'}; + bids = spec.interpretResponse({body: bidResponse}, bidRequest); }); - describe('with user agent client hints', function () { - it('should add json query param sua with BidRequest.device.sua if available', function () { - const bidderRequestWithUserAgentClientHints = { refererInfo: {}, - ortb2: { - device: { - sua: { - source: 2, - platform: { - brand: 'macOS', - version: [ '12', '4', '0' ] - }, - browsers: [ - { - brand: 'Chromium', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Google Chrome', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Not;A=Brand', - version: [ '99', '0', '0', '0' ] - }], - mobile: 0, - model: 'Pro', - bitness: '64', - architecture: 'x86' - } - } - }}; - - let request = spec.buildRequests([bidRequest], bidderRequestWithUserAgentClientHints); - expect(request[0].data.sua).to.exist; - const payload = JSON.parse(request[0].data.sua); - expect(payload).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); - const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; - request = spec.buildRequests([bidRequest], bidderRequestWithoutUserAgentClientHints); - expect(request[0].data.sua).to.not.exist; - }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); }); }); - }) - describe('interpretResponse for banner ads', function () { - beforeEach(function () { - sinon.spy(userSync, 'registerSync'); - }); + context('when there is no response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - afterEach(function () { - userSync.registerSync.restore(); + bidResponse = ''; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); }); - describe('when there is a standard response', function () { - const creativeOverride = { - id: 234, - width: '300', - height: '250', - tracking: { - impression: 'https://openx-d.openx.net/v/1.0/ri?ts=ts' + const SAMPLE_BID_REQUESTS = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + const SAMPLE_BID_RESPONSE = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + adomain: ['brand.com'], + ext: { + dsp_id: '123', + buyer_id: '456', + brand_id: '789', + paf: { + content_id: 'paf_content_id' + } + } + }] + }], + cur: 'AUS', + ext: { + paf: { + transmission: {version: '12'} } - }; - - const adUnitOverride = { - ts: 'test-1234567890-ts', - idx: '0', - currency: 'USD', - pub_rev: '10000', - html: '
OpenX Ad
' - }; - let adUnit; - let bidResponse; - - let bid; - let bidRequest; - let bidRequestConfigs; + } + }; + context('when there is a response, the common response properties', function () { beforeEach(function () { - bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); - adUnit = mockAdUnit(adUnitOverride, creativeOverride); - bidResponse = mockArjResponse(undefined, [adUnit]); bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; }); it('should return a price', function () { - expect(bid.cpm).to.equal(parseInt(adUnitOverride.pub_rev, 10) / 1000); + expect(bid.cpm).to.equal(bidResponse.seatbid[0].bid[0].price); }); it('should return a request id', function () { - expect(bid.requestId).to.equal(bidRequest.payload.bids[0].bidId); + expect(bid.requestId).to.equal(bidResponse.seatbid[0].bid[0].impid); }); it('should return width and height for the creative', function () { - expect(bid.width).to.equal(creativeOverride.width); - expect(bid.height).to.equal(creativeOverride.height); + expect(bid.width).to.equal(bidResponse.seatbid[0].bid[0].w); + expect(bid.height).to.equal(bidResponse.seatbid[0].bid[0].h); }); it('should return a creativeId', function () { - expect(bid.creativeId).to.equal(creativeOverride.id); + expect(bid.creativeId).to.equal(bidResponse.seatbid[0].bid[0].crid); }); it('should return an ad', function () { - expect(bid.ad).to.equal(adUnitOverride.html); + expect(bid.ad).to.equal(bidResponse.seatbid[0].bid[0].adm); + }); + + it('should return a deal id if it exists', function () { + expect(bid.dealId).to.equal(bidResponse.seatbid[0].bid[0].dealid); }); it('should have a time-to-live of 5 minutes', function () { @@ -1863,415 +1286,293 @@ describe('OpenxAdapter', function () { }); it('should return a currency', function () { - expect(bid.currency).to.equal(adUnitOverride.currency); - }); - - it('should return a transaction state', function () { - expect(bid.ts).to.equal(adUnitOverride.ts); + expect(bid.currency).to.equal(bidResponse.cur); }); it('should return a brand ID', function () { - expect(bid.meta.brandId).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.brand_id); + expect(bid.meta.brandId).to.equal(bidResponse.seatbid[0].bid[0].ext.brand_id); }); - it('should return an adomain', function () { - expect(bid.meta.advertiserDomains).to.deep.equal([]); + it('should return a dsp ID', function () { + expect(bid.meta.networkId).to.equal(bidResponse.seatbid[0].bid[0].ext.dsp_id); }); - it('should return a dsp ID', function () { - expect(bid.meta.dspid).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.adv_id); + it('should return a buyer ID', function () { + expect(bid.meta.advertiserId).to.equal(bidResponse.seatbid[0].bid[0].ext.buyer_id); }); - }); - describe('when there is a deal', function () { - const adUnitOverride = { - deal_id: 'ox-1000' - }; - let adUnit; - let bidResponse; + it('should return adomain', function () { + expect(bid.meta.advertiserDomains).to.equal(bidResponse.seatbid[0].bid[0].adomain); + }); - let bid; - let bidRequestConfigs; - let bidRequest; + it('should return paf fields', function () { + const paf = { + transmission: {version: '12'}, + content_id: 'paf_content_id' + } + expect(bid.meta.paf).to.deep.equal(paf); + }); + }); + context('when there is more than one response', () => { + let bids; beforeEach(function () { - bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); + bidResponse.seatbid[0].bid.push(deepClone(bidResponse.seatbid[0].bid[0])); + bidResponse.seatbid[0].bid[1].ext.paf.content_id = 'second_paf' - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; - adUnit = mockAdUnit(adUnitOverride); - bidResponse = mockArjResponse(null, [adUnit]); - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - mockArjResponse(); + bids = spec.interpretResponse({body: bidResponse}, bidRequest); }); - it('should return a deal id', function () { - expect(bid.dealId).to.equal(adUnitOverride.deal_id); + it('should not confuse paf content_id', () => { + expect(bids.map(b => b.meta.paf.content_id)).to.eql(['paf_content_id', 'second_paf']); }); - }); - - describe('when there is no bids in the response', function () { - let bidRequest; - let bidRequestConfigs; + }) + context('when the response is a banner', function() { beforeEach(function () { bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' }]; - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; - }); - - it('handles nobid responses', function () { - const bidResponse = { - 'ads': - { - 'version': 1, - 'count': 1, - 'pixels': 'https://testpixels.net', - 'ad': [] - } + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS' }; - const result = spec.interpretResponse({body: bidResponse}, bidRequest); - expect(result.length).to.equal(0); + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; }); - }); - describe('when adunits return out of order', function () { - const bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[100, 111]] - } - }, - bidId: 'test-bid-request-id-1', - bidderRequestId: 'test-request-1', - auctionId: 'test-auction-id-1' - }, { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[200, 222]] - } - }, - bidId: 'test-bid-request-id-2', - bidderRequestId: 'test-request-1', - auctionId: 'test-auction-id-1' - }, { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 333]] - } - }, - 'bidId': 'test-bid-request-id-3', - 'bidderRequestId': 'test-request-1', - 'auctionId': 'test-auction-id-1' - }]; - const bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequests, 'startTime': new Date()} - }; - - let outOfOrderAdunits = [ - mockAdUnit({ - idx: '1' - }, { - width: bidRequests[1].mediaTypes.banner.sizes[0][0], - height: bidRequests[1].mediaTypes.banner.sizes[0][1] - }), - mockAdUnit({ - idx: '2' - }, { - width: bidRequests[2].mediaTypes.banner.sizes[0][0], - height: bidRequests[2].mediaTypes.banner.sizes[0][1] - }), - mockAdUnit({ - idx: '0' - }, { - width: bidRequests[0].mediaTypes.banner.sizes[0][0], - height: bidRequests[0].mediaTypes.banner.sizes[0][1] - }) - ]; - - let bidResponse = mockArjResponse(undefined, outOfOrderAdunits); - - it('should return map adunits back to the proper request', function () { - const bids = spec.interpretResponse({body: bidResponse}, bidRequest); - expect(bids[0].requestId).to.equal(bidRequests[1].bidId); - expect(bids[0].width).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][0]); - expect(bids[0].height).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][1]); - expect(bids[1].requestId).to.equal(bidRequests[2].bidId); - expect(bids[1].width).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][0]); - expect(bids[1].height).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][1]); - expect(bids[2].requestId).to.equal(bidRequests[0].bidId); - expect(bids[2].width).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][0]); - expect(bids[2].height).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][1]); + it('should return the proper mediaType', function () { + it('should return a creativeId', function () { + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); }); }); - }); - - describe('interpretResponse for video ads', function () { - beforeEach(function () { - sinon.spy(userSync, 'registerSync'); - }); - afterEach(function () { - userSync.registerSync.restore(); - }); - - const bidsWithMediaTypes = [{ - 'bidder': 'openx', - 'mediaTypes': {video: {}}, - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - const bidsWithMediaType = [{ - 'bidder': 'openx', - 'mediaType': 'video', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - const bidRequestsWithMediaTypes = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/avjp', - data: {}, - payload: {'bid': bidsWithMediaTypes[0], 'startTime': new Date()} - }; - const bidRequestsWithMediaType = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/avjp', - data: {}, - payload: {'bid': bidsWithMediaType[0], 'startTime': new Date()} - }; - const bidResponse = { - 'pub_rev': '1000', - 'width': '640', - 'height': '480', - 'adid': '5678', - 'currency': 'AUD', - 'vastUrl': 'https://testvast.com', - 'pixels': 'https://testpixels.net' - }; + if (FEATURES.VIDEO) { + context('when the response is a video', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [[640, 360], [854, 480]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 854, + h: 480, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + }] + }], + cur: 'AUS' + }; + }); - it('should return correct bid response with MediaTypes', function () { - const expectedResponse = { - 'requestId': '30b31c1838de1e', - 'cpm': 1, - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'creativeId': '5678', - 'vastUrl': 'https://testvast.com', - 'ttl': 300, - 'netRevenue': true, - 'currency': 'AUD' - }; - - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - expect(result[0]).to.eql(expectedResponse); - }); + it('should return the proper mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); - it('should return correct bid response with MediaType', function () { - const expectedResponse = [ - { - 'requestId': '30b31c1838de1e', - 'cpm': 1, - 'width': '640', - 'height': '480', - 'mediaType': 'video', - 'creativeId': '5678', - 'vastUrl': 'https://testvast.com', - 'ttl': 300, - 'netRevenue': true, - 'currency': 'USD' - } - ]; + it('should return the proper mediaType', function () { + const winUrl = 'https//my.win.url'; + bidResponse.seatbid[0].bid[0].nurl = winUrl + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); - expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); - }); + expect(bid.vastUrl).to.equal(winUrl); + }); + }); + } - it('should return correct bid response with MediaType and deal_id', function () { - const bidResponseOverride = { 'deal_id': 'OX-mydeal' }; - const bidResponseWithDealId = Object.assign({}, bidResponse, bidResponseOverride); - const result = spec.interpretResponse({body: bidResponseWithDealId}, bidRequestsWithMediaType); - expect(result[0].dealId).to.equal(bidResponseOverride.deal_id); - }); + context('when the response contains FLEDGE interest groups config', function() { + let response; - it('should handle nobid responses for bidRequests with MediaTypes', function () { - const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - expect(result.length).to.equal(0); - }); + beforeEach(function () { + sinon.stub(config, 'getConfig') + .withArgs('fledgeEnabled') + .returns(true); - it('should handle nobid responses for bidRequests with MediaType', function () { - const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); - expect(result.length).to.equal(0); - }); - }); + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - describe('user sync', function () { - const syncUrl = 'https://testpixels.net'; + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS', + ext: { + fledge_auction_configs: { + 'test-bid-id': { + seller: 'codinginadtech.com', + interestGroupBuyers: ['somedomain.com'], + sellerTimeout: 0, + perBuyerSignals: { + 'somedomain.com': { + base_bid_micros: 0.1, + disallowed_advertiser_ids: [ + '1234', + '2345' + ], + multiplier: 1.3, + use_bid_multiplier: true, + win_reporting_id: '1234567asdf' + } + } + } + } + } + }; - describe('iframe sync', function () { - it('should register the pixel iframe from banner ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); - it('should register the pixel iframe from video ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {pixels: syncUrl}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + afterEach(function () { + config.getConfig.restore(); }); - it('should register the default iframe if no pixels available', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: 'https://u.openx.net/w/1.0/pd'}]); + it('should return FLEDGE auction_configs alongside bids', function () { + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs.length).to.equal(1); + expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); }); }); + }); - describe('pixel sync', function () { - it('should register the image pixel from banner ad response', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); - }); + describe('user sync', function () { + it('should register the default image pixel if no pixels available', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'image', url: DEFAULT_SYNC}]); + }); - it('should register the image pixel from video ad response', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {pixels: syncUrl}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); - }); + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {delDomain: 'www.url.com'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: 'https://www.url.com/w/1.0/pd'}]); + }); - it('should register the default image pixel if no pixels available', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'image', url: 'https://u.openx.net/w/1.0/pd'}]); - }); + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {platform: 'abc'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: SYNC_URL + '?ph=abc'}]); + }); + + it('when iframe sync is allowed, it should register an iframe sync', function () { + let syncs = spec.getUserSyncs( + {iframeEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); }); it('should prioritize iframe over image for user sync', function () { let syncs = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] + [] ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); }); describe('when gdpr applies', function () { let gdprConsent; let gdprPixelUrl; + const consentString = 'gdpr-pixel-consent'; + const gdprApplies = '1'; beforeEach(() => { gdprConsent = { - consentString: 'test-gdpr-consent-string', + consentString, gdprApplies: true }; - gdprPixelUrl = 'https://testpixels.net?gdpr=1&gdpr_consent=gdpr-pixel-consent' + gdprPixelUrl = `${SYNC_URL}&gdpr=${gdprApplies}&gdpr_consent=${consentString}`; }); it('when there is a response, it should have the gdpr query params', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: gdprPixelUrl}}}], - gdprConsent - ); - - expect(url).to.have.string('gdpr_consent=gdpr-pixel-consent'); - expect(url).to.have.string('gdpr=1'); - }); - - it('when there is no response, it should append gdpr query params', () => { let [{url}] = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, [], gdprConsent ); - expect(url).to.have.string('gdpr_consent=test-gdpr-consent-string'); - expect(url).to.have.string('gdpr=1'); + + expect(url).to.have.string(`gdpr_consent=${consentString}`); + expect(url).to.have.string(`gdpr=${gdprApplies}`); }); it('should not send signals if no consent object is available', function () { @@ -2287,28 +1588,19 @@ describe('OpenxAdapter', function () { describe('when ccpa applies', function () { let usPrivacyConsent; let uspPixelUrl; + const privacyString = 'TEST'; beforeEach(() => { usPrivacyConsent = 'TEST'; - uspPixelUrl = 'https://testpixels.net?us_privacy=AAAA' + uspPixelUrl = `${DEFAULT_SYNC}&us_privacy=${privacyString}` }); - it('when there is a response, it should send the us privacy string from the response, ', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: uspPixelUrl}}}], - undefined, - usPrivacyConsent - ); - - expect(url).to.have.string('us_privacy=AAAA'); - }); - it('when there is no response, it send have the us privacy string', () => { + it('should send the us privacy string, ', () => { let [{url}] = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, [], undefined, usPrivacyConsent ); - expect(url).to.have.string(`us_privacy=${usPrivacyConsent}`); + expect(url).to.have.string(`us_privacy=${privacyString}`); }); it('should not send signals if no consent string is available', function () { @@ -2320,75 +1612,4 @@ describe('OpenxAdapter', function () { }); }); }); - - /** - * Makes sure the override object does not introduce - * new fields against the contract - * - * This does a shallow check in order to make key checking simple - * with respect to what a helper handles. For helpers that have - * nested fields, either check your design on maybe breaking it up - * to smaller, manageable pieces - * - * OR just call this on your nth level field if necessary. - * - * @param {Object} override Object with keys that overrides the default - * @param {Object} contract Original object contains the default fields - * @param {string} typeName Name of the type we're checking for error messages - * @throws {AssertionError} - */ - function overrideKeyCheck(override, contract, typeName) { - expect(contract).to.include.all.keys(Object.keys(override)); - } - - /** - * Creates a mock ArjResponse - * @param {OxArjResponse=} response - * @param {Array=} adUnits - * @throws {AssertionError} - * @return {OxArjResponse} - */ - function mockArjResponse(response, adUnits = []) { - let mockedArjResponse = utils.deepClone(DEFAULT_ARJ_RESPONSE); - - if (response) { - overrideKeyCheck(response, DEFAULT_ARJ_RESPONSE, 'OxArjResponse'); - overrideKeyCheck(response.ads, DEFAULT_ARJ_RESPONSE.ads, 'OxArjResponse'); - Object.assign(mockedArjResponse, response); - } - - if (adUnits.length) { - mockedArjResponse.ads.count = adUnits.length; - mockedArjResponse.ads.ad = adUnits.map((adUnit) => { - overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); - return Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); - }); - } - - return mockedArjResponse; - } - - /** - * Creates a mock ArjAdUnit - * @param {OxArjAdUnit=} adUnit - * @param {OxArjCreative=} creative - * @throws {AssertionError} - * @return {OxArjAdUnit} - */ - function mockAdUnit(adUnit, creative) { - overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); - - let mockedAdUnit = Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); - - if (creative) { - overrideKeyCheck(creative, DEFAULT_TEST_ARJ_CREATIVE); - if (creative.tracking) { - overrideKeyCheck(creative.tracking, DEFAULT_TEST_ARJ_CREATIVE.tracking, 'OxArjCreative'); - } - Object.assign(mockedAdUnit.creative[0], creative); - } - - return mockedAdUnit; - } -}) -; +}); diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index 951399457c3..d8ea79ac698 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -1096,45 +1096,6 @@ describe('OpenxRtbAdapter', function () { }); }); } - - it.skip('should send ad unit ids when any are defined', function () { - const bidRequestsWithUnitIds = [{ - bidder: 'openx', - params: { - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - transactionId: 'test-transaction-id-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2', - transactionId: 'test-transaction-id-2' - }]; - mockBidderRequest.bids = bidRequestsWithUnitIds; - const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); - expect(request[0].data.imp[1].tagid).to.equal(bidRequestsWithUnitIds[1].params.unit); - expect(request[0].data.imp[1].ext.divid).to.equal(bidRequestsWithUnitIds[1].params.adUnitCode); - }); }); describe('interpretResponse()', function () { From d3b108f0d35cc44cd27a509ace2168fe6075efef Mon Sep 17 00:00:00 2001 From: ivs-mark <128337031+ivs-mark@users.noreply.github.com> Date: Fri, 14 Apr 2023 01:02:41 +0800 Subject: [PATCH 311/375] IVS Bid Adapter: initial adapter release (#9706) * Add IVS bid adapter * Fix publisherId in test parameters --- modules/ivsBidAdapter.js | 85 +++++++++++ modules/ivsBidAdapter.md | 34 +++++ test/spec/modules/ivsBidAdapter_spec.js | 195 ++++++++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 modules/ivsBidAdapter.js create mode 100644 modules/ivsBidAdapter.md create mode 100644 test/spec/modules/ivsBidAdapter_spec.js diff --git a/modules/ivsBidAdapter.js b/modules/ivsBidAdapter.js new file mode 100644 index 00000000000..47685fbbe46 --- /dev/null +++ b/modules/ivsBidAdapter.js @@ -0,0 +1,85 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { deepAccess, deepSetValue, getBidIdParameter, logError } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { INSTREAM } from '../src/video.js'; + +const BIDDER_CODE = 'ivs'; +const ENDPOINT_URL = 'https://a.ivstracker.net/prod/openrtb/2.5'; + +export const converter = ortbConverter({ + context: { + mediaType: VIDEO, + ttl: 360, + netRevenue: true + } +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if (bid && typeof bid.params !== 'object') { + logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); + return false; + } + + if (!deepAccess(bid, 'mediaTypes.video')) { + logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); + return false; + } + + if (deepAccess(bid, 'mediaTypes.video.context') !== INSTREAM) { + logError(BIDDER_CODE + ': only instream video context is allowed.'); + return false; + } + + if (!getBidIdParameter('publisherId', bid.params)) { + logError(BIDDER_CODE + ': publisherId is not present in bidder params.'); + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const data = converter.toORTB({ bidderRequest, validBidRequests, context: {mediaType: 'video'} }); + deepSetValue(data.site, 'publisher.id', validBidRequests[0].params.publisherId); + + return { + method: 'POST', + url: ENDPOINT_URL, + data: data, + options: { + contentType: 'application/json' + }, + }; + }, + + /** + * 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) { + if (!serverResponse.body) return; + return converter.fromORTB({ response: serverResponse.body, request: bidRequest.data }).bids; + }, +} + +registerBidder(spec); diff --git a/modules/ivsBidAdapter.md b/modules/ivsBidAdapter.md new file mode 100644 index 00000000000..d50061b640d --- /dev/null +++ b/modules/ivsBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: IVS Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@ivs.tv +``` + +# Description + +Module that connects to IVS's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'ivs', + params: { + publisherId: '3001234' // required + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/ivsBidAdapter_spec.js b/test/spec/modules/ivsBidAdapter_spec.js new file mode 100644 index 00000000000..0c10856a046 --- /dev/null +++ b/test/spec/modules/ivsBidAdapter_spec.js @@ -0,0 +1,195 @@ +import { spec, converter } from 'modules/ivsBidAdapter.js'; +import { assert } from 'chai'; +import { deepClone } from '../../../src/utils'; + +describe('ivsBidAdapter', function () { + describe('isBidRequestValid()', function () { + let validBid = { + bidder: 'ivs', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + params: { + bidderDomain: 'https://www.example.com', + publisherId: '3001234' + } + }; + + it('should return true for a valid bid', function () { + assert.isTrue(spec.isBidRequestValid(validBid)); + }); + + it('should return false if publisherId info is missing', function () { + let bid = deepClone(validBid); + delete bid.params.publisherId; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false for empty video parameters', function () { + let bid = deepClone(validBid); + delete bid.mediaTypes.video; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false for non instream context', function () { + let bid = deepClone(validBid); + bid.mediaTypes.video.context = 'outstream'; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests()', function () { + let validBidRequests, validBidderRequest; + + beforeEach(function () { + validBidRequests = [{ + bidder: 'ivs', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + }, + adUnitCode: 'video1', + transactionId: '1f420478-a3cd-452d-8e33-ac851e7bfba6', + bidId: '2d986cea00fd01', + bidderRequestId: '1022d594d79bf5', + auctionId: '835eacc9-cfe7-4fa2-8738-ab4b5c4f26d2' + }, + params: { + bidderDomain: 'https://www.example.com', + publisherId: '3001234' + } + }]; + + validBidderRequest = { + bidderCode: 'ivs', + auctionId: '409bd13d-d0be-43c4-9c4f-e6f81ecff475', + bidderRequestId: '17bfe74bd98e68', + refererInfo: { + domain: 'example.com', + page: 'https://www.example.com/test.html', + }, + bids: [{ + bidder: 'ivs', + params: { + publisherId: '3001234' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + mimes: ['video/mp4'] + } + }, + adUnitCode: 'video1', + transactionId: '91b1977f-d05c-45c3-af1f-69b4e7d11e86', + sizes: [ + [640, 480] + ], + }], + ortb2: { + site: { + publisher: { + domain: 'example.com', + } + } + } + }; + }); + + it('should return a validate bid request', function () { + const bidRequest = spec.buildRequests(validBidRequests, validBidderRequest); + assert.equal(bidRequest.method, 'POST'); + assert.deepEqual(bidRequest.options, { contentType: 'application/json' }); + assert.ok(bidRequest.data); + }); + + it('should contain the required parameters', function () { + const bidRequest = spec.buildRequests(validBidRequests, validBidderRequest); + const bidderRequest = bidRequest.data; + assert.equal(bidderRequest.id, validBidderRequest.auctionId); + assert.ok(bidderRequest.site); + assert.ok(bidderRequest.source); + assert.lengthOf(bidderRequest.imp, 1); + }); + }); + + describe('interpretResponse()', function () { + let serverResponse, bidderRequest, request; + + beforeEach(function () { + serverResponse = { + body: { + id: '635ba1ed-68ba-47b4-bcec-4a86565f25f9', + seatbid: [{ + bid: [{ + crid: 3715, + id: 'bca9823d-ca7a-4dac-b292-0e1fae5948f8', + impid: '200d1ca23b15a6', + price: 1.5, + nurl: 'https://a.ivstracker.net/dev/getvastxml?ad_creativeid=3715&domain=localhost%3A9999&ip=136.158.51.114&pageurl=http%3A%2F%2Flocalhost%3A9999%2Ftest%2Fpages%2Fivs.html&spid=3001234&adplacement=&brand=unknown&device=desktop&adsclientid=A45-fd46289e-dc60-4be2-a637-4bc8eb953ddf&clientid=3b5e435f-0351-4ba0-bd2d-8d6f3454c5ed&uastring=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F109.0.0.0%20Safari%2F537.36' + }] + }], + cur: 'USD' + }, + headers: {} + }; + + bidderRequest = { + bidderCode: 'ivs', + auctionId: '635ba1ed-68ba-47b4-bcec-4a86565f25f9', + bidderRequestId: '1def3e1d03f5a', + bids: [{ + bidder: 'ivs', + params: { + publisherId: '3001234' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [640, 480] + ], + mimes: ['video/mp4'] + } + }, + adUnitCode: 'video1', + transactionId: '89e5a3e7-df30-4ed6-a130-edfa91941e67', + bidId: '200d1ca23b15a6', + bidderRequestId: '1def3e1d03f5a', + auctionId: '635ba1ed-68ba-47b4-bcec-4a86565f25f9' + }], + }; + + request = { data: converter.toORTB({ bidderRequest }) }; + }); + + it('should match parsed server response', function () { + const results = spec.interpretResponse(serverResponse, request); + const expected = { + mediaType: 'video', + playerWidth: 640, + playerHeight: 480, + vastUrl: 'https://a.ivstracker.net/dev/getvastxml?ad_creativeid=3715&domain=localhost%3A9999&ip=136.158.51.114&pageurl=http%3A%2F%2Flocalhost%3A9999%2Ftest%2Fpages%2Fivs.html&spid=3001234&adplacement=&brand=unknown&device=desktop&adsclientid=A45-fd46289e-dc60-4be2-a637-4bc8eb953ddf&clientid=3b5e435f-0351-4ba0-bd2d-8d6f3454c5ed&uastring=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F109.0.0.0%20Safari%2F537.36', + requestId: '200d1ca23b15a6', + seatBidId: 'bca9823d-ca7a-4dac-b292-0e1fae5948f8', + cpm: 1.5, + currency: 'USD', + creativeId: 3715, + ttl: 360, + }; + + expect(results.length).to.equal(1); + sinon.assert.match(results[0], expected); + }); + + it('should return empty when no response', function () { + assert.ok(!spec.interpretResponse({}, request)); + }); + }); +}); From 578bda0cb2f93a2a6747c164017a6b4446d265b2 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 13 Apr 2023 11:46:07 -0700 Subject: [PATCH 312/375] IVS bid adapter: fix tests (#9807) --- test/spec/modules/ivsBidAdapter_spec.js | 38 +++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/test/spec/modules/ivsBidAdapter_spec.js b/test/spec/modules/ivsBidAdapter_spec.js index 0c10856a046..79b3d6811f4 100644 --- a/test/spec/modules/ivsBidAdapter_spec.js +++ b/test/spec/modules/ivsBidAdapter_spec.js @@ -169,24 +169,26 @@ describe('ivsBidAdapter', function () { request = { data: converter.toORTB({ bidderRequest }) }; }); - it('should match parsed server response', function () { - const results = spec.interpretResponse(serverResponse, request); - const expected = { - mediaType: 'video', - playerWidth: 640, - playerHeight: 480, - vastUrl: 'https://a.ivstracker.net/dev/getvastxml?ad_creativeid=3715&domain=localhost%3A9999&ip=136.158.51.114&pageurl=http%3A%2F%2Flocalhost%3A9999%2Ftest%2Fpages%2Fivs.html&spid=3001234&adplacement=&brand=unknown&device=desktop&adsclientid=A45-fd46289e-dc60-4be2-a637-4bc8eb953ddf&clientid=3b5e435f-0351-4ba0-bd2d-8d6f3454c5ed&uastring=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F109.0.0.0%20Safari%2F537.36', - requestId: '200d1ca23b15a6', - seatBidId: 'bca9823d-ca7a-4dac-b292-0e1fae5948f8', - cpm: 1.5, - currency: 'USD', - creativeId: 3715, - ttl: 360, - }; - - expect(results.length).to.equal(1); - sinon.assert.match(results[0], expected); - }); + if (FEATURES.VIDEO) { + it('should match parsed server response', function () { + const results = spec.interpretResponse(serverResponse, request); + const expected = { + mediaType: 'video', + playerWidth: 640, + playerHeight: 480, + vastUrl: 'https://a.ivstracker.net/dev/getvastxml?ad_creativeid=3715&domain=localhost%3A9999&ip=136.158.51.114&pageurl=http%3A%2F%2Flocalhost%3A9999%2Ftest%2Fpages%2Fivs.html&spid=3001234&adplacement=&brand=unknown&device=desktop&adsclientid=A45-fd46289e-dc60-4be2-a637-4bc8eb953ddf&clientid=3b5e435f-0351-4ba0-bd2d-8d6f3454c5ed&uastring=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F109.0.0.0%20Safari%2F537.36', + requestId: '200d1ca23b15a6', + seatBidId: 'bca9823d-ca7a-4dac-b292-0e1fae5948f8', + cpm: 1.5, + currency: 'USD', + creativeId: 3715, + ttl: 360, + }; + + expect(results.length).to.equal(1); + sinon.assert.match(results[0], expected); + }); + } it('should return empty when no response', function () { assert.ok(!spec.interpretResponse({}, request)); From fc5492699f85612fd3d7af9a4526e8d97a63bc8e Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 13 Apr 2023 19:03:56 +0000 Subject: [PATCH 313/375] Prebid 7.45.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 75ceaa1ac8e..0cac7fbdeb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.45.0-pre", + "version": "7.45.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index f710f319cd5..a46cddc621c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.45.0-pre", + "version": "7.45.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 67c778a0e35f564946c24e58ba78255b414a9ecb Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 13 Apr 2023 19:03:56 +0000 Subject: [PATCH 314/375] Increment version to 7.46.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0cac7fbdeb6..54442c348e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.45.0", + "version": "7.46.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index a46cddc621c..7ebcb655ed7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.45.0", + "version": "7.46.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From e7c26f8305329be11292ca5ab679aa4cbdf881a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Fri, 14 Apr 2023 16:40:50 +0200 Subject: [PATCH 315/375] Criteo Bid Adapter: Fix invalid deal property name mapping from bidder response (#9808) Currently, our PBJS adapter code expects to receive a property called 'dealCode' where our bidder emits 'deal' instead. We've updated our backend to temporary emit both dealCode & deal but long term we'd like to align to use 'deal 'across all integrations. --- modules/criteoBidAdapter.js | 2 +- test/spec/modules/criteoBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 9e179d7b332..cdb07252484 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -223,7 +223,7 @@ export const spec = { creativeId: slot.creativecode, width: slot.width, height: slot.height, - dealId: slot.dealCode, + dealId: slot.deal, }; if (body.ext?.paf?.transmission && slot.ext?.paf?.content_id) { const pafResponseMeta = { diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 69574166481..a05a25ff235 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1475,7 +1475,7 @@ describe('The Criteo bidding adapter', function () { creativecode: 'test-crId', width: 728, height: 90, - dealCode: 'myDealCode', + deal: 'myDealCode', adomain: ['criteo.com'], }], }, From ab4741db894cee4f2c08c6a28467c9bd753b6613 Mon Sep 17 00:00:00 2001 From: Jeremy Sadwith Date: Fri, 14 Apr 2023 15:41:35 -0400 Subject: [PATCH 316/375] Kargo Bid Adapter: Refactor of bid request (#9731) * pageURL pull from topmostLocation * Kargo: Support for client hints (#9) * Starting SUA support * Kargo: Adding support for client hints * Adding tests for sua * Kargo: Update referer logic * Refactor of Kargo Prebid adapter. * PR comments addressed. * Feedback addressed. * Pr comments addressed. * Continuing refactor of Kargo Bid adapter. * Logic adjustment to exclude values when not present. Relying on server defaults. * Updating unit tests. * PR feedback addressed. * Refactoring bid adapter functions. * PR feedback addressed. * Additional refactoring. * Refactoring for each to use Object entries. * Minor fixes. * Minor fixes. * Minor fixes. * TDID and linting updates * Conflicts resolved with master. * Re-adding raw CRB storage (#14) * Updating shared IDs object name * Fixing missing ad markup * Removing package json changes. Fixing unit tests broken by recent changes. * Linting * send requestCount even when it is 0 for BTO (#18) * Reverting package.json change * Reverting package-lock.json changes * Cleanup * Test cleanup * Test fix Test fix All tests fixed * Adding test for TDID * Resolving merge issue --------- Co-authored-by: Neil Flynn Co-authored-by: Julian Gan --- modules/kargoBidAdapter.js | 705 ++++++++++++++-------- test/spec/modules/kargoBidAdapter_spec.js | 422 +++++++++---- 2 files changed, 745 insertions(+), 382 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 0c647095e24..ecc40b26aa2 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -1,295 +1,504 @@ -import { _each, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; +import { _each, isEmpty, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; 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]; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +const PREBID_VERSION = '$prebid.version$' + +const BIDDER = Object.freeze({ + CODE: 'kargo', + HOST: 'krk2.kargo.com', + REQUEST_METHOD: 'POST', + REQUEST_ENDPOINT: '/api/v1/prebid', + TIMEOUT_ENDPOINT: '/api/v1/event/timeout', + GVLID: 972, + SUPPORTED_MEDIA_TYPES: [BANNER, VIDEO], +}); + +const STORAGE = getStorageManager({bidderCode: BIDDER.CODE}); + +const CURRENCY = Object.freeze({ + KEY: 'currency', + US_DOLLAR: 'USD', +}); + +const REQUEST_KEYS = Object.freeze({ + SOCIAL_CANVAS: 'params.socialCanvas', + SUA: 'ortb2.device.sua', + TDID_ADAPTER: 'userId.tdid', +}); + +const SUA = Object.freeze({ + BROWSERS: 'browsers', + MOBILE: 'mobile', + MODEL: 'model', + PLATFORM: 'platform', + SOURCE: 'source', +}); + +const SUA_ATTRIBUTES = [ + SUA.BROWSERS, + SUA.MOBILE, + SUA.MODEL, + SUA.SOURCE, + SUA.PLATFORM, +]; + +const CERBERUS = Object.freeze({ + KEY: 'krg_crb', + SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}', + SYNC_COUNT: 5, + PAGE_VIEW_ID: 'pageViewId', + PAGE_VIEW_TIMESTAMP: 'pageViewTimestamp', + PAGE_VIEW_URL: 'pageViewUrl' +}); let sessionId, lastPageUrl, requestCounter; -export const spec = { - gvlid: GVLID, - code: BIDDER_CODE, - isBidRequestValid: function(bid) { - if (!bid || !bid.params) { - return false; - } +function isBidRequestValid(bid) { + if (!bid || !bid.params) { + return false; + } - return !!bid.params.placementId; - }, - buildRequests: function(validBidRequests, bidderRequest) { - const currencyObj = config.getConfig('currency'); - const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; - const bidIDs = {}; - const bidSizes = {}; - - _each(validBidRequests, bid => { - bidIDs[bid.bidId] = bid.params.placementId; - bidSizes[bid.bidId] = bid.sizes; - }); + return !!bid.params.placementId; +} + +function buildRequests(validBidRequests, bidderRequest) { + const currencyObj = config.getConfig(CURRENCY.KEY); + const currency = (currencyObj && currencyObj.adServerCurrency) ? currencyObj.adServerCurrency : null; + const impressions = []; + + _each(validBidRequests, bid => { + impressions.push(getImpression(bid)) + }); + + const firstBidRequest = validBidRequests[0]; + const tdidAdapter = deepAccess(firstBidRequest, REQUEST_KEYS.TDID_ADAPTER); + + const metadata = getAllMetadata(bidderRequest); + + const krakenParams = Object.assign({}, { + pbv: PREBID_VERSION, + aid: firstBidRequest.auctionId, + sid: _getSessionId(), + url: metadata.pageURL, + timeout: bidderRequest.timeout, + ts: new Date().getTime(), + device: { + size: [ + window.screen.width, + window.screen.height + ] + }, + imp: impressions, + user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids), + }); + + const reqCount = getRequestCount() + if (reqCount != null) { + krakenParams.requestCount = reqCount; + } - const firstBidRequest = validBidRequests[0]; - - const tdid = deepAccess(firstBidRequest, 'userId.tdid') - - const transformedParams = Object.assign({}, { - sessionId: spec._getSessionId(), - requestCount: spec._getRequestCount(), - timeout: bidderRequest.timeout, - currency, - cpmGranularity: 1, - timestamp: (new Date()).getTime(), - cpmRange: { - floor: 0, - ceil: 20 - }, - bidIDs, - bidSizes, - device: { - width: window.screen.width, - height: window.screen.height - }, - prebidRawBidRequests: validBidRequests, - prebidVersion: PREBID_VERSION - }, spec._getAllMetadata(bidderRequest, tdid)); - - // User Agent Client Hints / SUA - const uaClientHints = deepAccess(firstBidRequest, 'ortb2.device.sua'); - if (uaClientHints) { - transformedParams.device.sua = pick(uaClientHints, ['browsers', 'platform', 'mobile', 'model']); - } + if (currency != null && currency != CURRENCY.US_DOLLAR) { + krakenParams.cur = currency; + } - // Pull Social Canvas segments and embed URL - const socialCanvas = deepAccess(firstBidRequest, 'params.socialCanvas'); - if (socialCanvas) { - transformedParams.socialCanvasSegments = socialCanvas.segments; - transformedParams.socialEmbedURL = socialCanvas.embedURL; - } + if (metadata.rawCRB != null) { + krakenParams.rawCRB = metadata.rawCRB + } - const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); - return Object.assign({}, bidderRequest, { - method: 'GET', - url: `${HOST}/api/v2/bid`, - data: `json=${encodedParams}`, - currency: currency - }); - }, - interpretResponse: function(response, bidRequest) { - let bids = response.body; - const bidResponses = []; - for (let bidId in bids) { - let adUnit = bids[bidId]; - let meta = { - mediaType: BANNER - }; - - if (adUnit.metadata && adUnit.metadata.landingPageDomain) { - meta.clickUrl = adUnit.metadata.landingPageDomain[0]; - meta.advertiserDomains = adUnit.metadata.landingPageDomain; + if (metadata.rawCRBLocalStorage != null) { + krakenParams.rawCRBLocalStorage = metadata.rawCRBLocalStorage + } + + // Pull Social Canvas segments and embed URL + const socialCanvas = deepAccess(firstBidRequest, REQUEST_KEYS.SOCIAL_CANVAS); + + if (socialCanvas != null) { + krakenParams.socan = socialCanvas; + } + + // User Agent Client Hints / SUA + const uaClientHints = deepAccess(firstBidRequest, REQUEST_KEYS.SUA); + if (uaClientHints) { + const suaValidAttributes = [] + + SUA_ATTRIBUTES.forEach(suaKey => { + const suaValue = uaClientHints[suaKey]; + if (!suaValue) { + return; } - if (adUnit.mediaType && SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType)) { - meta.mediaType = adUnit.mediaType; + // Do not pass any empty strings + if (typeof suaValue == 'string' && suaValue.trim() === '') { + return; } - const bidResponse = { - ad: adUnit.adm, - requestId: bidId, - cpm: Number(adUnit.cpm), - width: adUnit.width, - height: adUnit.height, - ttl: 300, - creativeId: adUnit.id, - dealId: adUnit.targetingCustom, - netRevenue: true, - currency: adUnit.currency || bidRequest.currency, - mediaType: meta.mediaType, - meta: meta - }; - - if (meta.mediaType == VIDEO) { - if (adUnit.admUrl) { - bidResponse.vastUrl = adUnit.admUrl; - } else { - bidResponse.vastXml = adUnit.adm; - } + switch (suaKey) { + case SUA.MOBILE && suaValue < 1: // Do not pass 0 value for mobile + case SUA.SOURCE && suaValue < 1: // Do not pass 0 value for source + break; + default: + suaValidAttributes.push(suaKey); } + }); - bidResponses.push(bidResponse); - } + krakenParams.device.sua = pick(uaClientHints, suaValidAttributes); + } + + const validPageId = getLocalStorageSafely(CERBERUS.PAGE_VIEW_ID) != null + const validPageTimestamp = getLocalStorageSafely(CERBERUS.PAGE_VIEW_TIMESTAMP) != null + const validPageUrl = getLocalStorageSafely(CERBERUS.PAGE_VIEW_URL) != null + + const page = {} + if (validPageId) { + page.id = getLocalStorageSafely(CERBERUS.PAGE_VIEW_ID); + } + if (validPageTimestamp) { + page.timestamp = Number(getLocalStorageSafely(CERBERUS.PAGE_VIEW_TIMESTAMP)); + } + if (validPageUrl) { + page.url = getLocalStorageSafely(CERBERUS.PAGE_VIEW_URL); + } + if (!isEmpty(page)) { + krakenParams.page = page; + } + + return Object.assign({}, bidderRequest, { + method: BIDDER.REQUEST_METHOD, + url: `https://${BIDDER.HOST}${BIDDER.REQUEST_ENDPOINT}`, + data: krakenParams, + currency: currency + }); +} + +function interpretResponse(response, bidRequest) { + let bids = response.body; + const bidResponses = []; + if (isEmpty(bids)) { return bidResponses; - }, - getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { - const syncs = []; - const seed = spec._generateRandomUuid(); - const clientId = spec._getClientId(); - var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; - var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; - // don't sync if opted out via usPrivacy - if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { - return syncs; + } + + if (typeof bids !== 'object') { + return bidResponses; + } + + Object.entries(bids).forEach((entry) => { + const [bidID, adUnit] = entry; + + let meta = { + mediaType: adUnit.mediaType && BIDDER.SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType) ? adUnit.mediaType : BANNER + }; + + if (adUnit.metadata && adUnit.metadata.landingPageDomain) { + meta.clickUrl = adUnit.metadata.landingPageDomain[0]; + meta.advertiserDomains = adUnit.metadata.landingPageDomain; } - if (syncOptions.iframeEnabled && seed && clientId) { - for (let i = 0; i < SYNC_COUNT; i++) { - syncs.push({ - type: 'iframe', - url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed) - .replace('{INDEX}', i) - .replace('{GDPR}', gdpr) - .replace('{GDPR_CONSENT}', gdprConsentString) - .replace('{US_PRIVACY}', usPrivacy || '') - }); + + const bidResponse = { + requestId: bidID, + cpm: Number(adUnit.cpm), + width: adUnit.width, + height: adUnit.height, + ttl: 300, + creativeId: adUnit.id, + dealId: adUnit.targetingCustom, + netRevenue: true, + currency: adUnit.currency || bidRequest.currency, + mediaType: meta.mediaType, + meta: meta + }; + + if (meta.mediaType == VIDEO) { + if (adUnit.admUrl) { + bidResponse.vastUrl = adUnit.admUrl; + } else { + bidResponse.vastXml = adUnit.adm; } + } else { + bidResponse.ad = adUnit.adm; } + + bidResponses.push(bidResponse); + }) + + return bidResponses; +} + +function getUserSyncs(syncOptions, responses, gdprConsent, usPrivacy) { + const syncs = []; + const seed = _generateRandomUUID(); + const clientId = getClientId(); + + var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; + var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; + + // don't sync if opted out via usPrivacy + if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { return syncs; - }, - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - onTimeout: function(timeoutData) { - if (timeoutData == null) { - return; + } + if (syncOptions.iframeEnabled && seed && clientId) { + for (let i = 0; i < CERBERUS.SYNC_COUNT; i++) { + syncs.push({ + type: 'iframe', + url: CERBERUS.SYNC_URL.replace('{UUID}', clientId) + .replace('{SEED}', seed) + .replace('{INDEX}', i) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') + }); } + } + return syncs; +} - timeoutData.forEach((bid) => { - this._sendTimeoutData(bid.auctionId, bid.timeout); - }); - }, - - _getCrbFromCookie() { - try { - const crb = JSON.parse(storage.getCookie('krg_crb')); - if (crb && crb.v) { - let vParsed = JSON.parse(atob(crb.v)); - if (vParsed) { - return vParsed; - } +function onTimeout(timeoutData) { + if (timeoutData == null) { + return; + } + + timeoutData.forEach((bid) => { + sendTimeoutData(bid.auctionId, bid.timeout); + }); +} + +function _generateRandomUUID() { + try { + // crypto.getRandomValues is supported everywhere but Opera Mini for years + var buffer = new Uint8Array(16); + crypto.getRandomValues(buffer); + buffer[6] = (buffer[6] & ~176) | 64; + buffer[8] = (buffer[8] & ~64) | 128; + var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { + return ('00' + x.toString(16)).slice(-2); + }).join(''); + return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20); + } catch (e) { + return ''; + } +} + +function _getCrb() { + let localStorageCrb = getCrbFromLocalStorage(); + if (Object.keys(localStorageCrb).length) { + return localStorageCrb; + } + return getCrbFromCookie(); +} + +function _getSessionId() { + if (!sessionId) { + sessionId = _generateRandomUUID(); + } + return sessionId; +} + +function getCrbFromCookie() { + try { + const crb = JSON.parse(STORAGE.getCookie(CERBERUS.KEY)); + if (crb && crb.v) { + let vParsed = JSON.parse(atob(crb.v)); + if (vParsed) { + return vParsed; } - return {}; - } catch (e) { - return {}; } - }, + return {}; + } catch (e) { + return {}; + } +} - _getCrbFromLocalStorage() { - try { - return JSON.parse(atob(spec._getLocalStorageSafely('krg_crb'))); - } catch (e) { - return {}; - } - }, +function getCrbFromLocalStorage() { + try { + return JSON.parse(atob(getLocalStorageSafely(CERBERUS.KEY))); + } catch (e) { + return {}; + } +} - _getCrb() { - let localStorageCrb = spec._getCrbFromLocalStorage(); - if (Object.keys(localStorageCrb).length) { - return localStorageCrb; - } - return spec._getCrbFromCookie(); - }, - - _getLocalStorageSafely(key) { - try { - return storage.getDataFromLocalStorage(key); - } catch (e) { - return null; - } - }, - - _getUserIds(tdid, usp, gdpr) { - const crb = spec._getCrb(); - const userIds = { - kargoID: crb.lexId, - clientID: crb.clientId, - crbIDs: crb.syncIds || {}, - optOut: crb.optOut, - usp: usp - }; +function getLocalStorageSafely(key) { + try { + return STORAGE.getDataFromLocalStorage(key); + } catch (e) { + return null; + } +} + +function getUserIds(tdidAdapter, usp, gdpr, eids) { + const crb = spec._getCrb(); + const userIds = { + crbIDs: crb.syncIds || {} + }; + + // Pull Trade Desk ID from adapter + if (tdidAdapter) { + userIds.tdID = tdidAdapter; + } + + // Pull Trade Desk ID from our storage + if (!tdidAdapter && crb.tdID) { + userIds.tdID = crb.tdID; + } + + if (usp) { + userIds.usp = usp; + } - try { - if (gdpr) { - userIds['gdpr'] = { - consent: gdpr.consentString || '', - applies: !!gdpr.gdprApplies, - } + try { + if (gdpr) { + userIds['gdpr'] = { + consent: gdpr.consentString || '', + applies: !!gdpr.gdprApplies, } - } catch (e) { } - if (tdid) { - userIds.tdID = tdid; + } catch (e) { + } + + if (crb.lexId != null) { + userIds.kargoID = crb.lexId; + } + + if (crb.clientId != null) { + userIds.clientID = crb.clientId; + } + + if (crb.optOut != null) { + userIds.optOut = crb.optOut; + } + + if (eids != null) { + userIds.sharedIDEids = eids; + } + + return userIds; +} + +function getClientId() { + const crb = spec._getCrb(); + return crb.clientId; +} + +function getAllMetadata(bidderRequest) { + return { + pageURL: bidderRequest?.refererInfo?.page, + rawCRB: STORAGE.getCookie(CERBERUS.KEY), + rawCRBLocalStorage: getLocalStorageSafely(CERBERUS.KEY) + }; +} + +function getRequestCount() { + if (lastPageUrl === window.location.pathname) { + return ++requestCounter; + } + lastPageUrl = window.location.pathname; + return requestCounter = 0; +} + +function sendTimeoutData(auctionId, auctionTimeout) { + let params = { + aid: auctionId, + ato: auctionTimeout + }; + + try { + let timeoutRequestUrl = buildUrl({ + protocol: 'https', + hostname: BIDDER.HOST, + pathname: BIDDER.TIMEOUT_ENDPOINT, + search: params + }); + + triggerPixel(timeoutRequestUrl); + } catch (e) {} +} + +function getImpression(bid) { + const imp = { + id: bid.bidId, + tid: bid.transactionId, + pid: bid.params.placementId, + code: bid.adUnitCode + }; + + if (bid.floorData != null && bid.floorData.floorMin > 0) { + imp.floor = bid.floorData.floorMin; + } + + if (bid.bidRequestsCount > 0) { + imp.bidRequestCount = bid.bidRequestsCount; + } + + if (bid.bidderRequestsCount > 0) { + imp.bidderRequestCount = bid.bidderRequestsCount; + } + + if (bid.bidderWinsCount > 0) { + imp.bidderWinCount = bid.bidderWinsCount; + } + + const gpid = getGPID(bid) + if (gpid != null && gpid != '') { + imp.fpd = { + gpid: gpid + } + } + + if (bid.mediaTypes != null) { + if (bid.mediaTypes.banner != null) { + imp.banner = bid.mediaTypes.banner; } - return userIds; - }, - - _getClientId() { - const crb = spec._getCrb(); - return crb.clientId; - }, - - _getAllMetadata(bidderRequest, tdid) { - return { - userIDs: spec._getUserIds(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent), - pageURL: bidderRequest?.refererInfo?.page, - rawCRB: storage.getCookie('krg_crb'), - rawCRBLocalStorage: spec._getLocalStorageSafely('krg_crb') - }; - }, - _getSessionId() { - if (!sessionId) { - sessionId = spec._generateRandomUuid(); + if (bid.mediaTypes.video != null) { + imp.video = bid.mediaTypes.video; } - return sessionId; - }, - _getRequestCount() { - if (lastPageUrl === window.location.pathname) { - return ++requestCounter; + if (bid.mediaTypes.native != null) { + imp.native = bid.mediaTypes.native; } - lastPageUrl = window.location.pathname; - return requestCounter = 0; - }, - - _generateRandomUuid() { - try { - // crypto.getRandomValues is supported everywhere but Opera Mini for years - var buffer = new Uint8Array(16); - crypto.getRandomValues(buffer); - buffer[6] = (buffer[6] & ~176) | 64; - buffer[8] = (buffer[8] & ~64) | 128; - var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { - return ('00' + x.toString(16)).slice(-2); - }).join(''); - return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20); - } catch (e) { - return ''; + } + + return imp +} + +function getGPID(bid) { + if (bid.ortb2Imp != null) { + if (bid.ortb2Imp.gpid != null && bid.ortb2Imp.gpid != '') { + return bid.ortb2Imp.gpid; } - }, - _sendTimeoutData(auctionId, auctionTimeout) { - let params = { - aid: auctionId, - ato: auctionTimeout, - }; + if (bid.ortb2Imp.ext != null && bid.ortb2Imp.ext.data != null) { + if (bid.ortb2Imp.ext.data.pbAdSlot != null && bid.ortb2Imp.ext.data.pbAdSlot != '') { + return bid.ortb2Imp.ext.data.pbAdSlot; + } - try { - let timeoutRequestUrl = buildUrl({ - protocol: 'https', - hostname: 'krk.kargo.com', - pathname: '/api/v1/event/timeout', - search: params - }); + if (bid.ortb2Imp.ext.data.adServer != null && bid.ortb2Imp.ext.data.adServer.adSlot != null && bid.ortb2Imp.ext.data.adServer.adSlot != '') { + return bid.ortb2Imp.ext.data.adServer.adSlot; + } + } + } - triggerPixel(timeoutRequestUrl); - } catch (e) {} + if (bid.adUnitCode != null && bid.adUnitCode != '') { + return bid.adUnitCode; } + return ''; +} + +export const spec = { + gvlid: BIDDER.GVLID, + code: BIDDER.CODE, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + supportedMediaTypes: BIDDER.SUPPORTED_MEDIA_TYPES, + onTimeout, + _getCrb, + _getSessionId }; + registerBidder(spec); diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 556e06268b1..260ac6c2132 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -35,7 +35,7 @@ describe('kargo adapter tests', function () { }); describe('build request', function() { - var bids, undefinedCurrency, noAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; + var bids, undefinedCurrency, noAdServerCurrency, nonUSDAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; beforeEach(function () { $$PREBID_GLOBAL$$.bidderSettings = { @@ -45,6 +45,7 @@ describe('kargo adapter tests', function () { }; undefinedCurrency = false; noAdServerCurrency = false; + nonUSDAdServerCurrency = false; sandbox.stub(config, 'getConfig').callsFake(function(key) { if (key === 'currency') { if (undefinedCurrency) { @@ -53,6 +54,9 @@ describe('kargo adapter tests', function () { if (noAdServerCurrency) { return {}; } + if (nonUSDAdServerCurrency) { + return {adServerCurrency: 'EUR'}; + } return {adServerCurrency: 'USD'}; } if (key === 'debug') return true; @@ -63,13 +67,45 @@ describe('kargo adapter tests', function () { bids = [ { params: { - placementId: 'foo' + placementId: 'foo', + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } }, - bidId: 1, + auctionId: '1234098', + bidId: '1', + adUnitCode: '101', + transactionId: '10101', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[320, 50], [300, 50]] + } + }, + bidRequestsCount: 1, + bidderRequestsCount: 2, + bidderWinsCount: 3, userId: { - tdid: 'fake-tdid' + tdid: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c' + }, + userIdAsEids: [ + { + 'source': 'adserver.org', + 'uids': [ + { + 'id': 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + } + ], + floorData: { + floorMin: 1 }, - sizes: [[320, 50], [300, 250], [300, 600]], ortb2: { device: { sua: { @@ -91,25 +127,76 @@ describe('kargo adapter tests', function () { version: [ '99', '0', '0', '0' ] } ], - mobile: 0, - model: '' + mobile: 1, + model: 'model', + source: 1, } } + }, + ortb2Imp: { + ext: { + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } } }, { params: { placementId: 'bar' }, - bidId: 2, - sizes: [[320, 50], [300, 250], [300, 600]] + bidId: '2', + adUnitCode: '202', + transactionId: '20202', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + video: { + sizes: [[320, 50], [300, 50]] + } + }, + bidRequestsCount: 0, + bidderRequestsCount: 0, + bidderWinsCount: 0, + ortb2Imp: { + ext: { + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } }, { params: { placementId: 'bar' }, - bidId: 3, - sizes: [[320, 50], [300, 250], [300, 600]] + bidId: '3', + adUnitCode: '303', + transactionId: '30303', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + native: { + sizes: [[320, 50], [300, 50]] + } + }, + ortb2Imp: { + ext: { + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } + } } ]; }); @@ -160,11 +247,19 @@ describe('kargo adapter tests', function () { function simulateNoCurrencyObject() { undefinedCurrency = true; noAdServerCurrency = false; + nonUSDAdServerCurrency = false; } function simulateNoAdServerCurrency() { undefinedCurrency = false; noAdServerCurrency = true; + nonUSDAdServerCurrency = false; + } + + function simulateNonUSDAdServerCurrency() { + undefinedCurrency = false; + noAdServerCurrency = false; + nonUSDAdServerCurrency = true; } function generateGDPR(applies, haveConsent) { @@ -182,6 +277,32 @@ describe('kargo adapter tests', function () { }; } + function generatePageView() { + return { + id: '112233', + timestamp: frozenNow.getTime(), + url: 'http://pageview.url' + } + } + + function generateRawCRB(rawCRB, rawCRBLocalStorage) { + if (rawCRB == null && rawCRBLocalStorage == null) { + return null + } + + let result = {} + + if (rawCRB != null) { + result.rawCRB = rawCRB + } + + if (rawCRBLocalStorage != null) { + result.rawCRBLocalStorage = rawCRBLocalStorage + } + + return result + } + function getKrgCrb() { return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0='; } @@ -253,6 +374,12 @@ describe('kargo adapter tests', function () { setLocalStorageItem('krg_crb', getEmptyKrgCrb()); } + function initializePageView() { + setLocalStorageItem('pageViewId', 112233); + setLocalStorageItem('pageViewTimestamp', frozenNow.getTime()); + setLocalStorageItem('pageViewUrl', 'http://pageview.url'); + } + function initializeEmptyKrgCrbCookie() { setCookie('krg_crb', getEmptyKrgCrbOldStyle()); } @@ -261,30 +388,20 @@ describe('kargo adapter tests', function () { return spec._getSessionId(); } - function getExpectedKrakenParams(excludeUserIds, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { + function getExpectedKrakenParams(expectedCRB, expectedPage, excludeUserIds, expectedGDPR, currency) { var base = { + pbv: '$prebid.version$', + aid: '1234098', + requestCount: 0, + sid: getSessionId(), + url: 'https://www.prebid.org', timeout: 200, - requestCount: requestCount++, - currency: 'USD', - cpmGranularity: 1, - timestamp: frozenNow.getTime(), - cpmRange: { - floor: 0, - ceil: 20 - }, - bidIDs: { - 1: 'foo', - 2: 'bar', - 3: 'bar' - }, - bidSizes: { - 1: [[320, 50], [300, 250], [300, 600]], - 2: [[320, 50], [300, 250], [300, 600]], - 3: [[320, 50], [300, 250], [300, 600]] - }, + ts: frozenNow.getTime(), device: { - width: screen.width, - height: screen.height, + size: [ + screen.width, + screen.height + ], sua: { platform: { brand: 'macOS', @@ -304,14 +421,61 @@ describe('kargo adapter tests', function () { version: [ '99', '0', '0', '0' ] } ], - mobile: 0, - model: '', + mobile: 1, + model: 'model', + source: 1 }, }, - userIDs: { + imp: [ + { + code: '101', + id: '1', + pid: 'foo', + tid: '10101', + banner: { + sizes: [[320, 50], [300, 50]] + }, + bidRequestCount: 1, + bidderRequestCount: 2, + bidderWinCount: 3, + floor: 1, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + }, + { + code: '202', + id: '2', + pid: 'bar', + tid: '20202', + video: { + sizes: [[320, 50], [300, 50]] + }, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + }, + { + code: '303', + id: '3', + pid: 'bar', + tid: '30303', + native: { + sizes: [[320, 50], [300, 50]] + }, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + ], + socan: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + }, + user: { kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f', - tdID: 'fake-tdid', + tdID: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', crbIDs: { 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', @@ -322,86 +486,63 @@ describe('kargo adapter tests', function () { '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' }, optOut: false, - usp: '1---' - }, - pageURL: 'https://www.prebid.org', - prebidVersion: '$prebid.version$', - prebidRawBidRequests: [ - { - bidId: 1, - ortb2: { - device: { - sua: { - platform: { - brand: 'macOS', - version: [ '12', '6', '0' ] - }, - browsers: [ - { - brand: 'Chromium', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Google Chrome', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Not;A=Brand', - version: [ '99', '0', '0', '0' ] - } - ], - mobile: 0, - model: '' + usp: '1---', + sharedIDEids: [ + { + source: 'adserver.org', + uids: [ + { + id: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', + atype: 1, + ext: { + rtiPartner: 'TDID' + } } - } - }, - params: { - placementId: 'foo' - }, - userId: { - tdid: 'fake-tdid' - }, - sizes: [[320, 50], [300, 250], [300, 600]], - }, - { - bidId: 2, - params: { - placementId: 'bar' - }, - sizes: [[320, 50], [300, 250], [300, 600]] - }, - { - bidId: 3, - params: { - placementId: 'bar' - }, - sizes: [[320, 50], [300, 250], [300, 600]] - } - ], - rawCRB: expectedRawCRBCookie, - rawCRBLocalStorage: expectedRawCRB + ] + } + ] + } }; + if (excludeUserIds) { + base.user.crbIDs = {}; + delete base.user.clientID; + delete base.user.kargoID; + delete base.user.optOut; + } + if (expectedGDPR) { - base.userIDs['gdpr'] = expectedGDPR; + base.user.gdpr = expectedGDPR; + } + + if (expectedPage) { + base.page = expectedPage; } - if (excludeUserIds === true) { - base.userIDs = { - crbIDs: {}, - usp: '1---' - }; - delete base.prebidRawBidRequests[0].userId.tdid; + if (currency) { + base.cur = currency; + } + + const reqCount = requestCount++; + if (reqCount > 0) { + base.requestCount = reqCount + } + + if (expectedCRB != null) { + if (expectedCRB.rawCRB != null) { + base.rawCRB = expectedCRB.rawCRB + } + if (expectedCRB.rawCRBLocalStorage != null) { + base.rawCRBLocalStorage = expectedCRB.rawCRBLocalStorage + } } return base; } - function testBuildRequests(excludeTdid, expected, gdpr) { + function testBuildRequests(expected, gdpr) { var clonedBids = JSON.parse(JSON.stringify(bids)); - if (excludeTdid) { - delete clonedBids[0].userId.tdid; - } + var payload = { timeout: 200, uspConsent: '1---', @@ -415,15 +556,13 @@ describe('kargo adapter tests', function () { } var request = spec.buildRequests(clonedBids, payload); - expected.sessionId = getSessionId(); - sessionIds.push(expected.sessionId); - var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); - expect(request.data.slice(0, 5)).to.equal('json='); - expect(request.url).to.equal('https://krk.kargo.com/api/v2/bid'); - expect(request.method).to.equal('GET'); - expect(request.currency).to.equal('USD'); + var krakenParams = request.data; + + expect(request.url).to.equal('https://krk2.kargo.com/api/v1/prebid'); + expect(request.method).to.equal('POST'); expect(request.timeout).to.equal(200); expect(krakenParams).to.deep.equal(expected); + // Make sure session ID stays the same across requests simulating multiple auctions on one page load for (let i in sessionIds) { if (i == 0) { @@ -436,76 +575,93 @@ describe('kargo adapter tests', function () { it('works when all params and localstorage and cookies are correctly set', function() { initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); }); it('works when all params and cookies are correctly set but no localstorage', function() { initializeKrgCrb(true); - testBuildRequests(false, getExpectedKrakenParams(undefined, null, getKrgCrbOldStyle())); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle()))); }); it('gracefully handles nothing being set', function() { - testBuildRequests(true, getExpectedKrakenParams(true, null, null)); + testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); }); it('gracefully handles browsers without localStorage', function() { simulateNoLocalStorage(); - testBuildRequests(true, getExpectedKrakenParams(true, null, null)); + testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); }); it('handles empty yet valid Kargo CRB', function() { initializeEmptyKrgCrb(); initializeEmptyKrgCrbCookie(); - testBuildRequests(true, getExpectedKrakenParams(true, getEmptyKrgCrb(), getEmptyKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getEmptyKrgCrbOldStyle(), getEmptyKrgCrb()), generatePageView(), true)); }); it('handles broken Kargo CRBs where base64 encoding is invalid', function() { initializeInvalidKrgCrbType1(); - testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType1(), null)); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType1()), generatePageView(), true)); }); it('handles broken Kargo CRBs where top level JSON is invalid on cookie', function() { initializeInvalidKrgCrbType1Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType1())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType1()), generatePageView(), true)); }); it('handles broken Kargo CRBs where decoded JSON is invalid', function() { initializeInvalidKrgCrbType2(); - testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType2(), null)); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType2()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner base 64 is invalid on cookie', function() { initializeInvalidKrgCrbType2Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType2OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType2OldStyle()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner JSON is invalid on cookie', function() { initializeInvalidKrgCrbType3Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType3OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType3OldStyle()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner JSON is falsey', function() { initializeInvalidKrgCrbType4Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType4OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType4OldStyle()), generatePageView(), true)); }); it('handles a non-existant currency object on the config', function() { simulateNoCurrencyObject(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); }); it('handles no ad server currency being set on the currency object in the config', function() { simulateNoAdServerCurrency(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); + }); + + it('handles non-USD ad server currency being set on the currency object in the config', function() { + simulateNonUSDAdServerCurrency(); + initializeKrgCrb(); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView(), undefined, undefined, 'EUR')); }); it('sends gdpr consent', function () { initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(true, true)), generateGDPR(true, true)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, true)), generateGDPR(false, true)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, false)), generateGDPR(false, false)); }); }); @@ -604,11 +760,11 @@ describe('kargo adapter tests', function () { }] }); var expectation = [{ + ad: '
', requestId: '1', cpm: 3, width: 320, height: 50, - ad: '
', ttl: 300, creativeId: 'foo', dealId: undefined, @@ -620,10 +776,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '2', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: 'dmpmptest1234', @@ -637,10 +793,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '3', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: undefined, @@ -652,10 +808,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '4', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: undefined, @@ -670,7 +826,6 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, - ad: '', vastXml: '', ttl: 300, creativeId: 'bar', @@ -686,7 +841,6 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, - ad: '', vastUrl: 'https://foobar.com/vast_adm', ttl: 300, creativeId: 'bar', @@ -831,7 +985,7 @@ describe('kargo adapter tests', function () { expect(triggerPixelStub.getCall(0)).to.be.null; spec.onTimeout([{ auctionId: '1234', timeout: 2000 }]); expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk.kargo.com/api/v1/event/timeout'); + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk2.kargo.com/api/v1/event/timeout'); expect(triggerPixelStub.getCall(0).args[0]).to.include('aid=1234'); expect(triggerPixelStub.getCall(0).args[0]).to.include('ato=2000'); }); From bab720d3be8c13cdf3d8ba8b75bfa13558edf474 Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 14 Apr 2023 21:42:50 +0200 Subject: [PATCH 317/375] Criteo Bid Adapter: Map device sua field to user ext sua (#9809) Co-authored-by: v.raybaud --- modules/criteoBidAdapter.js | 4 +++ test/spec/modules/criteoBidAdapter_spec.js | 32 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index cdb07252484..a27f77349bf 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -514,6 +514,10 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidderRequest && bidderRequest.uspConsent) { request.user.uspIab = bidderRequest.uspConsent; } + if (bidderRequest && bidderRequest.ortb2?.device?.sua) { + request.user.ext = request.user.ext || {}; + request.user.ext.sua = bidderRequest.ortb2?.device?.sua || {}; + } return request; } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index a05a25ff235..36d04d4b2e0 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -883,6 +883,38 @@ describe('The Criteo bidding adapter', function () { expect(request.data.user.uspIab).to.equal('1YNY'); }); + it('should properly build a request with device sua field', function () { + const sua = {} + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + timeout: 3000, + uspConsent: '1YNY', + ortb2: { + device: { + sua: sua + } + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.user.ext.sua).to.not.be.null; + expect(request.data.user.ext.sua).to.equal(sua); + }); + it('should properly build a request with gpp consent field', function () { const bidRequests = [ { From 2b355bdeb814cf81ab06e55e6b48850f5d91c821 Mon Sep 17 00:00:00 2001 From: Antti Ylitepsa <89379237+AYlitepsa@users.noreply.github.com> Date: Fri, 14 Apr 2023 22:44:10 +0300 Subject: [PATCH 318/375] Deleted the global.site.cattax field as it is not defined in openrtb 2.5 (#9810) --- modules/neuwoRtdProvider.js | 2 -- test/spec/modules/neuwoRtdProvider_spec.js | 1 - 2 files changed, 3 deletions(-) diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index 2ee4601e200..00a3c59b4a6 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -7,7 +7,6 @@ import CONSTANTS from '../src/constants.json'; export const DATA_PROVIDER = 'neuwo.ai'; const SEGTAX_IAB = 6 // IAB - Content Taxonomy version 2 -const CATTAX_IAB = 6 // IAB Tech Lab Content Taxonomy 2.2 const RESPONSE_IAB_TIER_1 = 'marketing_categories.iab_tier_1' const RESPONSE_IAB_TIER_2 = 'marketing_categories.iab_tier_2' @@ -106,7 +105,6 @@ export function injectTopics(topics, bidsConfig) { // upgrade category taxonomy to IAB 2.2, inject result to page categories if (segment.length > 0) { - addFragment(bidsConfig.ortb2Fragments.global, 'site.cattax', CATTAX_IAB) addFragment(bidsConfig.ortb2Fragments.global, 'site.pagecat', segment.map(s => s.id)) } diff --git a/test/spec/modules/neuwoRtdProvider_spec.js b/test/spec/modules/neuwoRtdProvider_spec.js index aed9634cd95..0ad3d7c1f74 100644 --- a/test/spec/modules/neuwoRtdProvider_spec.js +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -60,7 +60,6 @@ describe('neuwoRtdProvider', function () { neuwo.injectTopics(topics, bidsConfig, () => { }) expect(bidsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) expect(bidsConfig.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) - expect(bidsConfig.ortb2Fragments.global.site.cattax, 'category taxonomy code for pagecat').to.equal(6) // CATTAX_IAB expect(bidsConfig.ortb2Fragments.global.site.pagecat[0], 'category taxonomy code for pagecat').to.equal(TAX_ID) }) From 66a9476f87d41c14235689a3a56d6666e948ccc5 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Mon, 17 Apr 2023 16:25:37 +0300 Subject: [PATCH 319/375] kueezRtb Bid Adapter: Add support for passing metaData object from server response. (#9815) --- modules/kueezRtbBidAdapter.js | 17 +++++++++++++---- test/spec/modules/kueezRtbBidAdapter_spec.js | 13 +++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 3bd468a581f..8ed7e6ee408 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -204,7 +204,7 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; + const {creativeId, ad, price, exp, width, height, currency, metaData, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -218,11 +218,20 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - meta: { - advertiserDomains: advertiserDomains || [] - } }; + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + if (mediaType === BANNER) { Object.assign(response, { ad: ad, diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 2f48fd6df8c..92cad63bf27 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -448,6 +448,19 @@ describe('KueezRtbBidAdapter', function () { }); }); + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['kueezrtb.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['kueezrtb.com'], + agencyName: 'Agency Name' + }); + }); + it('should return an array of interpreted video responses', function () { const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); From 4534d4ba67427c54f6c284e84324ab07d9781d49 Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Mon, 17 Apr 2023 18:47:40 +0300 Subject: [PATCH 320/375] AdMatic Bid Adapter: badv added (#9820) * 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 * Update admaticBidAdapter.js * update --- modules/admaticBidAdapter.js | 9 +++++++-- test/spec/modules/admaticBidAdapter_spec.js | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 808c788fcb9..c216c66c78e 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,4 +1,4 @@ -import { getValue, logError, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; +import { getValue, logError, isEmpty, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -34,6 +34,7 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const bids = validBidRequests.map(buildRequestObject); + const blacklist = bidderRequest.ortb2 || {}; const networkId = getValue(validBidRequests[0].params, 'networkId'); const host = getValue(validBidRequests[0].params, 'host'); const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; @@ -59,6 +60,10 @@ export const spec = { } }; + if (!isEmpty(blacklist.badv)) { + payload.blacklist = blacklist.badv; + }; + if (payload) { switch (bidderName) { case 'pixad': @@ -69,7 +74,7 @@ export const spec = { break; } - return { method: 'POST', url: `https://${host}/pb?bidder=${bidderName}`, data: payload, options: { contentType: 'application/json' } }; + return { method: 'POST', url: `https://${host}/pb`, data: payload, options: { contentType: 'application/json' } }; } }, diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index 65a7b4111b7..ce4a4a2f0fb 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -3,7 +3,7 @@ import {spec, storage} from 'modules/admaticBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {getStorageManager} from 'src/storageManager'; -const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb?bidder=admatic'; +const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb'; describe('admaticBidAdapter', () => { const adapter = newBidder(spec); From ec76c8486e4de753d0ea2f186d78a0f4b341a8a4 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Mon, 17 Apr 2023 19:01:03 +0300 Subject: [PATCH 321/375] MinuteMediaPlus Bid Adapter: Add support for passing metaData object from server response. (#9816) --- modules/minutemediaplusBidAdapter.js | 17 +++++++++++++---- .../modules/minutemediaplusBidAdapter_spec.js | 13 +++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js index c07f227bcfa..9feb26181b0 100644 --- a/modules/minutemediaplusBidAdapter.js +++ b/modules/minutemediaplusBidAdapter.js @@ -204,7 +204,7 @@ function interpretResponse(serverResponse, request) { try { results.forEach(result => { - const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; + const {creativeId, ad, price, exp, width, height, currency, metaData, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -218,11 +218,20 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - meta: { - advertiserDomains: advertiserDomains || [] - } }; + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + if (mediaType === BANNER) { Object.assign(response, { ad: ad, diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js index 000ddb2778d..8741d8a4ccd 100644 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -448,6 +448,19 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); }); + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['minutemedia-prebid.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['minutemedia-prebid.com'], + agencyName: 'Agency Name' + }); + }); + it('should return an array of interpreted video responses', function () { const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); From 0d1af3b0267d18c0bf03a8e14bc3b9eca9af57ac Mon Sep 17 00:00:00 2001 From: jsfledd Date: Mon, 17 Apr 2023 09:25:43 -0700 Subject: [PATCH 322/375] Nativo Bid Adapter: Adding UserId support (#9767) * 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. * Removed spec test .only. Fixed unit tests that were breaking. * Added Prebid version to nativo exchange request * Removed unused bidder methods --- modules/nativoBidAdapter.js | 131 +++++++++++++++++---- test/spec/modules/nativoBidAdapter_spec.js | 101 ++++++++++++++++ 2 files changed, 211 insertions(+), 21 deletions(-) diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index a92168492d0..c62a74e6d6c 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -1,6 +1,7 @@ import { deepAccess, isEmpty } from '../src/utils.js' import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER } from '../src/mediaTypes.js' +import { getGlobal } from '../src/prebidGlobal.js' // import { config } from 'src/config' const BIDDER_CODE = 'nativo' @@ -14,6 +15,8 @@ const SUPPORTED_AD_TYPES = [BANNER] const FLOOR_PRICE_CURRENCY = 'USD' const PRICE_FLOOR_WILDCARD = '*' +const localPbjsRef = getGlobal() + /** * Keep track of bid data by keys * @returns {Object} - Map of bid data that can be referenced by multiple keys @@ -133,6 +136,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + const requestData = new RequestData() + requestData.addBidRequestDataSource(new UserEIDs()) + // Parse values from bid requests const placementIds = new Set() const bidDataMap = BidDataMap() @@ -166,6 +172,8 @@ export const spec = { if (bidRequestFloorPriceData) { floorPriceData[bidRequest.adUnitCode] = bidRequestFloorPriceData } + + requestData.processBidRequestData(bidRequest, bidderRequest) }) bidRequestMap[bidderRequest.bidderRequestId] = bidDataMap @@ -174,6 +182,10 @@ export const spec = { // Build basic required QS Params let params = [ + // Prebid version + { + key: 'ntv_pbv', value: localPbjsRef.version + }, // Prebid request id { key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId }, // Ad unit data @@ -255,9 +267,12 @@ export const spec = { params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) } + const qsParamStrings = [requestData.getRequestDataQueryString(), arrayToQS(params)] + const requestUrl = buildRequestUrl(BIDDER_ENDPOINT, qsParamStrings) + let serverRequest = { method: 'GET', - url: BIDDER_ENDPOINT + arrayToQS(params), + url: requestUrl } return serverRequest @@ -404,13 +419,6 @@ export const spec = { return syncs }, - /** - * Will be called when an adpater timed out for an auction. - * Adapter can fire a ajax or pixel call to register a timeout at thier end. - * @param {Object} timeoutData - Timeout specific data - */ - onTimeout: function (timeoutData) {}, - /** * Will be called when a bid from the adapter won the auction. * @param {Object} bid - The bid that won the auction @@ -425,12 +433,6 @@ export const spec = { appendFilterData(campaignsToFilter, ext.campaignsToFilter) }, - /** - * Will be called when the adserver targeting has been set for a bid from the adapter. - * @param {Object} bidder - The bid of which the targeting has been set - */ - onSetTargeting: function (bid) {}, - /** * Maps Prebid's bidId to Nativo's placementId values per unique bidderRequestId * @param {String} bidderRequestId - The unique ID value associated with the bidderRequest @@ -451,6 +453,78 @@ export const spec = { registerBidder(spec) // Utils +export class RequestData { + constructor() { + this.bidRequestDataSources = [] + } + + addBidRequestDataSource(bidRequestDataSource) { + if (!(bidRequestDataSource instanceof BidRequestDataSource)) return + + this.bidRequestDataSources.push(bidRequestDataSource) + } + + processBidRequestData(bidRequest, bidderRequest) { + for (let bidRequestDataSource of this.bidRequestDataSources) { + bidRequestDataSource.processBidRequestData(bidRequest, bidderRequest) + } + } + + getRequestDataQueryString() { + if (this.bidRequestDataSources.length == 0) return + + const queryParams = this.bidRequestDataSources.map(dataSource => dataSource.getRequestQueryString()).filter(queryString => queryString !== '') + return queryParams.join('&') + } +} + +export class BidRequestDataSource { + constructor() { + this.type = 'BidRequestDataSource' + } + processBidRequestData(bidRequest, bidderRequest) { } + getRequestQueryString() { return '' } +} + +export class UserEIDs extends BidRequestDataSource { + constructor() { + super() + this.type = 'UserEIDs' + this.qsParam = new QueryStringParam('ntv_pb_eid') + this.eids = [] + } + + processBidRequestData(bidRequest, bidderRequest) { + if (bidRequest.userIdAsEids === undefined || this.eids.length > 0) return + this.eids = bidRequest.userIdAsEids + } + + getRequestQueryString() { + if (this.eids.length === 0) return '' + + const encodedValueArray = encodeToBase64(this.eids) + this.qsParam.value = encodedValueArray + return this.qsParam.toString() + } +} + +export class QueryStringParam { + constructor(key, value) { + this.key = key + this.value = value + } +} + +QueryStringParam.prototype.toString = function () { + return `${this.key}=${this.value}` +} + +export function encodeToBase64(value) { + try { + return btoa(JSON.stringify(value)) + } catch (err) { } +} + export function parseFloorPriceData(bidRequest) { if (typeof bidRequest.getFloor !== 'function') return @@ -589,12 +663,9 @@ function appendQSParamString(str, key, value) { * @returns */ function arrayToQS(arr) { - return ( - '?' + - arr.reduce((value, obj) => { - return appendQSParamString(value, obj.key, obj.value) - }, '') - ) + return arr.reduce((value, obj) => { + return appendQSParamString(value, obj.key, obj.value) + }, '') } /** @@ -615,6 +686,24 @@ function getLargestSize(sizes, method = area) { }) } +/** + * Build the final request url + */ +export function buildRequestUrl(baseUrl, qsParamStringArray = []) { + if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) return baseUrl + + const nonEmptyQSParamStrings = qsParamStringArray.filter(qsParamString => qsParamString.trim() !== '') + + if (nonEmptyQSParamStrings.length === 0) return baseUrl + + let requestUrl = `${baseUrl}?${nonEmptyQSParamStrings[0]}` + for (let i = 1; i < nonEmptyQSParamStrings.length; i++) { + requestUrl += `&${nonEmptyQSParamStrings[i]}` + } + + return requestUrl +} + /** * Calculate the area * @param {Array} size - [width, height] @@ -645,7 +734,7 @@ export function getPageUrlFromBidRequest(bidRequest) { try { const url = new URL(paramPageUrl) return url.href - } catch (err) {} + } catch (err) { } } export function hasProtocol(url) { diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index 4d70e6f7071..51e78d1f6d6 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -8,6 +8,10 @@ import { getPageUrlFromBidRequest, hasProtocol, addProtocol, + BidRequestDataSource, + RequestData, + UserEIDs, + buildRequestUrl, } from '../../../modules/nativoBidAdapter' describe('bidDataMap', function () { @@ -120,6 +124,7 @@ describe('nativoBidAdapterTests', function () { expect(request.url).to.be.a('string') expect(request.url).to.include('?') + expect(request.url).to.include('ntv_pbv') expect(request.url).to.include('ntv_ptd') expect(request.url).to.include('ntv_pb_rid') expect(request.url).to.include('ntv_ppc') @@ -731,3 +736,99 @@ describe('getPageUrlFromBidRequest', () => { expect(url).not.to.be.undefined }) }) + +describe('RequestData', () => { + describe('addBidRequestDataSource', () => { + it('Adds a BidRequestDataSource', () => { + const requestData = new RequestData() + const testBidRequestDataSource = new BidRequestDataSource() + + requestData.addBidRequestDataSource(testBidRequestDataSource) + + expect(requestData.bidRequestDataSources.length == 1) + }) + + it("Doeasn't add a non BidRequestDataSource", () => { + const requestData = new RequestData() + + requestData.addBidRequestDataSource({}) + requestData.addBidRequestDataSource('test') + requestData.addBidRequestDataSource(1) + requestData.addBidRequestDataSource(true) + + expect(requestData.bidRequestDataSources.length == 0) + }) + }) + + describe('getRequestDataString', () => { + it("Doesn't append empty query strings", () => { + const requestData = new RequestData() + const testBidRequestDataSource = new BidRequestDataSource() + + requestData.addBidRequestDataSource(testBidRequestDataSource) + + let qs = requestData.getRequestDataQueryString() + expect(qs).to.be.empty + + testBidRequestDataSource.getRequestQueryString = () => { + return 'ntv_test=true' + } + qs = requestData.getRequestDataQueryString() + expect(qs).to.be.equal('ntv_test=true') + }) + }) +}) + +describe('UserEIDs', () => { + const userEids = new UserEIDs() + const eids = [{ 'testId': 1111 }] + + describe('processBidRequestData', () => { + it('Processes bid request without eids', () => { + userEids.processBidRequestData({}) + + expect(userEids.eids).to.be.empty + }) + + it('Processed bid request with eids', () => { + userEids.processBidRequestData({ userIdAsEids: eids }) + + expect(userEids.eids).to.not.be.empty + }) + }) + + describe('getRequestQueryString', () => { + it('Correctly prints out QS param string', () => { + const qs = userEids.getRequestQueryString() + const value = qs.slice(11) + + expect(qs).to.include('ntv_pb_eid=') + try { + expect(JSON.parse(value)).to.be.equal(eids) + } catch (err) { } + }) + }) +}) + +describe('buildRequestUrl', () => { + const baseUrl = 'https://www.testExchange.com' + it('Returns baseUrl if no QS strings passed', () => { + const url = buildRequestUrl(baseUrl) + expect(url).to.be.equal(baseUrl) + }) + + it('Returns baseUrl if empty QS strings passed', () => { + const url = buildRequestUrl(baseUrl, ['', '', '']) + expect(url).to.be.equal(baseUrl) + }) + + it('Returns baseUrl + QS params if QS strings passed', () => { + const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', 'ntv_foo=bar']) + expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) + }) + + it('Returns baseUrl + QS params if mixed QS strings passed', () => { + const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', '', '', 'ntv_foo=bar']) + expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) + }) +}) From b94ca2563f147b75a5e5ad7b451d5892f5cde44e Mon Sep 17 00:00:00 2001 From: Nisar Thadathil Date: Tue, 18 Apr 2023 19:53:03 +0530 Subject: [PATCH 323/375] vidoomy adapter: added userid module (#9795) --- modules/vidoomyBidAdapter.js | 7 +++++++ test/spec/modules/vidoomyBidAdapter_spec.js | 23 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index 29128998fd1..ded89861217 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -128,6 +128,12 @@ const buildRequests = (validBidRequests, bidderRequest) => { const bidfloor = deepAccess(bid, `params.bidfloor`, 0); const floor = getBidFloor(bid, adType, sizes, bidfloor); + let eids; + const userEids = deepAccess(bid, 'userIdAsEids'); + if (Array.isArray(userEids) && userEids.length > 0) { + eids = JSON.stringify(userEids) || ''; + } + const queryParams = { id: bid.params.id, adtype: adType, @@ -141,6 +147,7 @@ const buildRequests = (validBidRequests, bidderRequest) => { pid: bid.params.pid, requestId: bid.bidId, schain: serializeSupplyChainObj(bid.schain) || '', + eids: eids || '', bidfloor: floor, d: getDomainWithoutSubdomain(hostname), // 'vidoomy.com', // TODO: does the fallback make sense here? diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 607fdf4b5b8..2886800d001 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -162,6 +162,29 @@ describe('vidoomyBidAdapter', function() { expect(request[0].data.schain).to.eq(serializedForm); }); + it('should return standard json formated eids', function () { + const eids = [{ + source: 'pubcid.org', + uids: [ + { + id: 'some-random-id-value-1', + atype: 1 + } + ] + }, + { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value-2', + atype: 1 + }] + }] + bidRequests[0].userIdAsEids = eids + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + expect(bidRequest[0].data).to.include.any.keys('eids'); + expect(JSON.parse(bidRequest[0].data.eids)).to.eql(eids); + }); + it('should set the bidfloor if getFloor module is undefined but static bidfloor is present', function () { const request = { ...bidRequests[0], params: { bidfloor: 2.5 } } const req = spec.buildRequests([request], bidderRequest)[0]; From f53dfd75eee7489c35bd9aac260542ffe1634aa6 Mon Sep 17 00:00:00 2001 From: Jeremy Sadwith Date: Tue, 18 Apr 2023 10:57:48 -0400 Subject: [PATCH 324/375] KargoBidAdapter: GPP Support (#9812) --- modules/kargoBidAdapter.js | 26 +++++++++++++++++++---- test/spec/modules/kargoBidAdapter_spec.js | 21 ++++++++++-------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index ecc40b26aa2..b612c88bb12 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -47,7 +47,7 @@ const SUA_ATTRIBUTES = [ const CERBERUS = Object.freeze({ KEY: 'krg_crb', - SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}', + SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}&gpp={GPP_STRING}&gpp_sid={GPP_SID}', SYNC_COUNT: 5, PAGE_VIEW_ID: 'pageViewId', PAGE_VIEW_TIMESTAMP: 'pageViewTimestamp', @@ -94,7 +94,7 @@ function buildRequests(validBidRequests, bidderRequest) { ] }, imp: impressions, - user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids), + user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent), }); const reqCount = getRequestCount() @@ -229,7 +229,7 @@ function interpretResponse(response, bidRequest) { return bidResponses; } -function getUserSyncs(syncOptions, responses, gdprConsent, usPrivacy) { +function getUserSyncs(syncOptions, _, gdprConsent, usPrivacy, gppConsent) { const syncs = []; const seed = _generateRandomUUID(); const clientId = getClientId(); @@ -237,6 +237,9 @@ function getUserSyncs(syncOptions, responses, gdprConsent, usPrivacy) { var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; + var gppString = (gppConsent && gppConsent.consentString) ? gppConsent.consentString : ''; + var gppApplicableSections = (gppConsent && gppConsent.applicableSections && Array.isArray(gppConsent.applicableSections)) ? gppConsent.applicableSections.join(',') : ''; + // don't sync if opted out via usPrivacy if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { return syncs; @@ -251,6 +254,8 @@ function getUserSyncs(syncOptions, responses, gdprConsent, usPrivacy) { .replace('{GDPR}', gdpr) .replace('{GDPR_CONSENT}', gdprConsentString) .replace('{US_PRIVACY}', usPrivacy || '') + .replace('{GPP_STRING}', gppString) + .replace('{GPP_SID}', gppApplicableSections) }); } } @@ -329,7 +334,7 @@ function getLocalStorageSafely(key) { } } -function getUserIds(tdidAdapter, usp, gdpr, eids) { +function getUserIds(tdidAdapter, usp, gdpr, eids, gpp) { const crb = spec._getCrb(); const userIds = { crbIDs: crb.syncIds || {} @@ -375,6 +380,19 @@ function getUserIds(tdidAdapter, usp, gdpr, eids) { userIds.sharedIDEids = eids; } + if (gpp) { + const parsedGPP = {} + if (gpp && gpp.consentString) { + parsedGPP.gppString = gpp.consentString + } + if (gpp && gpp.applicableSections) { + parsedGPP.applicableSections = gpp.applicableSections + } + if (!isEmpty(parsedGPP)) { + userIds.gpp = parsedGPP + } + } + return userIds; } diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 260ac6c2132..d692cc67e26 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -524,9 +524,7 @@ describe('kargo adapter tests', function () { } const reqCount = requestCount++; - if (reqCount > 0) { - base.requestCount = reqCount - } + base.requestCount = reqCount if (expectedCRB != null) { if (expectedCRB.rawCRB != null) { @@ -894,8 +892,8 @@ describe('kargo adapter tests', function () { }); }); - function getUserSyncsWhenAllowed(gdprConsent, usPrivacy) { - return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy); + function getUserSyncsWhenAllowed(gdprConsent, usPrivacy, gppConsent) { + return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy, gppConsent); } function getUserSyncsWhenForbidden() { @@ -910,17 +908,17 @@ describe('kargo adapter tests', function () { shouldSimulateOutdatedBrowser = true; } - function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy) { + function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { return { type: 'iframe', - url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}` + url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}&gpp=${gpp}&gpp_sid=${gppSid}` }; } - function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy) { + function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { var syncs = []; for (var i = 0; i < 5; i++) { - syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || ''); + syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || '', gpp || '', gppSid || ''); } return syncs; } @@ -957,6 +955,11 @@ describe('kargo adapter tests', function () { safelyRun(() => expect(getUserSyncsWhenAllowed({ gdprApplies: true, consentString: 'consentstring' })).to.deep.equal(getSyncUrls(1, 'consentstring', ''))); }); + it('pass through gpp consent', function () { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, null, { consentString: 'gppString', applicableSections: [-1] })).to.deep.equal(getSyncUrls('', '', '', 'gppString', '-1'))); + }); + it('no user syncs when there is outdated browser', function() { turnOnClientId(); simulateOutdatedBrowser(); From 2324abd764a635b8a50744953923473e86e34a72 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Tue, 18 Apr 2023 15:58:27 -0400 Subject: [PATCH 325/375] support VIDEO feature flag in PubMatic bid adapter (#9744) --- modules/pubmaticBidAdapter.js | 21 +- test/spec/modules/pubmaticBidAdapter_spec.js | 1723 +++++++++--------- 2 files changed, 880 insertions(+), 864 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 43a688f20e9..edfee7a0b6d 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -534,7 +534,7 @@ function _createBannerRequest(bid) { export function checkVideoPlacement(videoData, adUnitCode) { // Check for video.placement property. If property is missing display log message. - if (!deepAccess(videoData, 'placement')) { + if (FEATURES.VIDEO && !deepAccess(videoData, 'placement')) { logWarn(MSG_VIDEO_PLACEMENT_MISSING + ' for ' + adUnitCode); }; } @@ -543,7 +543,7 @@ function _createVideoRequest(bid) { var videoData = mergeDeep(deepAccess(bid.mediaTypes, 'video'), bid.params.video); var videoObj; - if (videoData !== UNDEFINED) { + if (FEATURES.VIDEO && videoData !== UNDEFINED) { videoObj = {}; checkVideoPlacement(videoData, bid.adUnitCode); for (var key in VIDEO_CUSTOM_PARAMS) { @@ -673,7 +673,7 @@ function _createImpressionObject(bid) { isInvalidNativeRequest = false; } break; - case VIDEO: + case FEATURES.VIDEO && VIDEO: videoObj = _createVideoRequest(bid); if (videoObj !== UNDEFINED) { impObj.video = videoObj; @@ -709,7 +709,7 @@ function _createImpressionObject(bid) { return impObj.hasOwnProperty(BANNER) || impObj.hasOwnProperty(NATIVE) || - impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; + (FEATURES.VIDEO && impObj.hasOwnProperty(VIDEO)) ? impObj : UNDEFINED; } function _addImpressionFPD(imp, bid) { @@ -810,7 +810,7 @@ function _checkMediaType(bid, newBid) { var videoRegex = new RegExp(/VAST\s+version/); if (adm.indexOf('span class="PubAPIAd"') >= 0) { newBid.mediaType = BANNER; - } else if (videoRegex.test(adm)) { + } else if (FEATURES.VIDEO && videoRegex.test(adm)) { newBid.mediaType = VIDEO; } else { try { @@ -896,7 +896,10 @@ function _assignRenderer(newBid, request) { for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === newBid.requestId) { bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; - context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; + + if (FEATURES.VIDEO) { + context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; + } adUnitCode = request.bidderRequest.bids[bidderRequestBidsIndex].adUnitCode; } } @@ -916,7 +919,7 @@ function _assignRenderer(newBid, request) { * @returns */ export function assignDealTier(newBid, bid, request) { - if (!bid?.ext?.prebiddealpriority) return; + if (!bid?.ext?.prebiddealpriority || !FEATURES.VIDEO) return; const bidRequest = getBidRequest(newBid.requestId, [request.bidderRequest]); const videoObj = deepAccess(bidRequest, 'mediaTypes.video'); if (videoObj?.context != ADPOD) return; @@ -999,7 +1002,7 @@ export const spec = { return false; } // video ad validation - if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (FEATURES.VIDEO && 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'); @@ -1284,7 +1287,7 @@ export const spec = { switch (newBid.mediaType) { case BANNER: break; - case VIDEO: + case FEATURES.VIDEO && VIDEO: newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; newBid.vastXml = bid.adm; diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 66bcf42bb0f..df07e9ff66d 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -850,12 +850,86 @@ describe('PubMatic adapter', function () { expect(isValid).to.equal(true); }); - it('should check for context if video is present', function() { - let bid = { + if (FEATURES.VIDEO) { + it('should check for context if video is present', function() { + let bid = { + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890' + }, + '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 + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }) + + it('should return false if context is not present in video', function() { + let bid = { + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890' + }, + 'mediaTypes': { + 'video': { + 'w': 640, + 'h': 480, + 'protocols': [1, 2, 5], + '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 + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }) + + it('bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array', function() { + let bid = { 'bidder': 'pubmatic', 'params': { 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890' + 'publisherId': '5890', + 'video': {} }, 'mediaTypes': { 'video': { @@ -864,42 +938,6 @@ describe('PubMatic adapter', function () { ], '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 - }, - isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(true); - }) - - it('should return false if context is not present in video', function() { - let bid = { - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890' - }, - 'mediaTypes': { - 'video': { - 'w': 640, - 'h': 480, - 'protocols': [1, 2, 5], - 'mimes': ['video/flv'], 'skippable': false, 'skip': 1, 'linearity': 2 @@ -917,160 +955,124 @@ describe('PubMatic adapter', function () { 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0 - }, - isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(false); - }) - - it('bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array', function() { - let bid = { - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - 'video': {} - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - '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 - }; + }; - delete bid.params.video.mimes; // Undefined - bid.mediaTypes.video.mimes = 'string'; // NOT array - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.params.video.mimes; // Undefined + bid.mediaTypes.video.mimes = 'string'; // NOT array + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.params.video.mimes; // Undefined - delete bid.mediaTypes.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.params.video.mimes; // Undefined + delete bid.mediaTypes.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.params.video.mimes; // Undefined - bid.mediaTypes.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.video.mimes; // Undefined + bid.mediaTypes.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - bid.params.video = {mimes: 'string'}; // NOT array - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + bid.params.video = {mimes: 'string'}; // NOT array + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - delete bid.params.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + delete bid.params.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - bid.params.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + bid.params.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.mediaTypes.video.mimes; // Undefined - bid.params.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.mediaTypes.video.mimes; // Undefined + bid.params.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.mediaTypes.video.mimes; // Undefined - delete bid.params.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); + delete bid.mediaTypes.video.mimes; // Undefined + delete bid.params.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - it('checks on bid.params.outstreamAU & bid.renderer & bid.mediaTypes.video.renderer', function() { - const getThebid = function() { - let bid = utils.deepClone(validOutstreamBidRequest.bids[0]); - bid.params.outstreamAU = 'pubmatic-test'; - bid.renderer = ' '; // we are only checking if this key is set or not - bid.mediaTypes.video.renderer = ' '; // we are only checking if this key is set or not - return bid; - } + it('checks on bid.params.outstreamAU & bid.renderer & bid.mediaTypes.video.renderer', function() { + const getThebid = function() { + let bid = utils.deepClone(validOutstreamBidRequest.bids[0]); + bid.params.outstreamAU = 'pubmatic-test'; + bid.renderer = ' '; // we are only checking if this key is set or not + bid.mediaTypes.video.renderer = ' '; // we are only checking if this key is set or not + return bid; + } - // true: when all are present - // mdiatype: outstream - // bid.params.outstreamAU : Y - // bid.renderer : Y - // bid.mediaTypes.video.renderer : Y - let bid = getThebid(); - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : Y - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : Y - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : Y - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // false: none present; only outstream - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - // true: none present; outstream + Banner - // mdiatype: outstream, banner - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - bid.mediaTypes.banner = {sizes: [ [300, 250], [300, 600] ]}; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: none present; outstream + Native - // mdiatype: outstream, native - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - bid.mediaTypes.native = {} - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); + // true: when all are present + // mdiatype: outstream + // bid.params.outstreamAU : Y + // bid.renderer : Y + // bid.mediaTypes.video.renderer : Y + let bid = getThebid(); + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : Y + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : Y + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : Y + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // false: none present; only outstream + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + // true: none present; outstream + Banner + // mdiatype: outstream, banner + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + bid.mediaTypes.banner = {sizes: [ [300, 250], [300, 600] ]}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: none present; outstream + Native + // mdiatype: outstream, native + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + bid.mediaTypes.native = {} + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + } }); describe('Request formation', function () { @@ -2055,29 +2057,31 @@ describe('PubMatic adapter', function () { expect(data.bidfloor).to.equal(undefined); }); - it('ignore floormodule o/p if floor is not number', function() { - floorModuleTestData.banner['300x250'].floor = 'Not-a-Number'; - floorModuleTestData.banner['300x600'].floor = 'Not-a-Number'; - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' + if (FEATURES.VIDEO) { + it('ignore floormodule o/p if floor is not number', function() { + floorModuleTestData.banner['300x250'].floor = 'Not-a-Number'; + floorModuleTestData.banner['300x600'].floor = 'Not-a-Number'; + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(2.5); // video will be lowest now }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(2.5); // video will be lowest now - }); - it('ignore floormodule o/p if currency is not matched', function() { - floorModuleTestData.banner['300x250'].currency = 'INR'; - floorModuleTestData.banner['300x600'].currency = 'INR'; - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' + it('ignore floormodule o/p if currency is not matched', function() { + floorModuleTestData.banner['300x250'].currency = 'INR'; + floorModuleTestData.banner['300x600'].currency = 'INR'; + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(2.5); // video will be lowest now }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(2.5); // video will be lowest now - }); + } it('kadfloor is not passed, use minimum from floorModule', function() { newRequest[0].params.kadfloor = undefined; @@ -2556,114 +2560,6 @@ describe('PubMatic adapter', function () { }); }); - it('Request params check for video ad', function () { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(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('Request params check for 1 banner and 1 video ad', function () { - let request = spec.buildRequests(multipleMediaRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - - expect(data.imp).to.be.an('array') - expect(data.imp).with.length.above(1); - - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(multipleMediaRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(multipleMediaRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(multipleMediaRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(multipleMediaRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude - expect(data.device.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude - expect(data.user.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude - expect(data.user.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude - expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(multipleMediaRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(multipleMediaRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID - - // banner imp object check - expect(data.imp[0].id).to.equal(multipleMediaRequests[0].bidId); // Prebid bid id is passed as id - expect(data.imp[0].bidfloor).to.equal(parseFloat(multipleMediaRequests[0].params.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].ext.pmZoneId).to.equal(multipleMediaRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - - // video imp object check - expect(data.imp[1].video).to.exist; - expect(data.imp[1].tagid).to.equal('Div1'); - expect(data.imp[1]['video']['mimes']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['mimes'][0]).to.equal(multipleMediaRequests[1].params.video['mimes'][0]); - expect(data.imp[1]['video']['mimes'][1]).to.equal(multipleMediaRequests[1].params.video['mimes'][1]); - expect(data.imp[1]['video']['minduration']).to.equal(multipleMediaRequests[1].params.video['minduration']); - expect(data.imp[1]['video']['maxduration']).to.equal(multipleMediaRequests[1].params.video['maxduration']); - expect(data.imp[1]['video']['startdelay']).to.equal(multipleMediaRequests[1].params.video['startdelay']); - - expect(data.imp[1]['video']['playbackmethod']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['playbackmethod'][0]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][0]); - expect(data.imp[1]['video']['playbackmethod'][1]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][1]); - - expect(data.imp[1]['video']['api']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['api'][0]).to.equal(multipleMediaRequests[1].params.video['api'][0]); - expect(data.imp[1]['video']['api'][1]).to.equal(multipleMediaRequests[1].params.video['api'][1]); - - expect(data.imp[1]['video']['protocols']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['protocols'][0]).to.equal(multipleMediaRequests[1].params.video['protocols'][0]); - expect(data.imp[1]['video']['protocols'][1]).to.equal(multipleMediaRequests[1].params.video['protocols'][1]); - - expect(data.imp[1]['video']['battr']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['battr'][0]).to.equal(multipleMediaRequests[1].params.video['battr'][0]); - expect(data.imp[1]['video']['battr'][1]).to.equal(multipleMediaRequests[1].params.video['battr'][1]); - - expect(data.imp[1]['video']['linearity']).to.equal(multipleMediaRequests[1].params.video['linearity']); - expect(data.imp[1]['video']['placement']).to.equal(multipleMediaRequests[1].params.video['placement']); - expect(data.imp[1]['video']['minbitrate']).to.equal(multipleMediaRequests[1].params.video['minbitrate']); - expect(data.imp[1]['video']['maxbitrate']).to.equal(multipleMediaRequests[1].params.video['maxbitrate']); - - expect(data.imp[1]['video']['w']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[0]); - expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); - }); - it('should pass device.sua if present in bidderRequest fpd ortb2 object', function () { const suaObject = {'source': 2, 'platform': {'brand': 'macOS', 'version': ['12', '4', '0']}, 'browsers': [{'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']}], 'mobile': 0, 'model': '', 'bitness': '64', 'architecture': 'x86'}; let request = spec.buildRequests(multipleMediaRequests, { @@ -2723,127 +2619,6 @@ describe('PubMatic adapter', function () { expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpressionWithAllParams.native.request); }); - it('Request params - should handle banner and video format in single adunit', function() { - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length); - - // Case: when size is not present in adslo - bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][0]); - expect(data.banner.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][1]); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length - 1); - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - }); - - it('Request params - banner and video req in single adslot - should ignore banner imp if banner size is set to fluid and send video imp object', function () { - /* Adslot configured for banner and video. - banner size is set to [['fluid'], [300, 250]] - adslot specifies a size as 300x250 - => banner imp object should have primary w and h set to 300 and 250. fluid is ignored - */ - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; - - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format[0].w).to.equal(160); - expect(data.banner.format[0].h).to.equal(600); - - /* Adslot configured for banner and video. - banner size is set to [['fluid'], [300, 250]] - adslot does not specify any size - => banner imp object should have primary w and h set to 300 and 250. fluid is ignored - */ - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; - bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(160); - expect(data.banner.h).to.equal(600); - expect(data.banner.format).to.not.exist; - - /* Adslot configured for banner and video. - banner size is set to [[728 90], ['fluid'], [300, 250]] - adslot does not specify any size - => banner imp object should have primary w and h set to 728 and 90. - banner.format should have 300, 250 set in it - fluid is ignore - */ - - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [[728, 90], ['fluid'], [300, 250]]; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(728); - expect(data.banner.h).to.equal(90); - expect(data.banner.format).to.exist; - expect(data.banner.format[0].w).to.equal(300); - expect(data.banner.format[0].h).to.equal(250); - - /* Adslot configured for banner and video. - banner size is set to [['fluid']] - adslot does not specify any size - => banner object should not be sent in the request. only video should be sent. - */ - - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid']]; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.not.exist; - expect(data.video).to.exist; - }); - - it('Request params - should not contain banner imp if mediaTypes.banner is not present and sizes is specified in bid.sizes', function() { - delete bannerAndVideoBidRequests[0].mediaTypes.banner; - bannerAndVideoBidRequests[0].params.sizes = [300, 250]; - - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.not.exist; - }); - it('Request params - should handle banner and native format in single adunit', function() { let request = spec.buildRequests(bannerAndNativeBidRequests, { auctionId: 'new-auction-id' @@ -2861,58 +2636,6 @@ describe('PubMatic adapter', function () { expect(data.native.request).to.exist; }); - it('Request params - should handle video and native format in single adunit', function() { - let request = spec.buildRequests(videoAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - - it('Request params - should handle banner, video and native format in single adunit', function() { - let request = spec.buildRequests(bannerVideoAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - - it('Request params - should not add banner object if mediaTypes.banner is missing, but adunits.sizes is present', function() { - delete bannerAndNativeBidRequests[0].mediaTypes.banner; - bannerAndNativeBidRequests[0].sizes = [729, 90]; - - let request = spec.buildRequests(bannerAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.not.exist; - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - 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 }, @@ -2936,115 +2659,399 @@ describe('PubMatic adapter', function () { expect(data.native).to.exist; }); - 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 }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - }; - videoAndNativeBidRequests[0].nativeParams = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - } - let request = spec.buildRequests(videoAndNativeBidRequests, { + it('Request params - should not add banner object if mediaTypes.banner is missing, but adunits.sizes is present', function() { + delete bannerAndNativeBidRequests[0].mediaTypes.banner; + bannerAndNativeBidRequests[0].sizes = [729, 90]; + + let request = spec.buildRequests(bannerAndNativeBidRequests, { auctionId: 'new-auction-id' }); let data = JSON.parse(request.data); data = data.imp[0]; - expect(data.video).to.exist; + expect(data.banner).to.not.exist; + expect(data.native).to.exist; + expect(data.native.request).to.exist; }); - it('should build video impression if video params are present in adunit.mediaTypes instead of bid.params', function() { - let videoReq = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', - 'sizes': [ - [640, 480] - ], - 'bidId': '21b59b1353ba82', - 'bidderRequestId': '1a08245305e6dd', - 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }] - let request = spec.buildRequests(videoReq, { - auctionId: 'new-auction-id' + if (FEATURES.VIDEO) { + it('Request params - should not contain banner imp if mediaTypes.banner is not present and sizes is specified in bid.sizes', function() { + delete bannerAndVideoBidRequests[0].mediaTypes.banner; + bannerAndVideoBidRequests[0].params.sizes = [300, 250]; + + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.not.exist; }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.video).to.exist; - }); - it('should build video impression with overwriting video params present in adunit.mediaTypes with bid.params', function() { - let videoReq = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', - 'sizes': [ - [640, 480] - ], - 'bidId': '21b59b1353ba82', - 'bidderRequestId': '1a08245305e6dd', - 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }] - let request = spec.buildRequests(videoReq, { - auctionId: 'new-auction-id' + it('Request params check for 1 banner and 1 video ad', function () { + let request = spec.buildRequests(multipleMediaRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + + expect(data.imp).to.be.an('array') + expect(data.imp).with.length.above(1); + + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.page).to.equal(multipleMediaRequests[0].params.kadpageurl); // forced pageURL + expect(data.site.publisher.id).to.equal(multipleMediaRequests[0].params.publisherId); // publisher Id + expect(data.user.yob).to.equal(parseInt(multipleMediaRequests[0].params.yob)); // YOB + expect(data.user.gender).to.equal(multipleMediaRequests[0].params.gender); // Gender + expect(data.device.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version + expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID + expect(data.ext.wrapper.profile).to.equal(parseInt(multipleMediaRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID + expect(data.ext.wrapper.version).to.equal(parseInt(multipleMediaRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + + // banner imp object check + expect(data.imp[0].id).to.equal(multipleMediaRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(multipleMediaRequests[0].params.kadfloor)); // kadfloor + expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.pmZoneId).to.equal(multipleMediaRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid + + // video imp object check + expect(data.imp[1].video).to.exist; + expect(data.imp[1].tagid).to.equal('Div1'); + expect(data.imp[1]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['mimes'][0]).to.equal(multipleMediaRequests[1].params.video['mimes'][0]); + expect(data.imp[1]['video']['mimes'][1]).to.equal(multipleMediaRequests[1].params.video['mimes'][1]); + expect(data.imp[1]['video']['minduration']).to.equal(multipleMediaRequests[1].params.video['minduration']); + expect(data.imp[1]['video']['maxduration']).to.equal(multipleMediaRequests[1].params.video['maxduration']); + expect(data.imp[1]['video']['startdelay']).to.equal(multipleMediaRequests[1].params.video['startdelay']); + + expect(data.imp[1]['video']['playbackmethod']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['playbackmethod'][0]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][0]); + expect(data.imp[1]['video']['playbackmethod'][1]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][1]); + + expect(data.imp[1]['video']['api']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['api'][0]).to.equal(multipleMediaRequests[1].params.video['api'][0]); + expect(data.imp[1]['video']['api'][1]).to.equal(multipleMediaRequests[1].params.video['api'][1]); + + expect(data.imp[1]['video']['protocols']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['protocols'][0]).to.equal(multipleMediaRequests[1].params.video['protocols'][0]); + expect(data.imp[1]['video']['protocols'][1]).to.equal(multipleMediaRequests[1].params.video['protocols'][1]); + + expect(data.imp[1]['video']['battr']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['battr'][0]).to.equal(multipleMediaRequests[1].params.video['battr'][0]); + expect(data.imp[1]['video']['battr'][1]).to.equal(multipleMediaRequests[1].params.video['battr'][1]); + + expect(data.imp[1]['video']['linearity']).to.equal(multipleMediaRequests[1].params.video['linearity']); + expect(data.imp[1]['video']['placement']).to.equal(multipleMediaRequests[1].params.video['placement']); + expect(data.imp[1]['video']['minbitrate']).to.equal(multipleMediaRequests[1].params.video['minbitrate']); + expect(data.imp[1]['video']['maxbitrate']).to.equal(multipleMediaRequests[1].params.video['maxbitrate']); + + expect(data.imp[1]['video']['w']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[0]); + expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.video).to.exist; - expect(data.video.linearity).to.equal(1); - }); + // ================================================ + it('Request params - should handle banner and video format in single adunit', function() { + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length); + + // Case: when size is not present in adslo + bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][0]); + expect(data.banner.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][1]); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length - 1); + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + + it('Request params - should handle banner, video and native format in single adunit', function() { + let request = spec.buildRequests(bannerVideoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - should handle video and native format in single adunit', function() { + let request = spec.buildRequests(videoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - banner and video req in single adslot - should ignore banner imp if banner size is set to fluid and send video imp object', function () { + /* Adslot configured for banner and video. + banner size is set to [['fluid'], [300, 250]] + adslot specifies a size as 300x250 + => banner imp object should have primary w and h set to 300 and 250. fluid is ignored + */ + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; + + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format[0].w).to.equal(160); + expect(data.banner.format[0].h).to.equal(600); + + /* Adslot configured for banner and video. + banner size is set to [['fluid'], [300, 250]] + adslot does not specify any size + => banner imp object should have primary w and h set to 300 and 250. fluid is ignored + */ + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; + bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; + + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(160); + expect(data.banner.h).to.equal(600); + expect(data.banner.format).to.not.exist; + + /* Adslot configured for banner and video. + banner size is set to [[728 90], ['fluid'], [300, 250]] + adslot does not specify any size + => banner imp object should have primary w and h set to 728 and 90. + banner.format should have 300, 250 set in it + fluid is ignore + */ + + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [[728, 90], ['fluid'], [300, 250]]; + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(728); + expect(data.banner.h).to.equal(90); + expect(data.banner.format).to.exist; + expect(data.banner.format[0].w).to.equal(300); + expect(data.banner.format[0].h).to.equal(250); + + /* Adslot configured for banner and video. + banner size is set to [['fluid']] + adslot does not specify any size + => banner object should not be sent in the request. only video should be sent. + */ + + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid']]; + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.not.exist; + expect(data.video).to.exist; + }); + + 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 }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + }; + videoAndNativeBidRequests[0].nativeParams = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + } + let request = spec.buildRequests(videoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.native).to.exist; + }); + + it('should build video impression if video params are present in adunit.mediaTypes instead of bid.params', function() { + let videoReq = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890', + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', + 'sizes': [ + [640, 480] + ], + 'bidId': '21b59b1353ba82', + 'bidderRequestId': '1a08245305e6dd', + 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + let request = spec.buildRequests(videoReq, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.video).to.exist; + }); + + it('should build video impression with overwriting video params present in adunit.mediaTypes with bid.params', function() { + let videoReq = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890', + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', + 'sizes': [ + [640, 480] + ], + 'bidId': '21b59b1353ba82', + 'bidderRequestId': '1a08245305e6dd', + 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + let request = spec.buildRequests(videoReq, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.video.linearity).to.equal(1); + }); + + it('Request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(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('Request params dctr check', function () { @@ -3573,14 +3580,6 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('banner'); }); - it('should check for valid video mediaType in case of multiformat request', function() { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].mediaType).to.equal('video'); - }); - it('should check for valid native mediaType in case of multiformat request', function() { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' @@ -3590,28 +3589,6 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('native'); }); - it('should assign renderer if bid is video and request is for outstream', function() { - let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); - let response = spec.interpretResponse(outstreamVideoBidResponse, request); - expect(response[0].renderer).to.exist; - }); - - it('should not assign renderer if bidderRequest is not present', function() { - let request = spec.buildRequests(outstreamBidRequest, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(outstreamVideoBidResponse, request); - expect(response[0].renderer).to.not.exist; - }); - - 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 not assign renderer if bid is native', function() { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' @@ -3628,154 +3605,186 @@ describe('PubMatic adapter', function () { expect(response[0].renderer).to.not.exist; }); - it('should assign mediaType by reading bid.ext.mediaType', function() { - let newvideoRequests = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5670', - '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' + if (FEATURES.VIDEO) { + it('should check for valid video mediaType in case of multiformat request', function() { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].mediaType).to.equal('video'); + }); + + it('should assign renderer if bid is video and request is for outstream', function() { + let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.exist; + }); + + it('should not assign renderer if bidderRequest is not present', function() { + let request = spec.buildRequests(outstreamBidRequest, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + 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 assign mediaType by reading bid.ext.mediaType', function() { + let newvideoRequests = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5670', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } }, - '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, - 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', - 'ext': { - 'bidtype': 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' - } - }] - }, - 'headers': {} - } - let newrequest = spec.buildRequests(newvideoRequests, { - auctionId: 'new-auction-id' - }); - let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); - expect(newresponse[0].mediaType).to.equal('video') - }) + }, + '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, + 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'ext': { + 'bidtype': 1 + } + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + } + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video') + }) - it('should assign mediaType even if bid.ext.mediaType does not exists', function() { - let newvideoRequests = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5670', - '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' + it('should assign mediaType even if bid.ext.mediaType does not exists', function() { + let newvideoRequests = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5670', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } }, - '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, - 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420' - }], + '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' - } - }] - }, - 'headers': {} - } - let newrequest = spec.buildRequests(newvideoRequests, { - auctionId: 'new-auction-id' + }, + '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, + 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420' + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + } + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video') }); - let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); - expect(newresponse[0].mediaType).to.equal('video') - }); + } }); describe('Preapare metadata', function () { @@ -3955,26 +3964,55 @@ describe('PubMatic adapter', function () { }); }); - describe('Checking for Video.Placement property', function() { - let sandbox, utilsMock; - const adUnit = 'Div1'; - const msg_placement_missing = 'Video.Placement param missing for Div1'; - 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'], - } + if (FEATURES.VIDEO) { + describe('Checking for Video.Placement property', function() { + let sandbox, utilsMock; + const adUnit = 'Div1'; + const msg_placement_missing = 'Video.Placement param missing for Div1'; + 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); + 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); + }) + }); + } + }); + + if (FEATURES.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(() => { @@ -3982,112 +4020,87 @@ describe('PubMatic adapter', function () { sandbox.restore(); }) - it('should log Video.Placement param missing', function() { - checkVideoPlacement(videoData, adUnit); - 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); - }) - }); - }); + it('Should log warning if video params from mediaTypes and params obj of bid are not present', function () { + delete newVideoRequest[0].mediaTypes.video; + delete newVideoRequest[0].params.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('Should log warning if 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, { - auctionId: 'new-auction-id' - }); - - sinon.assert.calledOnce(utils.logWarn); - 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, { - auctionId: 'new-auction-id' - }); - 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); - }); + let request = spec.buildRequests(newVideoRequest, { + auctionId: 'new-auction-id' + }); - describe('Assign Deal Tier (i.e. prebidDealPriority)', function () { - let videoSeatBid, request, newBid; - // let data = JSON.parse(request.data); - beforeEach(function () { - videoSeatBid = videoBidResponse.body.seatbid[0].bid[0]; - // const adpodValidOutstreamBidRequest = validOutstreamBidRequest.bids[0].mediaTypes.video.context = 'adpod'; - request = spec.buildRequests(bidRequests, validOutstreamBidRequest); - newBid = { - requestId: '47acc48ad47af5' - }; - videoSeatBid.ext = videoSeatBid.ext || {}; - videoSeatBid.ext.video = videoSeatBid.ext.video || {}; - // videoBidRequests[0].mediaTypes.video = videoBidRequests[0].mediaTypes.video || {}; + sinon.assert.calledOnce(utils.logWarn); + expect(request).to.equal(undefined); }); - it('should not assign video object if deal priority is missing', function () { - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video).to.equal(undefined); - expect(newBid.video).to.not.exist; - }); + it('Should consider video params from mediaType object of bid', function () { + delete newVideoRequest[0].params.video; - it('should not assign video object if context is not a adpod', function () { - videoSeatBid.ext.prebiddealpriority = 5; - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video).to.equal(undefined); - expect(newBid.video).to.not.exist; + let request = spec.buildRequests(newVideoRequest, { + auctionId: 'new-auction-id' + }); + 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('when video deal tier object is present', function () { + describe('Assign Deal Tier (i.e. prebidDealPriority)', function () { + let videoSeatBid, request, newBid; + // let data = JSON.parse(request.data); beforeEach(function () { - videoSeatBid.ext.prebiddealpriority = 5; - request.bidderRequest.bids[0].mediaTypes.video = { - ...request.bidderRequest.bids[0].mediaTypes.video, - context: 'adpod', - maxduration: 50 + videoSeatBid = videoBidResponse.body.seatbid[0].bid[0]; + // const adpodValidOutstreamBidRequest = validOutstreamBidRequest.bids[0].mediaTypes.video.context = 'adpod'; + request = spec.buildRequests(bidRequests, validOutstreamBidRequest); + newBid = { + requestId: '47acc48ad47af5' }; + videoSeatBid.ext = videoSeatBid.ext || {}; + videoSeatBid.ext.video = videoSeatBid.ext.video || {}; + // videoBidRequests[0].mediaTypes.video = videoBidRequests[0].mediaTypes.video || {}; }); - it('should set video deal tier object, when maxduration is present in ext', function () { + it('should not assign video object if deal priority is missing', function () { assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video.durationSeconds).to.equal(50); - expect(newBid.video.context).to.equal('adpod'); - expect(newBid.video.dealTier).to.equal(5); + expect(newBid.video).to.equal(undefined); + expect(newBid.video).to.not.exist; }); - it('should set video deal tier object, when duration is present in ext', function () { - videoSeatBid.ext.video.duration = 20; + it('should not assign video object if context is not a adpod', function () { + videoSeatBid.ext.prebiddealpriority = 5; assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video.durationSeconds).to.equal(20); - expect(newBid.video.context).to.equal('adpod'); - expect(newBid.video.dealTier).to.equal(5); + expect(newBid.video).to.equal(undefined); + expect(newBid.video).to.not.exist; + }); + + describe('when video deal tier object is present', function () { + beforeEach(function () { + videoSeatBid.ext.prebiddealpriority = 5; + request.bidderRequest.bids[0].mediaTypes.video = { + ...request.bidderRequest.bids[0].mediaTypes.video, + context: 'adpod', + maxduration: 50 + }; + }); + + it('should set video deal tier object, when maxduration is present in ext', function () { + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video.durationSeconds).to.equal(50); + expect(newBid.video.context).to.equal('adpod'); + expect(newBid.video.dealTier).to.equal(5); + }); + + it('should set video deal tier object, when duration is present in ext', function () { + videoSeatBid.ext.video.duration = 20; + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video.durationSeconds).to.equal(20); + expect(newBid.video.context).to.equal('adpod'); + expect(newBid.video.dealTier).to.equal(5); + }); }); }); }); - }); + } describe('Marketplace params', function () { let sandbox, utilsMock, newBidRequests, newBidResponses; From 454caa34db9eb9f1e834a7d7d4a8089d86751812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Robakowski?= Date: Wed, 19 Apr 2023 08:06:35 +0200 Subject: [PATCH 326/375] Eskimi Bid Adapter: initial adapter release (#9768) * rewrite the adapter to use ortbConverter * Eskimi: fix `isBidRequestValid` * Eskimi: fix request validation tests --------- Co-authored-by: Sekandar --- modules/eskimiBidAdapter.js | 72 ++++++++++ modules/eskimiBidAdapter.md | 34 +++++ test/spec/modules/eskimiBidAdapter_spec.js | 155 +++++++++++++++++++++ 3 files changed, 261 insertions(+) create mode 100644 modules/eskimiBidAdapter.js create mode 100644 modules/eskimiBidAdapter.md create mode 100644 test/spec/modules/eskimiBidAdapter_spec.js diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js new file mode 100644 index 00000000000..4a00b97614b --- /dev/null +++ b/modules/eskimiBidAdapter.js @@ -0,0 +1,72 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' + +const BIDDER_CODE = 'eskimi'; +// const ENDPOINT = 'https://hb.eskimi.com/bids' +const ENDPOINT = 'https://sspback.eskimi.com/bid-request' + +const DEFAULT_BID_TTL = 30; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const GVLID = 814; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!bid.params.placementId; + }, + + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}) + + let bid = bidRequests.find((b) => b.params.placementId) + if (!data.site) data.site = {} + data.site.ext = {placementId: bid.params.placementId} + + if (bidderRequest.gdprConsent) { + if (!data.user) data.user = {}; + if (!data.user.ext) data.user.ext = {}; + if (!data.regs) data.regs = {}; + if (!data.regs.ext) data.regs.ext = {}; + data.user.ext.consent = bidderRequest.gdprConsent.consentString; + data.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + + return [{ + method: 'POST', + url: ENDPOINT, + data, + options: {contentType: 'application/json;charset=UTF-8', withCredentials: false} + }] + }, + + interpretResponse(response, request) { + return converter.fromORTB({response: response.body, request: request.data}).bids; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} bid The bid that won the auction + */ + onBidWon: function (bid) { + if (bid.burl) { + utils.triggerPixel(bid.burl); + } + } +} + +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, + currency: DEFAULT_CURRENCY, + mediaType: BANNER // TODO: support more types, we should set mtype on the winning bid + } +}); + +registerBidder(spec); diff --git a/modules/eskimiBidAdapter.md b/modules/eskimiBidAdapter.md new file mode 100644 index 00000000000..83ae87fd01b --- /dev/null +++ b/modules/eskimiBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +Module Name: ESKIMI Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech@eskimi.com + +# Description + +An adapter to get a bid from Eskimi DSP. + +# Test Parameters +```javascript + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + + bids: [{ + bidder: 'eskimi', + params: { + placementId: 612 + } + }] + + }]; +``` + +Where: + +* placementId - Placement ID of the ad unit (required) + diff --git a/test/spec/modules/eskimiBidAdapter_spec.js b/test/spec/modules/eskimiBidAdapter_spec.js new file mode 100644 index 00000000000..4622b374de5 --- /dev/null +++ b/test/spec/modules/eskimiBidAdapter_spec.js @@ -0,0 +1,155 @@ +import {expect} from 'chai'; +import {spec} from 'modules/eskimiBidAdapter.js'; + +const REQUEST = { + 'bidderCode': 'eskimi', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', + 'bidderRequestId': 'requestId', + 'bidRequest': [{ + 'bidder': 'eskimi', + 'params': { + 'placementId': 1003000, + 'accId': 123 + }, + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId1', + 'adUnitCode': 'adUnitCode1', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }, + { + 'bidder': 'eskimi', + 'params': { + 'placementId': 1003001, + 'accId': 123 + }, + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId2', + 'adUnitCode': 'adUnitCode2', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'id': 'requestId', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'bidId1', + 'impid': 'bidId1', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'eskimi': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'seat' + } + ] + } +}; + +describe('Eskimi bid adapter', function () { + describe('isBidRequestValid', function () { + it('should accept request if placementId is passed', function () { + let bid = { + bidder: 'eskimi', + params: { + placementId: 123 + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('reject requests without params', function () { + let bid = { + bidder: 'eskimi', + params: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('creates request data', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + expect(request).to.exist.and.to.be.a('object'); + const payload = request.data; + expect(payload.imp[0]).to.have.property('id', REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(REQUEST.bidRequest, req)[0]; + + const payload = request.data; + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + }); + + describe('interpretResponse', function () { + it('has bids', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + let bids = spec.interpretResponse(RESPONSE, request); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].id); + 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]).to.have.property('ttl', 30); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, request); + expect(bids).to.be.empty; + }); + }); +}); From 5fd37b260afe13aa8e380df126125cb4f2cabc1b Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Wed, 19 Apr 2023 07:25:59 -0700 Subject: [PATCH 327/375] Update the Conversant adapter to have source.tid to have auctionId (#9822) Co-authored-by: johwier --- modules/conversantBidAdapter.js | 3 +++ test/spec/modules/conversantBidAdapter_spec.js | 1 + 2 files changed, 4 insertions(+) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index c2ffba8bb48..e17a9fe4021 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -124,6 +124,9 @@ export const spec = { const payload = { id: requestId, imp: conversantImps, + source: { + tid: requestId + }, site: { id: siteId, mobile: document.querySelector('meta[name="viewport"][content*="width=device-width"]') !== null ? 1 : 0, diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 261414f336d..bda5d8daca7 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -263,6 +263,7 @@ describe('Conversant adapter tests', function() { const payload = request.data; expect(payload).to.have.property('id', 'req000'); + expect(payload.source).to.have.property('tid', 'req000'); expect(payload).to.have.property('at', 1); expect(payload).to.have.property('imp'); expect(payload.imp).to.be.an('array').with.lengthOf(8); From c878dc2fb9dc68088e2ba2b7f8650b5058e9e186 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 19 Apr 2023 17:50:13 +0300 Subject: [PATCH 328/375] MinuteMediaPlus Bid Adapter: Pass gpid to server. (#9830) --- modules/minutemediaplusBidAdapter.js | 3 +++ test/spec/modules/minutemediaplusBidAdapter_spec.js | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js index 9feb26181b0..bd7874c405b 100644 --- a/modules/minutemediaplusBidAdapter.js +++ b/modules/minutemediaplusBidAdapter.js @@ -77,6 +77,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { const pId = extractPID(params); const subDomain = extractSubDomain(params); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ currency: 'USD', @@ -105,6 +107,7 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { res: `${screen.width}x${screen.height}`, schain: schain, mediaTypes: mediaTypes, + gpid: gpid, auctionId: auctionId, transactionId: transactionId, bidderRequestId: bidderRequestId, diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js index 8741d8a4ccd..e19323c794f 100644 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -43,7 +43,12 @@ const BID = { 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER] + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '1234567890' + } + } }; const VIDEO_BID = { @@ -315,7 +320,8 @@ describe('MinuteMediaPlus Bid Adapter', function () { protocols: [2, 3, 5, 6], startdelay: 0 } - } + }, + gpid: '' } }); }); @@ -373,6 +379,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], + gpid: '1234567890', uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', From 452f0264760639b975efabf5169e145ea170a7ac Mon Sep 17 00:00:00 2001 From: shahinrahbariasl <56240400+shahinrahbariasl@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:52:42 -0400 Subject: [PATCH 329/375] IX Bid Adapter: Support for Ad unit specific First Party Data (#9821) * feat: add adunit specific fpd support [PB-1387] * feat: move duplicate code to its function [PB-1387] --------- Co-authored-by: shahin.rahbariasl --- modules/ixBidAdapter.js | 30 ++++++++++++++ test/spec/modules/ixBidAdapter_spec.js | 57 ++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index e791d933352..06f96edca30 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -195,6 +195,9 @@ function bidToVideoImp(bid) { // populate imp level transactionId imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + // AdUnit-Specific First Party Data + addAdUnitFPD(imp, bid) + // copy all video properties to imp object for (const adUnitProperty in videoAdUnitRef) { if (VIDEO_PARAMS_ALLOW_LIST.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { @@ -268,6 +271,9 @@ function bidToNativeImp(bid) { // populate imp level transactionId imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + // AdUnit-Specific First Party Data + addAdUnitFPD(imp, bid) + _applyFloor(bid, imp, NATIVE); return imp; @@ -956,6 +962,11 @@ function addImpressions(impressions, transactionIds, r, adUnitIndex) { _bannerImpression.bidfloorcur = impressionObjects[0].bidfloorcur; } + const adUnitFPD = impressions[transactionIds[adUnitIndex]].adUnitFPD + if (adUnitFPD) { + _bannerImpression.ext.data = adUnitFPD; + } + r.imp.push(_bannerImpression); } else { // set imp.ext.gpid to resolved gpid for each imp @@ -1011,6 +1022,19 @@ function addFPD(bidderRequest, r, fpd, site, user) { return r; } +/** + * Adds First-Party Data (FPD) from the bid object to the imp object. + * + * @param {Object} imp - The imp object, representing an impression in the OpenRTB format. + * @param {Object} bid - The bid object, containing information about the bid request. + */ +function addAdUnitFPD(imp, bid) { + const adUnitFPD = deepAccess(bid, 'ortb2Imp.ext.data'); + if (adUnitFPD) { + deepSetValue(imp, 'ext.data', adUnitFPD) + } +} + /** * addIdentifiersInfo adds indentifier info to ixDaig. * @@ -1196,6 +1220,12 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { bannerImps[validBidRequest.transactionId].tagId = deepAccess(validBidRequest, 'params.tagId'); bannerImps[validBidRequest.transactionId].pos = deepAccess(validBidRequest, 'mediaTypes.banner.pos'); + // AdUnit-Specific First Party Data + const adUnitFPD = deepAccess(validBidRequest, 'ortb2Imp.ext.data'); + if (adUnitFPD) { + bannerImps[validBidRequest.transactionId].adUnitFPD = adUnitFPD; + } + const sid = deepAccess(validBidRequest, 'params.id'); if (sid && (typeof sid === 'string' || typeof sid === 'number')) { bannerImps[validBidRequest.transactionId].sid = String(sid); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 8ea3b9dc522..149c4c44c21 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -1703,6 +1703,63 @@ describe('IndexexchangeAdapter', function () { expect(r.regs.gpp_sid).to.be.an('array'); expect(r.regs.gpp_sid).to.include(1); }); + + it('should add adunit specific data to imp ext for banner', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam banner', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = extractPayload(requests[0]).imp[0]; + expect(deepAccess(imp, 'ext.data.adserver.name')).to.equal('gam banner'); + expect(deepAccess(imp, 'ext.data.adserver.adslot')).to.equal(AD_UNIT_CODE); + }); + + it('should add adunit specific data to imp ext for native', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_NATIVE_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam native', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = extractPayload(requests[0]).imp[0]; + expect(deepAccess(imp, 'ext.data.adserver.name')).to.equal('gam native'); + expect(deepAccess(imp, 'ext.data.adserver.adslot')).to.equal(AD_UNIT_CODE); + }); + + it('should add adunit specific data to imp ext for video', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam video', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = extractPayload(requests[0]).imp[0]; + expect(deepAccess(imp, 'ext.data.adserver.name')).to.equal('gam video'); + expect(deepAccess(imp, 'ext.data.adserver.adslot')).to.equal(AD_UNIT_CODE); + }); }); describe('buildRequests', function () { From a28b6072c5e7dfb66221595e5f21603c7fdcb083 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 19 Apr 2023 18:04:44 +0300 Subject: [PATCH 330/375] KueezRtb Bid Adapter: Pass gpid to server. (#9831) --- modules/kueezRtbBidAdapter.js | 3 +++ test/spec/modules/kueezRtbBidAdapter_spec.js | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 8ed7e6ee408..ef53ed9baf4 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -77,6 +77,8 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { const pId = extractPID(params); const subDomain = extractSubDomain(params); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ currency: 'USD', @@ -105,6 +107,7 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { res: `${screen.width}x${screen.height}`, schain: schain, mediaTypes: mediaTypes, + gpid: gpid, auctionId: auctionId, transactionId: transactionId, bidderRequestId: bidderRequestId, diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 92cad63bf27..fc7219d2ee7 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -43,7 +43,12 @@ const BID = { 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER] + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789' + } + } }; const VIDEO_BID = { @@ -315,7 +320,8 @@ describe('KueezRtbBidAdapter', function () { protocols: [2, 3, 5, 6], startdelay: 0 } - } + }, + gpid: '' } }); }); @@ -373,6 +379,7 @@ describe('KueezRtbBidAdapter', function () { schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], + gpid: '0123456789', uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', From 467f78f211775702ac61e2ade86ed393ada26b9a Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Wed, 19 Apr 2023 17:57:30 +0200 Subject: [PATCH 331/375] Adnuntius Bid Adapter: Europe endpoint (#9829) * Adnuntius Bid Adapter: Route traffic to euro-servers if GDPR applies. * Tests Added to gdpr applies. --- modules/adnuntiusBidAdapter.js | 4 +++- test/spec/modules/adnuntiusBidAdapter_spec.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index c3cd1963248..e59d12eadad 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -6,6 +6,7 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'adnuntius'; const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; +const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i'; const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' // const DEFAULT_NATIVE = 'native' @@ -130,10 +131,11 @@ export const spec = { const network = networkKeys[j]; const networkRequest = [...request] if (network.indexOf('_video') > -1) { networkRequest.push('tt=' + DEFAULT_VAST_VERSION) } + const requestURL = gdprApplies ? ENDPOINT_URL_EUROPE : ENDPOINT_URL // if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } requests.push({ method: 'POST', - url: ENDPOINT_URL + '?' + networkRequest.join('&'), + url: requestURL + '?' + networkRequest.join('&'), data: JSON.stringify(networks[network]), bid: bidRequests[network] }); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 150e013af98..153565eb3ca 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -8,6 +8,7 @@ import { getStorageManager } from 'src/storageManager.js'; describe('adnuntiusBidAdapter', function () { const URL = 'https://ads.adnuntius.delivery/i?tzo='; + const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo='; const GVLID = 855; const usi = utils.generateUUID() const meta = [{ key: 'usi', value: usi }] @@ -35,7 +36,7 @@ describe('adnuntiusBidAdapter', function () { const ENDPOINT_URL_VIDEO = `${URL}${tzo}&format=json&userId=${usi}&tt=vast4`; const ENDPOINT_URL_NOCOOKIE = `${URL}${tzo}&format=json&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${URL}${tzo}&format=json&segments=segment1,segment2,segment3&userId=${usi}`; - const ENDPOINT_URL_CONSENT = `${URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; + const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; const adapter = newBidder(spec); const bidderRequests = [ From c19157316e82d2be7d35a41df48872d64967e4fa Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:27:12 +0200 Subject: [PATCH 332/375] TheMediaGrid: make gridNMBidAdater as alias for gridBidAdapter (#9832) * TheMediaGrid: make gridNMBidAdater as alias for gridBidAdapter * TheMediaGrid: fix alias name --- modules/gridBidAdapter.js | 61 +- modules/gridNMBidAdapter.js | 422 -------------- modules/gridNMBidAdapter.md | 39 -- test/spec/modules/gridBidAdapter_spec.js | 8 + test/spec/modules/gridNMBidAdapter_spec.js | 636 --------------------- 5 files changed, 50 insertions(+), 1116 deletions(-) delete mode 100644 modules/gridNMBidAdapter.js delete mode 100644 modules/gridNMBidAdapter.md delete mode 100644 test/spec/modules/gridNMBidAdapter_spec.js diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 792dc3b15b0..a043483d9b0 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -6,7 +6,10 @@ import { generateUUID, mergeDeep, logWarn, - parseUrl, isArray, isNumber + parseUrl, + isArray, + isNumber, + isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; @@ -52,7 +55,12 @@ const ALIAS_CONFIG = { bidResponseExternal: { netRevenue: false } - } + }, + 'gridNM': { + defaultParams: { + multiRequest: true + } + }, }; let hasSynced = false; @@ -60,7 +68,7 @@ let hasSynced = false; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['playwire', 'adlivetech', { code: 'trustx', skipPbsAliasing: true }], + aliases: ['playwire', 'adlivetech', 'gridNM', { code: 'trustx', skipPbsAliasing: true }], supportedMediaTypes: [ BANNER, VIDEO ], /** * Determines whether or not the given bid request is valid. @@ -126,8 +134,9 @@ export const spec = { if (!endpoint) { endpoint = ALIAS_CONFIG[bid.bidder] && ALIAS_CONFIG[bid.bidder].endpoint; } - const { params: { uid, keywords, forceBidder, multiRequest }, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; - const { secid, pubid, source, content: bidParamsContent } = bid.params; + const { params, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; + const { defaultParams } = ALIAS_CONFIG[bid.bidder] || {}; + const { secid, pubid, source, uid, keywords, forceBidder, multiRequest, content: bidParamsContent, video: videoParams } = { ...defaultParams, ...params }; const bidFloor = _getFloor(mediaTypes || {}, bid); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; if (jwTargeting && !content && jwTargeting.content) { @@ -178,7 +187,7 @@ export const spec = { } } if (mediaTypes && mediaTypes[VIDEO]) { - const video = createVideoRequest(bid, mediaTypes[VIDEO]); + const video = createVideoRequest(videoParams, mediaTypes[VIDEO], bid.sizes); if (video) { impObj.video = video; } @@ -588,27 +597,41 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bid } } -function createVideoRequest(bid, mediaType) { - const { playerSize, mimes, durationRangeSec, protocols } = mediaType; - const size = (playerSize || bid.sizes || [])[0]; - if (!size) return; +function createVideoRequest(videoParams, mediaType, bidSizes) { + const { mind, maxd, size, playerSize, protocols, durationRangeSec = [], ...videoData } = { ...mediaType, ...videoParams }; + if (size && isStr(size)) { + const sizeArray = size.split('x'); + if (sizeArray.length === 2 && parseInt(sizeArray[0]) && parseInt(sizeArray[1])) { + videoData.w = parseInt(sizeArray[0]); + videoData.h = parseInt(sizeArray[1]); + } + } + if (!videoData.w || !videoData.h) { + const pSizesString = (playerSize || bidSizes || []).toString(); + const pSizeString = (pSizesString.match(/^\d+,\d+/) || [])[0]; + const pSize = pSizeString && pSizeString.split(',').map((d) => parseInt(d)); + if (pSize && pSize.length === 2) { + Object.assign(videoData, parseGPTSingleSizeArrayToRtbSize(pSize)); + } + } - let result = parseGPTSingleSizeArrayToRtbSize(size); + if (!videoData.w || !videoData.h) return; - if (mimes) { - result.mimes = mimes; - } + const minDur = mind || durationRangeSec[0] || videoData.minduration; + const maxDur = maxd || durationRangeSec[1] || videoData.maxduration; - if (durationRangeSec && durationRangeSec.length === 2) { - result.minduration = durationRangeSec[0]; - result.maxduration = durationRangeSec[1]; + if (minDur) { + videoData.minduration = minDur; + } + if (maxDur) { + videoData.maxduration = maxDur; } if (protocols && protocols.length) { - result.protocols = protocols; + videoData.protocols = protocols; } - return result; + return videoData; } function createBannerRequest(bid, mediaType) { diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js deleted file mode 100644 index 36038d521bd..00000000000 --- a/modules/gridNMBidAdapter.js +++ /dev/null @@ -1,422 +0,0 @@ -import { - isStr, - deepAccess, - isArray, - isNumber, - logError, - logWarn, - parseGPTSingleSizeArrayToRtbSize, - mergeDeep -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'gridNM'; -const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; -const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; -const TIME_TO_LIVE = 360; -const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -let hasSynced = false; - -const LOG_ERROR_MESS = { - noAdm: 'Bid from response has no adm parameter - ', - noPrice: 'Bid from response has no price parameter - ', - wrongContentType: 'Bid from response has wrong content_type parameter - ', - noBid: 'Array of bid objects is empty', - noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', - emptyUids: 'Uids should be not empty', - emptySeatbid: 'Seatbid array from response has empty item', - emptyResponse: 'Response is empty', - hasEmptySeatbidArray: 'Response has empty seatbid array', - hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' -}; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [ VIDEO ], - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - let invalid = - !bid.params.source || !isStr(bid.params.source) || - !bid.params.secid || !isStr(bid.params.secid) || - !bid.params.pubid || !isStr(bid.params.pubid); - - const video = deepAccess(bid, 'mediaTypes.video') || {}; - const { protocols = video.protocols, mimes = video.mimes } = deepAccess(bid, 'params.video') || {}; - if (!invalid) { - invalid = !protocols || !mimes; - } - if (!invalid) { - invalid = !isArray(mimes) || !mimes.length || mimes.filter((it) => !(it && isStr(it))).length; - if (!invalid) { - invalid = !isArray(protocols) || !protocols.length || protocols.filter((it) => !(isNumber(it) && it > 0 && !(it % 1))).length; - } - } - return !invalid; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests - an array of bids - * @param {bidderRequest} bidderRequest bidder request object - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - const bids = validBidRequests || []; - const requests = []; - let { bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo } = bidderRequest || {}; - - const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; - - bids.forEach(bid => { - let user; - let userExt; - - const schain = bid.schain; - const userIdAsEids = bid.userIdAsEids; - - if (!bidderRequestId) { - bidderRequestId = bid.bidderRequestId; - } - if (!auctionId) { - auctionId = bid.auctionId; - } - const { - params: { floorcpm, source, secid, pubid, content, video }, - mediaTypes, bidId, adUnitCode, rtd, ortb2Imp, sizes - } = bid; - - const bidFloor = _getFloor(mediaTypes || {}, bid, isNumber(floorcpm) && floorcpm); - const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; - - const siteContent = content || (jwTargeting && jwTargeting.content); - - const impObj = { - id: bidId.toString(), - tagid: secid.toString(), - video: createVideoForImp(mergeDeep({}, video, mediaTypes && mediaTypes.video), sizes), - ext: { - divid: adUnitCode.toString() - } - }; - - if (ortb2Imp && ortb2Imp.ext && ortb2Imp.ext.data) { - impObj.ext.data = ortb2Imp.ext.data; - if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { - impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); - } else { - impObj.ext.gpid = ortb2Imp.ext.data.pbadslot && ortb2Imp.ext.data.pbadslot.toString(); - } - } - - if (bidFloor) { - impObj.bidfloor = bidFloor; - } - - const imp = [impObj]; - - const reqSource = { - tid: auctionId && auctionId.toString(), - ext: { - wrapper: 'Prebid_js', - wrapper_version: '$prebid.version$' - } - }; - - if (schain) { - reqSource.ext.schain = schain; - } - - const bidderTimeout = timeout; - const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; - - const request = { - id: bidderRequestId && bidderRequestId.toString(), - site: { - page: referer, - publisher: { - id: pubid, - }, - }, - source: reqSource, - tmax, - imp, - }; - - if (siteContent) { - request.site.content = siteContent; - } - - if (gdprConsent && gdprConsent.consentString) { - userExt = { consent: gdprConsent.consentString }; - } - - if (userIdAsEids && userIdAsEids.length) { - userExt = userExt || {}; - userExt.eids = [...userIdAsEids]; - } - - if (userExt && Object.keys(userExt).length) { - user = user || {}; - user.ext = userExt; - } - - const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); - if (ortb2UserData && ortb2UserData.length) { - if (!user) { - user = { data: [] }; - } - user = mergeDeep(user, { - data: [...ortb2UserData] - }); - } - - if (user) { - request.user = user; - } - const site = deepAccess(bidderRequest, 'ortb2.site'); - if (site) { - const data = deepAccess(site, 'content.data'); - if (data && data.length) { - const siteContent = request.site.content || {}; - request.site.content = mergeDeep(siteContent, { data }); - } - } - - if (gdprConsent && gdprConsent.gdprApplies) { - request.regs = { - ext: { - gdpr: gdprConsent.gdprApplies ? 1 : 0 - } - }; - } - - if (uspConsent) { - if (!request.regs) { - request.regs = { ext: {} }; - } - request.regs.ext.us_privacy = uspConsent; - } - - if (config.getConfig('coppa') === true) { - if (!request.regs) { - request.regs = {}; - } - request.regs.coppa = 1; - } - - requests.push({ - method: 'POST', - url: ENDPOINT_URL + '?no_mapping=1&sp=' + source, - bid: bid, - data: request - }); - }); - - return requests; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @param {*} bidRequest - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidRequest) { - serverResponse = serverResponse && serverResponse.body; - const bidResponses = []; - - let errorMessage; - - if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (serverResponse.seatbid && !serverResponse.seatbid.length) { - errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - } - - if (!errorMessage && serverResponse.seatbid) { - const serverBid = _getBidFromResponse(serverResponse.seatbid[0]); - if (serverBid) { - if (!serverBid.adm && !serverBid.nurl) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); - else if (!serverBid.price) errorMessage = LOG_ERROR_MESS.noPrice + JSON.stringify(serverBid); - else if (serverBid.content_type !== 'video') errorMessage = LOG_ERROR_MESS.wrongContentType + serverBid.content_type; - if (!errorMessage) { - const bid = bidRequest.bid; - const bidResponse = { - requestId: bid.bidId, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid || bid.bidderRequestId, - currency: 'USD', - netRevenue: true, - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid, - mediaType: VIDEO, - meta: { - advertiserDomains: serverBid.adomain ? serverBid.adomain : [] - } - }; - - if (serverBid.adm) { - bidResponse.vastXml = serverBid.adm; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - } else if (serverBid.nurl) { - bidResponse.vastUrl = serverBid.nurl; - } - - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }); - } - bidResponses.push(bidResponse); - } - } - } - if (errorMessage) logError(errorMessage); - return bidResponses; - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - if (!hasSynced && syncOptions.pixelEnabled) { - let params = ''; - - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent) { - params += `&us_privacy=${uspConsent}`; - } - - hasSynced = true; - return { - type: 'image', - url: SYNC_URL + params - }; - } - } -}; - -/** - * Gets bidfloor - * @param {Object} mediaTypes - * @param {Object} bid - * @param {Number} floor - * @returns {Number} floor - */ -function _getFloor (mediaTypes, bid, floor) { - const curMediaType = mediaTypes.video ? 'video' : 'banner'; - - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: curMediaType, - size: bid.sizes.map(([w, h]) => ({w, h})) - }); - - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); - } - } - - return floor; -} - -function _getBidFromResponse(respItem) { - if (!respItem) { - logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - -function outstreamRender (bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - targetId: bid.adUnitCode, - adResponse: bid.adResponse - }); - }); -} - -function createRenderer (bid, rendererParams) { - const renderer = Renderer.install({ - id: rendererParams.id, - url: rendererParams.url, - loaded: false - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } - - return renderer; -} - -function createVideoForImp({ mind, maxd, size, ...paramsVideo }, bidSizes) { - if (size && isStr(size)) { - const sizeArray = size.split('x'); - if (sizeArray.length === 2 && parseInt(sizeArray[0]) && parseInt(sizeArray[1])) { - paramsVideo.w = parseInt(sizeArray[0]); - paramsVideo.h = parseInt(sizeArray[1]); - } - } - - if (!paramsVideo.w || !paramsVideo.h) { - const playerSizes = paramsVideo.playerSize && paramsVideo.playerSize.length === 2 ? paramsVideo.playerSize : bidSizes; - if (playerSizes) { - const playerSize = playerSizes[0]; - if (playerSize) { - Object.assign(paramsVideo, parseGPTSingleSizeArrayToRtbSize(playerSize)); - } - } - } - - if (paramsVideo.playerSize) { - delete paramsVideo.playerSize; - } - - const durationRangeSec = paramsVideo.durationRangeSec || []; - const minDur = mind || durationRangeSec[0] || paramsVideo.minduration; - const maxDur = maxd || durationRangeSec[1] || paramsVideo.maxduration; - - if (minDur) { - paramsVideo.minduration = minDur; - } - if (maxDur) { - paramsVideo.maxduration = maxDur; - } - - return paramsVideo; -} - -export function resetUserSync() { - hasSynced = false; -} - -export function getSyncUrl() { - return SYNC_URL; -} - -registerBidder(spec); diff --git a/modules/gridNMBidAdapter.md b/modules/gridNMBidAdapter.md deleted file mode 100644 index 6decdde7f4c..00000000000 --- a/modules/gridNMBidAdapter.md +++ /dev/null @@ -1,39 +0,0 @@ -# Overview - -Module Name: The Grid Media Bidder Adapter -Module Type: Bidder Adapter -Maintainer: grid-tech@themediagrid.com - -# Description - -Module that connects to Grid demand source to fetch bids. -Grid bid adapter supports Banner and Video (instream and outstream). - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - video: { - playerSize: [728, 90], - context: 'outstream' - } - }, - bids: [ - { - bidder: "gridNM", - params: { - source: 'jwp', - secid: '11', - pubid: '22', - video: { - mimes: ['video/mp4', 'video/x-ms-wmv'], - protocols: [1,2,3,4,5,6] - } - } - } - ] - } - ]; -``` diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 1cafdf6134f..2ef604dd097 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -480,6 +480,14 @@ describe('TheMediaGrid Adapter', function () { 'h': 600, 'protocols': [1, 2, 3], 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + 'context': 'instream', + 'maxduration': 30, + 'minduration': 0, + 'api': [1, 2], + 'skip': 1, + 'placement': 1, + 'playbackmethod': 1, + 'startdelay': 0 } }, { 'id': bidRequests[3].bidId, diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js deleted file mode 100644 index e4f06a451d2..00000000000 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ /dev/null @@ -1,636 +0,0 @@ -import { expect } from 'chai'; -import { spec, resetUserSync, getSyncUrl } from 'modules/gridNMBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('TheMediaGridNM Adapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - const paramsList = [ - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - } - }, - { - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - } - ]; - paramsList.forEach((params) => { - const invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = params; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - }); - - it('should return false when required params has invalid values', function () { - const paramsList = [ - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': '1,2,3,4,5' - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': [1, 2], - 'protocols': [1, 2, 3, 4, 5] - } - }, - { - 'source': 'jwp', - 'secid': 11, - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5] - } - }, - { - 'source': 111, - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5] - } - } - ]; - - paramsList.forEach((params) => { - const invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = params; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - }); - - it('should return true when required params is absent, but available in mediaTypes', function () { - const paramsList = [ - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - } - } - ]; - - const mediaTypes = { - video: { - mimes: ['video/mp4', 'video/x-ms-wmv'], - playerSize: [200, 300], - protocols: [1, 2, 3, 4, 5, 6] - } - }; - - paramsList.forEach((params) => { - const validBid = Object.assign({}, bid); - delete validBid.params; - validBid.params = params; - validBid.mediaTypes = mediaTypes; - expect(spec.isBidRequestValid(validBid)).to.equal(true); - }); - }); - }); - - describe('buildRequests', function () { - function parseRequestUrl(url) { - const res = {}; - url.replace(/^[^\?]+\?/, '').split('&').forEach((it) => { - const couple = it.split('='); - res[couple[0]] = decodeURIComponent(couple[1]); - }); - return res; - } - const bidderRequest = { - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - timeout: 3000, - refererInfo: { page: 'https://example.com' } - }; - const referrer = encodeURIComponent(bidderRequest.refererInfo.page); - let bidRequests = [ - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'floorcpm': 2, - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3], - 'skip': 1 - } - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '3150ccb55da321', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('if content and segment is present in jwTargeting, payload must have right params', function () { - const jsContent = {id: 'test_jw_content_id'}; - const jsSegments = ['test_seg_1', 'test_seg_2']; - const bidRequestsWithJwTargeting = bidRequests.map((bid) => { - return Object.assign({ - rtd: { - jwplayer: { - targeting: { - segments: jsSegments, - content: jsContent - } - } - } - }, bid); - }); - const requests = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); - requests.forEach((req, i) => { - const payload = req.data; - expect(req).to.have.property('data'); - expect(payload).to.have.property('site'); - expect(payload.site.content).to.deep.equal(jsContent); - }); - }); - - it('should attach valid params to the tag', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - const requestsSizes = ['300x250,300x600', '728x90']; - requests.forEach((req, i) => { - expect(req.url).to.be.an('string'); - const payload = parseRequestUrl(req.url); - expect(payload).to.have.property('no_mapping', '1'); - expect(payload).to.have.property('sp', 'jwp'); - - const sizes = { w: bidRequests[i].sizes[0][0], h: bidRequests[i].sizes[0][1] }; - const impObj = { - 'id': bidRequests[i].bidId, - 'tagid': bidRequests[i].params.secid, - 'ext': {'divid': bidRequests[i].adUnitCode}, - 'video': Object.assign(sizes, bidRequests[i].params.video) - }; - - if (bidRequests[i].params.floorcpm) { - impObj.bidfloor = bidRequests[i].params.floorcpm; - } - - expect(req.data).to.deep.equal({ - 'id': bidderRequest.bidderRequestId, - 'site': { - 'page': referrer, - 'publisher': { - 'id': bidRequests[i].params.pubid - } - }, - 'tmax': bidderRequest.timeout, - 'source': { - 'tid': bidderRequest.auctionId, - 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} - }, - 'imp': [impObj] - }); - }); - }); - - it('should attach valid params from mediaTypes', function () { - const mediaTypes = { - video: { - skipafter: 10, - minduration: 10, - maxduration: 100, - protocols: [1, 3, 4], - playerSize: [[300, 250]] - } - }; - const bidRequest = Object.assign({ mediaTypes }, bidRequests[0]); - const req = spec.buildRequests([bidRequest], bidderRequest)[0]; - const expectedVideo = { - 'skipafter': 10, - 'minduration': 10, - 'maxduration': 100, - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6], - 'w': 300, - 'h': 250 - }; - - expect(req.url).to.be.an('string'); - const payload = parseRequestUrl(req.url); - expect(payload).to.have.property('no_mapping', '1'); - expect(payload).to.have.property('sp', 'jwp'); - expect(req.data).to.deep.equal({ - 'id': bidderRequest.bidderRequestId, - 'site': { - 'page': referrer, - 'publisher': { - 'id': bidRequest.params.pubid - } - }, - 'tmax': bidderRequest.timeout, - 'source': { - 'tid': bidderRequest.auctionId, - 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} - }, - 'imp': [{ - 'id': bidRequest.bidId, - 'bidfloor': bidRequest.params.floorcpm, - 'tagid': bidRequest.params.secid, - 'ext': {'divid': bidRequest.adUnitCode}, - 'video': expectedVideo - }] - }); - }); - - it('if gdprConsent is present payload must have gdpr params', function () { - const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); - const request = spec.buildRequests([bidRequests[0]], gdprBidderRequest)[0]; - const payload = request.data; - expect(request).to.have.property('data'); - expect(payload).to.have.property('user'); - expect(payload.user).to.have.property('ext'); - expect(payload.user.ext).to.have.property('consent', 'AAA'); - expect(payload).to.have.property('regs'); - expect(payload.regs).to.have.property('ext'); - expect(payload.regs.ext).to.have.property('gdpr', 1); - }); - - it('if usPrivacy is present payload must have us_privacy param', function () { - const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const request = spec.buildRequests([bidRequests[0]], bidderRequestWithUSP)[0]; - const payload = request.data; - expect(payload).to.have.property('regs'); - expect(payload.regs).to.have.property('ext'); - expect(payload.regs.ext).to.have.property('us_privacy', '1YNN'); - }); - }); - - describe('interpretResponse', function () { - const responses = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '2'}, - {'bid': [{'price': 0.5, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 600, 'w': 300, adomain: ['my_domain.ru']}], 'seat': '2'}, - {'bid': [{'price': 2.00, 'nurl': 'https://some_test_vast_url.com', 'content_type': 'video', 'adomain': ['example.com'], 'w': 300, 'h': 600}], 'seat': '2'}, - {'bid': [{'price': 0, 'h': 250, 'w': 300}], 'seat': '2'}, - {'bid': [{'price': 0, 'adm': '\n<\/Ad>\n<\/VAST>', 'h': 250, 'w': 300}], 'seat': '2'}, - undefined, - {'bid': [], 'seat': '2'}, - {'seat': '2'}, - ]; - - it('should get correct video bid response', function () { - const bidRequests = [ - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '659423fff799cb', - 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }, - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3, 4, 5], - 'skip': 1 - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '2bc598e42b6a', - 'bidderRequestId': '1e8b5a465f404', - 'auctionId': '1cbd2feafe5e8b', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }, - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3], - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '127f4b12a432c', - 'bidderRequestId': 'a75bc868f32', - 'auctionId': '1cbd2feafe5e8b', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - } - ]; - const requests = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '659423fff799cb', - 'cpm': 1.15, - 'creativeId': '5f2009617a7c0a', - 'dealId': 11, - 'width': 300, - 'height': 250, - 'currency': 'USD', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'meta': { - 'advertiserDomains': [] - }, - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - }, - { - 'requestId': '2bc598e42b6a', - 'cpm': 0.5, - 'creativeId': '1e8b5a465f404', - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'currency': 'USD', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'meta': { - 'advertiserDomains': ['my_domain.ru'] - }, - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - }, - { - 'requestId': '127f4b12a432c', - 'cpm': 2.00, - 'creativeId': 'a75bc868f32', - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'currency': 'USD', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'meta': { - advertiserDomains: ['example.com'] - }, - 'vastUrl': 'https://some_test_vast_url.com', - } - ]; - - requests.forEach((req, i) => { - const result = spec.interpretResponse({'body': {'seatbid': [responses[i]]}}, req); - expect(result[0]).to.deep.equal(expectedResponse[i]); - }); - }); - - it('handles wrong and nobid responses', function () { - responses.slice(3).forEach((resp) => { - const request = spec.buildRequests([{ - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3, 4, 5], - 'skip': 1 - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '2bc598e42b6a', - 'bidderRequestId': '39d74f5b71464', - 'auctionId': '1cbd2feafe5e8b', - 'meta': { - 'advertiserDomains': [] - }, - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }]); - const result = spec.interpretResponse({'body': {'seatbid': [resp]}}, request[0]); - expect(result.length).to.equal(0); - }); - }); - }); - - describe('user sync', function () { - const syncUrl = getSyncUrl(); - - beforeEach(function () { - resetUserSync(); - }); - - it('should register the Emily iframe', function () { - let syncs = spec.getUserSyncs({ - pixelEnabled: true - }); - - expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); - }); - - it('should not register the Emily iframe more than once', function () { - let syncs = spec.getUserSyncs({ - pixelEnabled: true - }); - expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); - - // when called again, should still have only been called once - syncs = spec.getUserSyncs(); - expect(syncs).to.equal(undefined); - }); - - it('should pass gdpr params if consent is true', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - gdprApplies: true, consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr=1&gdpr_consent=foo` - }); - }); - - it('should pass gdpr params if consent is false', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - gdprApplies: false, consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr=0&gdpr_consent=foo` - }); - }); - - it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr_consent=foo` - }); - }); - - it('should pass no params if gdpr consentString is not defined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {})).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is a number', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: 0 - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is null', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: null - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is a object', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: {} - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr is not defined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined)).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass usPrivacy param if it is available', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {}, '1YNN')).to.deep.equal({ - type: 'image', url: `${syncUrl}&us_privacy=1YNN` - }); - }); - }); -}); From a53964661668c8e2dd45a93a78c69128ab3da86f Mon Sep 17 00:00:00 2001 From: congdu-kun <126609480+congdu-kun@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:40:10 -0700 Subject: [PATCH 333/375] pairId userId submodule: initial commit for pairId submodule (#9662) * add userId submodule for pairId system * [PairIdSystem] add logic to fetch from liveramp cookie * Fix liveramp local storage/cookie key for PairId * addressed PR review comments --- modules/pairIdSystem.js | 78 ++++++++++++++++++++++++++ modules/userId/eids.js | 6 ++ test/spec/modules/pairIdSystem_spec.js | 68 ++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 modules/pairIdSystem.js create mode 100644 test/spec/modules/pairIdSystem_spec.js diff --git a/modules/pairIdSystem.js b/modules/pairIdSystem.js new file mode 100644 index 00000000000..1f39169d52b --- /dev/null +++ b/modules/pairIdSystem.js @@ -0,0 +1,78 @@ +/** + * This module adds PAIR Id to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/pairIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js' +import { logError } from '../src/utils.js'; + +const MODULE_NAME = 'pairId'; +const PAIR_ID_KEY = 'pairId'; +const DEFAULT_LIVERAMP_PAIR_ID_KEY = '_lr_pairId'; + +export const storage = getStorageManager() + +function pairIdFromLocalStorage(key) { + return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(key) : null; +} + +function pairIdFromCookie(key) { + return storage.cookiesAreEnabled ? storage.getCookie(key) : null; +} + +/** @type {Submodule} */ +export const pairIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param { string | undefined } value + * @returns {{pairId:string} | undefined } + */ + decode(value) { + return value && Array.isArray(value) ? {'pairId': value} : undefined + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @returns {id: string | undefined } + */ + getId(config) { + const pairIdsString = pairIdFromLocalStorage(PAIR_ID_KEY) || pairIdFromCookie(PAIR_ID_KEY) + let ids = [] + if (pairIdsString && typeof pairIdsString == 'string') { + try { + ids = ids.concat(JSON.parse(atob(pairIdsString))) + } catch (error) { + logError(error) + } + } + + const configParams = (config && config.params) || {}; + if (configParams && configParams.liveramp) { + let LRStorageLocation = configParams.liveramp.storageKey || DEFAULT_LIVERAMP_PAIR_ID_KEY + const liverampValue = pairIdFromLocalStorage(LRStorageLocation) || pairIdFromCookie(LRStorageLocation) + try { + const obj = JSON.parse(atob(liverampValue)); + ids = ids.concat(obj.envelope); + } catch (error) { + logError(error) + } + } + + if (ids.length == 0) { + logError('PairId not found.') + return undefined; + } + return {'id': ids}; + } +}; + +submodule('userId', pairIdSubmodule); diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 2f2d1ca7514..fa8e6f7647c 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -45,6 +45,12 @@ export const USER_IDS_CONFIG = { atype: 1 }, + // pairId + 'pairId': { + source: 'google.com', + atype: 571187 + }, + // justId 'justId': { source: 'justtag.com', diff --git a/test/spec/modules/pairIdSystem_spec.js b/test/spec/modules/pairIdSystem_spec.js new file mode 100644 index 00000000000..4f75666affe --- /dev/null +++ b/test/spec/modules/pairIdSystem_spec.js @@ -0,0 +1,68 @@ +import { storage, pairIdSubmodule } from 'modules/pairIdSystem.js'; +import * as utils from 'src/utils.js'; + +describe('pairId', function () { + let sandbox; + let logErrorStub; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + logErrorStub = sandbox.stub(utils, 'logError'); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('should log an error if no ID is found when getId', function() { + pairIdSubmodule.getId({ params: {} }); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should read pairId from local storage if exists', function() { + let pairIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('pairId').returns(btoa(JSON.stringify(pairIds))); + + let id = pairIdSubmodule.getId({ params: {} }); + expect(id).to.be.deep.equal({id: pairIds}); + }); + + it('should read pairId from cookie if exists', function() { + let pairIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6']; + sandbox.stub(storage, 'getCookie').withArgs('pairId').returns(btoa(JSON.stringify(pairIds))); + + let id = pairIdSubmodule.getId({ params: {} }); + expect(id).to.be.deep.equal({id: pairIds}); + }); + + it('should read pairId from default liveramp envelope local storage key if configured', function() { + let pairIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': pairIds}))); + let id = pairIdSubmodule.getId({ + params: { + liveramp: {} + }}) + expect(id).to.be.deep.equal({id: pairIds}) + }) + + it('should read pairId from default liveramp envelope cookie entry if configured', function() { + let pairIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': pairIds}))); + let id = pairIdSubmodule.getId({ + params: { + liveramp: {} + }}) + expect(id).to.be.deep.equal({id: pairIds}) + }) + + it('should read pairId from specified liveramp envelope cookie entry if configured with storageKey', function() { + let pairIds = ['test-pair-id7', 'test-pair-id8', 'test-pair-id9']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('lr_pairId_custom').returns(btoa(JSON.stringify({'envelope': pairIds}))); + let id = pairIdSubmodule.getId({ + params: { + liveramp: { + storageKey: 'lr_pairId_custom' + } + }}) + expect(id).to.be.deep.equal({id: pairIds}) + }) +}); From ad7d8989e671148aebaee7954f4128fbfe7e09c7 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 19 Apr 2023 15:05:20 -0400 Subject: [PATCH 334/375] Build Process: Disallow it.skip (#9799) * Update realvuAnalyticsAdapter_spec.js * Update axonixBidAdapter_spec.js * Update pubCommonId_spec.js * Update test_index.js * Update test_index.js * Update adagioBidAdapter_spec.js * Update test_index.js * Update test_index.js * Update test_index.js * Update test_index.js * Update openxOrtbBidAdapter_spec.js * Update 1plusXRtdProvider_spec.js * Update pubmaticBidAdapter_spec.js --- test/spec/modules/1plusXRtdProvider_spec.js | 15 ++++++----- test/spec/modules/adagioBidAdapter_spec.js | 25 ----------------- test/spec/modules/axonixBidAdapter_spec.js | 27 ------------------- test/spec/modules/pubCommonId_spec.js | 17 ------------ test/spec/modules/pubmaticBidAdapter_spec.js | 12 --------- .../modules/realvuAnalyticsAdapter_spec.js | 5 ---- test/test_index.js | 16 ++++++++--- 7 files changed, 21 insertions(+), 96 deletions(-) diff --git a/test/spec/modules/1plusXRtdProvider_spec.js b/test/spec/modules/1plusXRtdProvider_spec.js index 496c005f470..8657c37d7d8 100644 --- a/test/spec/modules/1plusXRtdProvider_spec.js +++ b/test/spec/modules/1plusXRtdProvider_spec.js @@ -332,15 +332,16 @@ describe('1plusXRtdProvider', () => { } it('correctly builds URLs if gdpr parameters are present', () => { - const url1 = getPapiUrl(customer) - const url2 = getPapiUrl(customer, extractConsent(consent)) - expect(['&consent_string=myConsent&gdpr_applies=1', '&gdpr_applies=1&consent_string=myConsent']).to.contain(url2.replace(url1, '')) + const url1 = getPapiUrl(customer); + const url2 = getPapiUrl(customer, extractConsent(consent)); + expect(['&consent_string=myConsent&gdpr_applies=1', '&gdpr_applies=1&consent_string=myConsent']).to.contain(url2.replace(url1, '')); }) - it('correctly builds URLs if fpid parameters are present') - const url1 = getPapiUrl(customer) - const url2 = getPapiUrl(customer, {}, 'my_first_party_id') - expect(url2.replace(url1, '')).to.equal('&fpid=my_first_party_id') + it('correctly builds URLs if fpid parameters are present', () => { + const url1 = getPapiUrl(customer); + const url2 = getPapiUrl(customer, {}, 'my_first_party_id'); + expect(url2.replace(url1, '')).to.equal('&fpid=my_first_party_id'); + }) }) describe('updateBidderConfig', () => { diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 759b16a81bb..adba79ddc96 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1481,31 +1481,6 @@ describe('Adagio bid adapter', () => { }); }); - describe.skip('optional params auto detection', function() { - it('should auto detect adUnitElementId when GPT is used', function() { - sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').withArgs('banner').returns({divId: 'gpt-banner'}); - expect(adagio.autoDetectAdUnitElementId('banner')).to.eq('gpt-banner'); - }); - }); - - describe.skip('print number handling', function() { - it('should return 1 if no adunit-code found. This means it is the first auction', function() { - sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); - expect(adagio.computePrintNumber('adunit-code')).to.eql(1); - }); - - it('should increment the adunit print number when the adunit-code has already been used for an other auction', function() { - sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); - - window.top.ADAGIO.adUnits['adunit-code'] = { - pageviewId: 'abc-def', - printNumber: 1, - }; - - expect(adagio.computePrintNumber('adunit-code')).to.eql(2); - }); - }); - describe('site information using refererDetection or window.top', function() { it('should returns domain, page and window.referrer in a window.top context', function() { const bidderRequest = new BidderRequestBuilder({ diff --git a/test/spec/modules/axonixBidAdapter_spec.js b/test/spec/modules/axonixBidAdapter_spec.js index 37f409e5769..c1cc6d46fb2 100644 --- a/test/spec/modules/axonixBidAdapter_spec.js +++ b/test/spec/modules/axonixBidAdapter_spec.js @@ -286,26 +286,6 @@ describe('AxonixBidAdapter', function () { }); }); - describe.skip('buildRequests: can handle native ad requests', function () { - it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { - // loop: - // set supply id - // set region/endpoint in ssp config - // call buildRequests, validate request (url, method, supply id) - expect.fail('Not implemented'); - }); - - it('creates ServerRequests pointing to default endpoint if missing', function () { - // no endpoint in config means default value openrtb.axonix.com - expect.fail('Not implemented'); - }); - - it('creates ServerRequests pointing to default region if missing', function () { - // no region in config means default value us-east-1 - expect.fail('Not implemented'); - }); - }); - describe('interpretResponse', function () { it('considers corner cases', function() { expect(spec.interpretResponse(null)).to.be.an('array').that.is.empty; @@ -331,13 +311,6 @@ describe('AxonixBidAdapter', function () { expect(response).to.be.an('array').that.is.not.empty; expect(response[0]).to.equal(VIDEO_RESPONSE.body[0]); }); - - it.skip('parses 1 native responses', function () { - // passing 1 valid native in a response generates an array with 1 correct prebid response - // examine mediaType:native, native element - // check nativeBidIsValid from {@link file://./../../../src/native.js} - expect.fail('Not implemented'); - }); }); describe('onBidWon', function () { diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js index a46ff26c4b8..7c539014cc5 100644 --- a/test/spec/modules/pubCommonId_spec.js +++ b/test/spec/modules/pubCommonId_spec.js @@ -233,23 +233,6 @@ describe('Publisher Common ID', function () { }); }); }); - - it.skip('disable auto create', function() { - setConfig({ - create: false - }); - - const config = getPubcidConfig(); - expect(config.create).to.be.false; - expect(config.typeEnabled).to.equal('html5'); - - let adUnits = getAdUnits(); - let innerAdUnits; - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - const pubcid = localStorage.getItem(ID_NAME); - expect(pubcid).to.be.null; - }); }); describe('Invoking requestBid', function () { diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index df07e9ff66d..3198fe406e7 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1118,18 +1118,6 @@ describe('PubMatic adapter', function () { expect(data.test).to.equal(undefined); }); - // disabled this test case as it refreshes the whole suite when in karma watch mode - // todo: needs a fix - xit('test flag set to 1 when pubmaticTest=true is present in page url', function() { - window.location.href += '#pubmaticTest=true'; - // now all the test cases below will have window.location.href with #pubmaticTest=true - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.test).to.equal(1); - }); - it('Request params check', function () { let request = spec.buildRequests(bidRequests, { auctionId: 'new-auction-id' diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js index e51a4e2e3a2..aa17a2c7c75 100644 --- a/test/spec/modules/realvuAnalyticsAdapter_spec.js +++ b/test/spec/modules/realvuAnalyticsAdapter_spec.js @@ -81,11 +81,6 @@ describe('RealVu', function() { result = realvuAnalyticsAdapter.checkIn(bid, ''); // test invalid partnerId '' }); - it.skip('isInView returns "yes"', () => { - let inview = realvuAnalyticsAdapter.isInView('ad1'); - expect(inview).to.equal('yes'); - }); - it('isInView return "NA"', function () { const adUnitCode = '1234'; let result = realvuAnalyticsAdapter.isInView(adUnitCode); diff --git a/test/test_index.js b/test/test_index.js index 04d1412860b..ce9b671be89 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,4 +1,3 @@ - [it, describe].forEach((ob) => { ob.only = function () { [ @@ -7,8 +6,19 @@ // eslint-disable-next-line no-console ].forEach(l => console.error(l)) throw new Error('do not use .only()') - } -}) + }; +}); + +[it, describe].forEach((ob) => { + ob.skip = function () { + [ + 'describe.skip and it.skip are disabled,', + 'because they pollute the pipeline test output', + // eslint-disable-next-line no-console + ].forEach(l => console.error(l)) + throw new Error('do not use .skip()') + }; +}); require('./test_deps.js'); From dcc99b31e1315ecd809bfaca6f7f2b40d2984078 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 19 Apr 2023 12:20:20 -0700 Subject: [PATCH 335/375] Pair ID system: fix storageManager invocation (#9833) --- modules/pairIdSystem.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pairIdSystem.js b/modules/pairIdSystem.js index 1f39169d52b..4d73d4efb7e 100644 --- a/modules/pairIdSystem.js +++ b/modules/pairIdSystem.js @@ -8,12 +8,13 @@ import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js' import { logError } from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'pairId'; const PAIR_ID_KEY = 'pairId'; const DEFAULT_LIVERAMP_PAIR_ID_KEY = '_lr_pairId'; -export const storage = getStorageManager() +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); function pairIdFromLocalStorage(key) { return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(key) : null; From b8c1c958c78fa869caafa5836e6a4dad85186462 Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Wed, 19 Apr 2023 12:24:56 -0700 Subject: [PATCH 336/375] Yieldmo Adapter: Send GPP data in bid request. (#9460) * Adding gpp and gpp_sid adding gpp parameters to Banner and Video bids. * Sending gdpr data if no gpp * Refactor --- modules/yieldmoBidAdapter.js | 20 +++++++++++++++----- test/spec/modules/yieldmoBidAdapter_spec.js | 20 +++++++++++++++++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 526e1911a06..508b94853e4 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -68,6 +68,7 @@ export const spec = { const videoBidRequests = bidRequests.filter(request => hasVideoMediaType(request)); let serverRequests = []; const eids = getEids(bidRequests[0]) || []; + if (bannerBidRequests.length > 0) { let serverRequest = { pbav: '$prebid.version$', @@ -80,7 +81,9 @@ export const spec = { userConsent: JSON.stringify({ // case of undefined, stringify will remove param gdprApplies: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', - cmp: deepAccess(bidderRequest, 'gdprConsent.consentString') || '' + cmp: deepAccess(bidderRequest, 'gdprConsent.consentString') || '', + gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || '', + gpp_sid: deepAccess(bidderRequest, 'gppConsent.applicableSections') || '' }), us_privacy: deepAccess(bidderRequest, 'uspConsent') || '' }; @@ -514,12 +517,19 @@ function openRtbSite(bidRequest, bidderRequest) { */ function populateOpenRtbGdpr(openRtbRequest, bidderRequest) { const gdpr = bidderRequest.gdprConsent; - if (gdpr && 'gdprApplies' in gdpr) { - deepSetValue(openRtbRequest, 'regs.ext.gdpr', gdpr.gdprApplies ? 1 : 0); - deepSetValue(openRtbRequest, 'user.ext.consent', gdpr.consentString); + const gpp = deepAccess(bidderRequest, 'gppConsent.gppString'); + const gppsid = deepAccess(bidderRequest, 'gppConsent.applicableSections'); + if (gpp) { + deepSetValue(openRtbRequest, 'regs.ext.gpp', gpp); + } else { + deepSetValue(openRtbRequest, 'regs.ext.gdpr', gdpr && gdpr.gdprApplies ? 1 : 0); + deepSetValue(openRtbRequest, 'user.ext.consent', gdpr && gdpr.consentString ? gdpr.consentString : ''); + } + if (gppsid) { + deepSetValue(openRtbRequest, 'regs.ext.gpp_sid', gppsid); } const uspConsent = deepAccess(bidderRequest, 'uspConsent'); - if (uspConsent) { + if (!gpp && uspConsent) { deepSetValue(openRtbRequest, 'regs.ext.us_privacy', uspConsent); } } diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index d6bf15ff9d4..2199e4d613b 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -212,7 +212,7 @@ describe('YieldmoAdapter', function () { expect(data.hasOwnProperty('h')).to.be.true; expect(data.hasOwnProperty('w')).to.be.true; expect(data.hasOwnProperty('pubcid')).to.be.true; - expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":""}'); + expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":"","gpp":"","gpp_sid":""}'); expect(data.us_privacy).to.equal(''); }); @@ -262,6 +262,24 @@ describe('YieldmoAdapter', function () { JSON.stringify({ gdprApplies: true, cmp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gpp: '', + gpp_sid: '', + }) + ); + }); + + it('should add gpp information to request if available', () => { + const gppConsent = { + 'gppString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'applicableSections': [8] + }; + const data = buildAndGetData([mockBannerBid()], 0, mockBidderRequest({gppConsent})); + expect(data.userConsent).equal( + JSON.stringify({ + gdprApplies: '', + cmp: '', + gpp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gpp_sid: [8], }) ); }); From 343fa89b59924e4e8a506822d6dc9317742852d7 Mon Sep 17 00:00:00 2001 From: Alain Cajuste <63727343+alaincajuste-msq@users.noreply.github.com> Date: Wed, 19 Apr 2023 21:26:49 +0200 Subject: [PATCH 337/375] Bidwatch Analytics Adapter: add of the referer for bid won (#9818) --- modules/bidwatchAnalyticsAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js index 0908b02de2e..b6cfa54170c 100644 --- a/modules/bidwatchAnalyticsAdapter.js +++ b/modules/bidwatchAnalyticsAdapter.js @@ -2,6 +2,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; +import { getRefererInfo } from '../src/refererDetection.js'; const analyticsType = 'endpoint'; const url = 'URL_TO_SERVER_ENDPOINT'; @@ -145,6 +146,7 @@ function handleBidWon(args) { }); } args['cpmIncrement'] = increment; + args['referer'] = encodeURIComponent(getRefererInfo().page || getRefererInfo().topmostLocation); if (typeof saveEvents.bidRequested == 'object' && saveEvents.bidRequested.length > 0 && saveEvents.bidRequested[0].gdprConsent) { args.gdpr = saveEvents.bidRequested[0].gdprConsent; } ajax(endpoint + '.bidwatch.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true}); } From e8101894b0c473fbc94b366f73f3b26d518288e8 Mon Sep 17 00:00:00 2001 From: Alain Cajuste <63727343+alaincajuste-msq@users.noreply.github.com> Date: Thu, 20 Apr 2023 14:46:49 +0200 Subject: [PATCH 338/375] Mediasquare Bid Adapter: add of the referer for onBidWon (#9817) * Mediasquare Bid Adapter: add of the referer for onBidWon * Mediasquare Bid Adapter: replay test --- modules/mediasquareBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 819ff280e35..4076cd6927a 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {Renderer} from '../src/Renderer.js'; +import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' @@ -170,7 +171,7 @@ export const spec = { */ onBidWon: function(bid) { // fires a pixel to confirm a winning bid - let params = {'pbjs': '$prebid.version$'}; + let params = { pbjs: '$prebid.version$', referer: encodeURIComponent(getRefererInfo().page || getRefererInfo().topmostLocation) }; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'requestId', 'auctionId', 'originalCpm', 'originalCurrency']; if (bid.hasOwnProperty('mediasquare')) { From 1fc1ec38e5c272e6502b87e0cba94913089ddb73 Mon Sep 17 00:00:00 2001 From: Nisar Thadathil Date: Thu, 20 Apr 2023 18:29:19 +0530 Subject: [PATCH 339/375] vidoomy adapter: sync url changed (#9834) --- modules/vidoomyBidAdapter.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index ded89861217..6cc953c9eb3 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -10,11 +10,12 @@ const BIDDER_CODE = 'vidoomy'; const GVLID = 380; const COOKIE_SYNC_FALLBACK_URLS = [ - 'https://x.bidswitch.net/sync?ssp=vidoomy', - 'https://ib.adnxs.com/getuid?https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', + 'https://x.bidswitch.net/sync?ssp=vidoomy&gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=', 'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D', 'https://cm.adform.net/cookie?redirect_url=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', - 'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}' + 'https://pixel.rubiconproject.com/exchange/sync.php?p=pbs-vidoomy&gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=', + 'https://rtb.openx.net/sync/prebid?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&r=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dopenx%26uid%3D$%7BUID%7D', + 'https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=&predirect=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D' ]; const isBidRequestValid = bid => { From de228261a0e5eae6976168299f5e41485481db9e Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Thu, 20 Apr 2023 14:46:10 -0400 Subject: [PATCH 340/375] Support VIDEO feature flag in AppNexus bid adapter (#9653) --- modules/appnexusBidAdapter.js | 210 ++-- test/spec/modules/appnexusBidAdapter_spec.js | 912 +++++++++--------- .../modules/big-richmediaBidAdapter_spec.js | 132 +-- test/spec/unit/pbjs_api_spec.js | 22 +- 4 files changed, 659 insertions(+), 617 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 8f499e1e31e..cf3763be9c8 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -333,14 +333,16 @@ export const spec = { payload.referrer_detection = refererinfo; } - const hasAdPodBid = find(bidRequests, hasAdPod); - if (hasAdPodBid) { - bidRequests.filter(hasAdPod).forEach(adPodBid => { - const adPodTags = createAdPodRequest(tags, adPodBid); - // don't need the original adpod placement because it's in adPodTags - const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); - payload.tags = [...nonPodTags, ...adPodTags]; - }); + if (FEATURES.VIDEO) { + const hasAdPodBid = find(bidRequests, hasAdPod); + if (hasAdPodBid) { + bidRequests.filter(hasAdPod).forEach(adPodBid => { + const adPodTags = createAdPodRequest(tags, adPodBid); + // don't need the original adpod placement because it's in adPodTags + const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); + payload.tags = [...nonPodTags, ...adPodTags]; + }); + } } if (bidRequests[0].userId) { @@ -653,7 +655,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); } - if (rtbBid.rtb.video) { + if (FEATURES.VIDEO && rtbBid.rtb.video) { // shared video properties used for all 3 contexts Object.assign(bid, { width: rtbBid.rtb.video.player_width, @@ -865,107 +867,111 @@ function bidToTag(bid) { } } - const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = deepAccess(bid, 'mediaTypes.video.context'); - - if (videoMediaType && context === 'adpod') { - tag.hb_source = 7; - } else { - tag.hb_source = 1; - } - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } - - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } + if (FEATURES.VIDEO) { + const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = deepAccess(bid, 'mediaTypes.video.context'); - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'context': - case 'playback_method': - let type = bid.params.video[param]; - type = (isArray(type)) ? type[0] : type; - tag.video[param] = VIDEO_MAPPING[param][type]; - break; - // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks - case 'frameworks': - break; - default: - tag.video[param] = bid.params.video[param]; - } - }); + if (videoMediaType && context === 'adpod') { + tag.hb_source = 7; + } else { + tag.hb_source = 1; + } + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } - if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { - tag['video_frameworks'] = bid.params.video.frameworks; + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; } - } - // use IAB ORTB values if the corresponding values weren't already set by bid.params.video - if (videoMediaType) { - tag.video = tag.video || {}; - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_RTB_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'minduration': - case 'maxduration': - if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; - break; - case 'skip': - if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); - break; - case 'skipafter': - if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; - break; - case 'playbackmethod': - if (typeof tag.video['playback_method'] !== 'number') { - let type = videoMediaType[param]; + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'context': + case 'playback_method': + let type = bid.params.video[param]; type = (isArray(type)) ? type[0] : type; + tag.video[param] = VIDEO_MAPPING[param][type]; + break; + // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks + case 'frameworks': + break; + default: + tag.video[param] = bid.params.video[param]; + } + }); - // we only support iab's options 1-4 at this time. - if (type >= 1 && type <= 4) { - tag.video['playback_method'] = type; - } - } - break; - case 'api': - if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { - // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) - let apiTmp = videoMediaType[param].map(val => { - let v = (val === 4) ? 5 : (val === 5) ? 4 : val; - - if (v >= 1 && v <= 5) { - return v; + if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { + tag['video_frameworks'] = bid.params.video.frameworks; + } + } + + // use IAB ORTB values if the corresponding values weren't already set by bid.params.video + if (videoMediaType) { + tag.video = tag.video || {}; + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_RTB_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'minduration': + case 'maxduration': + if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; + break; + case 'skip': + if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); + break; + case 'skipafter': + if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; + break; + case 'playbackmethod': + if (typeof tag.video['playback_method'] !== 'number') { + let type = videoMediaType[param]; + type = (isArray(type)) ? type[0] : type; + + // we only support iab's options 1-4 at this time. + if (type >= 1 && type <= 4) { + tag.video['playback_method'] = type; } - }).filter(v => v); - tag['video_frameworks'] = apiTmp; - } - break; - - case 'startdelay': - case 'placement': - const contextKey = 'context'; - if (typeof tag.video[contextKey] !== 'number') { - const placement = videoMediaType['placement']; - const startdelay = videoMediaType['startdelay']; - const context = getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); - tag.video[contextKey] = VIDEO_MAPPING[contextKey][context]; - } - break; - } - }); - } + } + break; + case 'api': + if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { + // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) + let apiTmp = videoMediaType[param].map(val => { + let v = (val === 4) ? 5 : (val === 5) ? 4 : val; + + if (v >= 1 && v <= 5) { + return v; + } + }).filter(v => v); + tag['video_frameworks'] = apiTmp; + } + break; + + case 'startdelay': + case 'placement': + const contextKey = 'context'; + if (typeof tag.video[contextKey] !== 'number') { + const placement = videoMediaType['placement']; + const startdelay = videoMediaType['startdelay']; + const context = getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); + tag.video[contextKey] = VIDEO_MAPPING[contextKey][context]; + } + break; + } + }); + } - if (bid.renderer) { - tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + } + } else { + tag.hb_source = 1; } if (bid.params.frameworks && isArray(bid.params.frameworks)) { diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 9e88e2875c7..1603c6e9397 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -257,11 +257,15 @@ describe('AppNexusAdapter', function () { transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' }]; - let types = ['banner', 'video']; + let types = ['banner']; if (FEATURES.NATIVE) { types.push('native'); } + if (FEATURES.VIDEO) { + types.push('video'); + } + types.forEach(type => { getAdUnitsStub.callsFake(function (...args) { return adUnits; @@ -290,122 +294,303 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].ad_types).to.not.exist; }); - it('should populate the ad_types array on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = { context: 'outstream' }; + if (FEATURES.VIDEO) { + it('should populate the ad_types array on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = { context: 'outstream' }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - expect(payload.tags[0].ad_types).to.deep.equal(['video']); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); - }); + it('should attach valid video params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + video: { + id: 123, + minduration: 100, + foobar: 'invalid' + } + } + } + ); - it('should attach valid video params to the tag', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('should include ORTB video values when video params were not set', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + + it('should add video property when adUnit includes a renderer', function () { + const videoData = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + }, params: { placementId: '10433394', video: { - id: 123, - minduration: 100, - foobar: 'invalid' + skippable: true, + playback_method: ['auto_play_sound_off'] } } - } - ); + }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - id: 123, - minduration: 100 - }); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); + let bidRequest1 = deepClone(bidRequests[0]); + bidRequest1 = Object.assign({}, bidRequest1, videoData, { + renderer: { + url: 'https://test.renderer.url', + render: function () { } + } + }); - it('should include ORTB video values when video params were not set', function () { - let bidRequest = deepClone(bidRequests[0]); - bidRequest.params = { - placementId: '1234235', - video: { + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + bidRequest2 = Object.assign({}, bidRequest2, videoData); + + const request = spec.buildRequests([bidRequest1, bidRequest2]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ skippable: true, - playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], - context: 'outstream' - } - }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] - } - }; + playback_method: 2, + custom_renderer_present: true + }); + expect(payload.tags[1].video).to.deep.equal({ + skippable: true, + playback_method: 2 + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + it('should duplicate adpod placements into batches and set correct maxduration', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + + // 300 / 15 = 20 total + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload1.tags[0].video.maxduration).to.equal(30); + + expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload2.tags[0].video.maxduration).to.equal(30); }); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) - }); - it('should add video property when adUnit includes a renderer', function () { - const videoData = { - mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mp4'] + it('should round down adpod placements when numbers are uneven', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 123, + durationRangeSec: [45], + } + } } - }, - params: { - placementId: '10433394', - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(2); + }); + + it('should duplicate adpod placements when requireExactDuration is set', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true, + } + } } - } - }; + ); - let bidRequest1 = deepClone(bidRequests[0]); - bidRequest1 = Object.assign({}, bidRequest1, videoData, { - renderer: { - url: 'https://test.renderer.url', - render: function () { } - } + // 20 total placements with 15 max impressions = 2 requests + const request = spec.buildRequests([bidRequest]); + expect(request.length).to.equal(2); + + // 20 spread over 2 requests = 15 in first request, 5 in second + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + // 10 placements should have max/min at 15 + // 10 placemenst should have max/min at 30 + const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); + const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); + expect(payload1tagsWith15.length).to.equal(10); + expect(payload1tagsWith30.length).to.equal(5); + + // 5 placemenst with min/max at 30 were in the first request + // so 5 remaining should be in the second + const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); + expect(payload2tagsWith30.length).to.equal(5); }); - let bidRequest2 = deepClone(bidRequests[0]); - bidRequest2.adUnitCode = 'adUnit_code_2'; - bidRequest2 = Object.assign({}, bidRequest2, videoData); + it('should set durations for placements when requireExactDuration is set and numbers are uneven', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 105, + durationRangeSec: [15, 30, 60], + requireExactDuration: true, + } + } + } + ); - const request = spec.buildRequests([bidRequest1, bidRequest2]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - skippable: true, - playback_method: 2, - custom_renderer_present: true + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(7); + + const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); + const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); + const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); + expect(tagsWith15.length).to.equal(3); + expect(tagsWith30.length).to.equal(3); + expect(tagsWith60.length).to.equal(1); }); - expect(payload.tags[1].video).to.deep.equal({ - skippable: true, - playback_method: 2 + + it('should break adpod request into batches', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 225, + durationRangeSec: [5], + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + const payload3 = JSON.parse(request[2].data); + + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(15); + expect(payload3.tags.length).to.equal(15); + }); + + it('should contain hb_source value for adpod', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + const request = spec.buildRequests([bidRequest])[0]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(7); }); + } // VIDEO + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); }); it('should attach valid user params to the tag', function () { @@ -486,185 +671,6 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].reserve).to.exist.and.to.equal(3); }); - it('should duplicate adpod placements into batches and set correct maxduration', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - - // 300 / 15 = 20 total - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload1.tags[0].video.maxduration).to.equal(30); - - expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload2.tags[0].video.maxduration).to.equal(30); - }); - - it('should round down adpod placements when numbers are uneven', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 123, - durationRangeSec: [45], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(2); - }); - - it('should duplicate adpod placements when requireExactDuration is set', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - requireExactDuration: true, - } - } - } - ); - - // 20 total placements with 15 max impressions = 2 requests - const request = spec.buildRequests([bidRequest]); - expect(request.length).to.equal(2); - - // 20 spread over 2 requests = 15 in first request, 5 in second - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - // 10 placements should have max/min at 15 - // 10 placemenst should have max/min at 30 - const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); - const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); - expect(payload1tagsWith15.length).to.equal(10); - expect(payload1tagsWith30.length).to.equal(5); - - // 5 placemenst with min/max at 30 were in the first request - // so 5 remaining should be in the second - const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); - expect(payload2tagsWith30.length).to.equal(5); - }); - - it('should set durations for placements when requireExactDuration is set and numbers are uneven', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 105, - durationRangeSec: [15, 30, 60], - requireExactDuration: true, - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(7); - - const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); - const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); - const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); - expect(tagsWith15.length).to.equal(3); - expect(tagsWith30.length).to.equal(3); - expect(tagsWith60.length).to.equal(1); - }); - - it('should break adpod request into batches', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 225, - durationRangeSec: [5], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - const payload3 = JSON.parse(request[2].data); - - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(15); - expect(payload3.tags.length).to.equal(15); - }); - - it('should contain hb_source value for adpod', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - const request = spec.buildRequests([bidRequest])[0]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].hb_source).to.deep.equal(7); - }); - it('should contain hb_source value for other media', function () { let bidRequest = Object.assign({}, bidRequests[0], @@ -1392,27 +1398,31 @@ describe('AppNexusAdapter', function () { }); it('should populate iab_support object at the root level if omid support is detected', function () { - // with bid.params.frameworks - let bidRequest_A = Object.assign({}, bidRequests[0], { - params: { - frameworks: [1, 2, 5, 6], - video: { - frameworks: [1, 2, 5, 6] + let request, payload; + + if (FEATURES.VIDEO) { + // with bid.params.frameworks + let bidRequest_A = Object.assign({}, bidRequests[0], { + params: { + frameworks: [1, 2, 5, 6], + video: { + frameworks: [1, 2, 5, 6] + } } - } - }); - let request = spec.buildRequests([bidRequest_A]); - let payload = JSON.parse(request.data); - expect(payload.iab_support).to.be.an('object'); - expect(payload.iab_support).to.deep.equal({ - omidpn: 'Appnexus', - omidpv: '$prebid.version$' - }); - expect(payload.tags[0].banner_frameworks).to.be.an('array'); - expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video_frameworks).to.be.an('array'); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video.frameworks).to.not.exist; + }); + request = spec.buildRequests([bidRequest_A]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.be.an('object'); + expect(payload.iab_support).to.deep.equal({ + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + }); + expect(payload.tags[0].banner_frameworks).to.be.an('array'); + expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video_frameworks).to.be.an('array'); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video.frameworks).to.not.exist; + } // without bid.params.frameworks const bidRequest_B = Object.assign({}, bidRequests[0]); @@ -1422,19 +1432,21 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].banner_frameworks).to.not.exist; expect(payload.tags[0].video_frameworks).to.not.exist; - // with video.frameworks but it is not an array - const bidRequest_C = Object.assign({}, bidRequests[0], { - params: { - video: { - frameworks: "'1', '2', '3', '6'" + if (FEATURES.VIDEO) { + // with video.frameworks but it is not an array + const bidRequest_C = Object.assign({}, bidRequests[0], { + params: { + video: { + frameworks: "'1', '2', '3', '6'" + } } - } - }); - request = spec.buildRequests([bidRequest_C]); - payload = JSON.parse(request.data); - expect(payload.iab_support).to.not.exist; - expect(payload.tags[0].banner_frameworks).to.not.exist; - expect(payload.tags[0].video_frameworks).to.not.exist; + }); + request = spec.buildRequests([bidRequest_C]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + } }); }) @@ -1591,116 +1603,118 @@ describe('AppNexusAdapter', function () { expect(result.length).to.equal(0); }); - it('handles outstream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' + if (FEATURES.VIDEO) { + it('handles outstream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } } - } - }] - } + }] + } - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastXml'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); - it('handles instream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/vid' - } - }, - 'javascriptTrackers': '' + it('handles instream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/vid' + } + }, + 'javascriptTrackers': '' + }] }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'instream' + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } } - } - }] - } + }] + } - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); - it('handles adpod responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'brand_category_id': 10, - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/adpod', - 'duration_ms': 30000, + it('handles adpod responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'brand_category_id': 10, + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/adpod', + 'duration_ms': 30000, + } + }, + 'viewability': { + 'config': '' } - }, - 'viewability': { - 'config': '' - } + }] }] - }] - }; + }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } } - } - }] - }; - bfStub.returns('1'); + }] + }; + bfStub.returns('1'); - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0].video.context).to.equal('adpod'); - expect(result[0].video.durationSeconds).to.equal(30); - }); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0].video.context).to.equal('adpod'); + expect(result[0].video.durationSeconds).to.equal(30); + }); + } if (FEATURES.NATIVE) { it('handles native responses', function () { @@ -1758,59 +1772,61 @@ describe('AppNexusAdapter', function () { }); } - it('supports configuring outstream renderers', function () { - const outstreamResponse = deepClone(response); - outstreamResponse.tags[0].ads[0].rtb.video = {}; - outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + if (FEATURES.VIDEO) { + it('supports configuring outstream renderers', function () { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - renderer: { - options: { - adText: 'configured' - } - }, - mediaTypes: { - video: { - context: 'outstream' + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + renderer: { + options: { + adText: 'configured' + } + }, + mediaTypes: { + video: { + context: 'outstream' + } } - } - }] - }; + }] + }; - const result = spec.interpretResponse({ body: outstreamResponse }, { bidderRequest }); - expect(result[0].renderer.config).to.deep.equal( - bidderRequest.bids[0].renderer.options - ); - }); + const result = spec.interpretResponse({ body: outstreamResponse }, { bidderRequest }); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); - it('should add deal_priority and deal_code', function () { - let responseWithDeal = deepClone(response); - responseWithDeal.tags[0].ads[0].ad_type = 'video'; - responseWithDeal.tags[0].ads[0].deal_priority = 5; - responseWithDeal.tags[0].ads[0].deal_code = '123'; - responseWithDeal.tags[0].ads[0].rtb.video = { - duration_ms: 1500, - player_width: 640, - player_height: 340, - }; + it('should add deal_priority and deal_code', function () { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].ad_type = 'video'; + responseWithDeal.tags[0].ads[0].deal_priority = 5; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + responseWithDeal.tags[0].ads[0].rtb.video = { + duration_ms: 1500, + player_width: 640, + player_height: 340, + }; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } } - } - }] - } - let result = spec.interpretResponse({ body: responseWithDeal }, { bidderRequest }); - expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); - expect(result[0].video.dealTier).to.equal(5); - }); + }] + } + let result = spec.interpretResponse({ body: responseWithDeal }, { bidderRequest }); + expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + expect(result[0].video.dealTier).to.equal(5); + }); + } it('should add advertiser id', function () { let responseAdvertiserId = deepClone(response); diff --git a/test/spec/modules/big-richmediaBidAdapter_spec.js b/test/spec/modules/big-richmediaBidAdapter_spec.js index b2ee0725d49..f01e261ef9f 100644 --- a/test/spec/modules/big-richmediaBidAdapter_spec.js +++ b/test/spec/modules/big-richmediaBidAdapter_spec.js @@ -92,40 +92,42 @@ describe('bigRichMediaAdapterTests', function () { expect(payload.tags[0].sizes).to.have.lengthOf(3); }); - it('should build video bid request', function() { - const bidRequest = deepClone(bidRequests[0]); - bidRequest.params = { - placementId: '1234235', - video: { - skippable: true, - playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], - context: 'outstream', - format: 'sticky-top' - } - }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] - } - }; + if (FEATURES.VIDEO) { + it('should build video bid request', function() { + const bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream', + format: 'sticky-top' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) }); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) - }); + } }); describe('interpretResponse', function () { @@ -227,42 +229,44 @@ describe('bigRichMediaAdapterTests', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('handles outstream video responses', function () { - const response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' + if (FEATURES.VIDEO) { + it('handles outstream video responses', function () { + const response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] }] - }] - }; - const bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' + }; + const bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } } - } - }] - } + }] + } - const result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).not.to.have.property('vastXml'); - expect(result[0]).not.to.have.property('vastUrl'); - expect(result[0]).to.have.property('width', 1); - expect(result[0]).to.have.property('height', 1); - expect(result[0]).to.have.property('mediaType', 'banner'); - }); + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).not.to.have.property('vastXml'); + expect(result[0]).not.to.have.property('vastUrl'); + expect(result[0]).to.have.property('width', 1); + expect(result[0]).to.have.property('height', 1); + expect(result[0]).to.have.property('mediaType', 'banner'); + }); + } }); describe('getUserSyncs', function() { diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index b069b13fc37..820d87ef49c 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -888,16 +888,32 @@ describe('Unit: Prebid Module', function () { it('should only apply price granularity if bid media type matches', function () { initTestConfig({ - adUnits: [ createAdUnit('div-gpt-ad-1460505748561-0', 'video') ], + adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] }); - response = videoResponse; + response = bannerResponse; response.tags[0].ads[0].cpm = 3.4288; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); + + if (FEATURES.VIDEO) { + ajaxStub.restore(); + + initTestConfig({ + adUnits: [createAdUnit('div-gpt-ad-1460505748561-0', 'video')], + adUnitCodes: ['div-gpt-ad-1460505748561-0'] + }); + + response = videoResponse; + response.tags[0].ads[0].cpm = 3.4288; + + auction.callBids(cbTimeout); + let bidTargeting = targeting.getAllTargeting(); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); + } }); }); From 55b2e08c8b1794d534ae2a51d69f0f9e21896d2b Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Fri, 21 Apr 2023 14:13:33 -0700 Subject: [PATCH 341/375] ortbConverter: support video.plcmt (#9840) --- libraries/ortbConverter/processors/video.js | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js index 5fe411e6bcf..c38231d9002 100644 --- a/libraries/ortbConverter/processors/video.js +++ b/libraries/ortbConverter/processors/video.js @@ -6,6 +6,7 @@ import {sizesToFormat} from '../lib/sizes.js'; const ORTB_VIDEO_PARAMS = new Set([ 'pos', 'placement', + 'plcmt', 'api', 'mimes', 'protocols', From 608dedaa4cbf9daa10d432a04d76f07e8062d10a Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Fri, 21 Apr 2023 14:14:27 -0700 Subject: [PATCH 342/375] Documentation: Add FLEDGE aka Protected Audience API examples (#9839) * Add FLEDGE aka Protected Audience API examples * Remove alias from pbs example --- integrationExamples/gpt/fledge_example.html | 103 ++++++++++++++++ .../gpt/prebidServer_fledge_example.html | 111 ++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 integrationExamples/gpt/fledge_example.html create mode 100644 integrationExamples/gpt/prebidServer_fledge_example.html diff --git a/integrationExamples/gpt/fledge_example.html b/integrationExamples/gpt/fledge_example.html new file mode 100644 index 00000000000..5059e03daef --- /dev/null +++ b/integrationExamples/gpt/fledge_example.html @@ -0,0 +1,103 @@ + + + + + + + + + +

Prebid.js FLEDGE+GPT Example

+ +
Div-1
+
+ +
+ + diff --git a/integrationExamples/gpt/prebidServer_fledge_example.html b/integrationExamples/gpt/prebidServer_fledge_example.html new file mode 100644 index 00000000000..8523c0f2920 --- /dev/null +++ b/integrationExamples/gpt/prebidServer_fledge_example.html @@ -0,0 +1,111 @@ + + + + + + + + + +

Prebid.js FLEDGE+GPT Example

+ +
Div-1
+
+ +
+ + From 9c3196f3f5ee85cdf8de9bf8eb60bfaf98cb859d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 21 Apr 2023 14:16:46 -0700 Subject: [PATCH 343/375] Core: fix spurious warnings on `mergeConfig` (#9704) * Core: fix spurious warnings on `mergeConfig` * Fix sendAllBid config name --- src/config.js | 205 +++++++----------- .../spec/modules/ooloAnalyticsAdapter_spec.js | 2 +- 2 files changed, 79 insertions(+), 128 deletions(-) diff --git a/src/config.js b/src/config.js index 0922a2a2bea..48909d677e9 100644 --- a/src/config.js +++ b/src/config.js @@ -66,158 +66,109 @@ export function newConfig() { function resetConfig() { defaults = {}; - let newConfig = { - // `debug` is equivalent to legacy `pbjs.logging` property - _debug: DEFAULT_DEBUG, - get debug() { - return this._debug; - }, - set debug(val) { - this._debug = val; - }, - // default timeout for all bids - _bidderTimeout: DEFAULT_BIDDER_TIMEOUT, - get bidderTimeout() { - return this._bidderTimeout; - }, - set bidderTimeout(val) { - this._bidderTimeout = val; - }, + function getProp(name) { + return props[name].val; + } - _publisherDomain: null, - get publisherDomain() { - return this._publisherDomain; - }, - set publisherDomain(val) { - logWarn('publisherDomain is deprecated and has no effect since v7 - use pageUrl instead') - this._publisherDomain = val; - }, + function setProp(name, val) { + props[name].val = val; + } - // calls existing function which may be moved after deprecation - _priceGranularity: GRANULARITY_OPTIONS.MEDIUM, - set priceGranularity(val) { - if (validatePriceGranularity(val)) { - if (typeof val === 'string') { - this._priceGranularity = (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM; - } else if (isPlainObject(val)) { - this._customPriceBucket = val; - this._priceGranularity = GRANULARITY_OPTIONS.CUSTOM; - logMessage('Using custom price granularity'); + const props = { + publisherDomain: { + set(val) { + if (val != null) { + logWarn('publisherDomain is deprecated and has no effect since v7 - use pageUrl instead') } + setProp('publisherDomain', val); } }, - get priceGranularity() { - return this._priceGranularity; - }, - - _customPriceBucket: {}, - get customPriceBucket() { - return this._customPriceBucket; - }, - - /** - * mediaTypePriceGranularity - * @type {MediaTypePriceGranularity} - */ - _mediaTypePriceGranularity: {}, - - get mediaTypePriceGranularity() { - return this._mediaTypePriceGranularity; - }, - set mediaTypePriceGranularity(val) { - this._mediaTypePriceGranularity = Object.keys(val).reduce((aggregate, item) => { - if (validatePriceGranularity(val[item])) { + priceGranularity: { + val: GRANULARITY_OPTIONS.MEDIUM, + set(val) { + if (validatePriceGranularity(val)) { if (typeof val === 'string') { - aggregate[item] = (hasGranularity(val[item])) ? val[item] : this._priceGranularity; + setProp('priceGranularity', (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM); } else if (isPlainObject(val)) { - aggregate[item] = val[item]; - logMessage(`Using custom price granularity for ${item}`); + setProp('customPriceBucket', val); + setProp('priceGranularity', GRANULARITY_OPTIONS.CUSTOM) + logMessage('Using custom price granularity'); } - } else { - logWarn(`Invalid price granularity for media type: ${item}`); } - return aggregate; - }, {}); - }, - - _sendAllBids: DEFAULT_ENABLE_SEND_ALL_BIDS, - get enableSendAllBids() { - return this._sendAllBids; + } }, - set enableSendAllBids(val) { - this._sendAllBids = val; + customPriceBucket: { + val: {}, + set() {} }, - - _useBidCache: DEFAULT_BID_CACHE, - get useBidCache() { - return this._useBidCache; + mediaTypePriceGranularity: { + val: {}, + set(val) { + val != null && setProp('mediaTypePriceGranularity', Object.keys(val).reduce((aggregate, item) => { + if (validatePriceGranularity(val[item])) { + if (typeof val === 'string') { + aggregate[item] = (hasGranularity(val[item])) ? val[item] : getProp('priceGranularity'); + } else if (isPlainObject(val)) { + aggregate[item] = val[item]; + logMessage(`Using custom price granularity for ${item}`); + } + } else { + logWarn(`Invalid price granularity for media type: ${item}`); + } + return aggregate; + }, {})); + } }, - set useBidCache(val) { - this._useBidCache = val; + bidderSequence: { + val: DEFAULT_BIDDER_SEQUENCE, + set(val) { + if (VALID_ORDERS[val]) { + setProp('bidderSequence', val); + } else { + logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); + } + } }, + auctionOptions: { + val: {}, + set(val) { + if (validateauctionOptions(val)) { + setProp('auctionOptions', val); + } + } + } + } + let newConfig = { + // `debug` is equivalent to legacy `pbjs.logging` property + debug: DEFAULT_DEBUG, + bidderTimeout: DEFAULT_BIDDER_TIMEOUT, + enableSendAllBids: DEFAULT_ENABLE_SEND_ALL_BIDS, + useBidCache: DEFAULT_BID_CACHE, /** * deviceAccess set to false will disable setCookie, getCookie, hasLocalStorage * @type {boolean} */ - _deviceAccess: DEFAULT_DEVICE_ACCESS, - get deviceAccess() { - return this._deviceAccess; - }, - set deviceAccess(val) { - this._deviceAccess = val; - }, - - _bidderSequence: DEFAULT_BIDDER_SEQUENCE, - get bidderSequence() { - return this._bidderSequence; - }, - set bidderSequence(val) { - if (VALID_ORDERS[val]) { - this._bidderSequence = val; - } else { - logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); - } - }, + deviceAccess: DEFAULT_DEVICE_ACCESS, // timeout buffer to adjust for bidder CDN latency - _timeoutBuffer: DEFAULT_TIMEOUTBUFFER, - get timeoutBuffer() { - return this._timeoutBuffer; - }, - set timeoutBuffer(val) { - this._timeoutBuffer = val; - }, - - _disableAjaxTimeout: DEFAULT_DISABLE_AJAX_TIMEOUT, - get disableAjaxTimeout() { - return this._disableAjaxTimeout; - }, - set disableAjaxTimeout(val) { - this._disableAjaxTimeout = val; - }, + timeoutBuffer: DEFAULT_TIMEOUTBUFFER, + disableAjaxTimeout: DEFAULT_DISABLE_AJAX_TIMEOUT, // default max nested iframes for referer detection - _maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, - get maxNestedIframes() { - return this._maxNestedIframes; - }, - set maxNestedIframes(val) { - this._maxNestedIframes = val; - }, - - _auctionOptions: {}, - get auctionOptions() { - return this._auctionOptions; - }, - set auctionOptions(val) { - if (validateauctionOptions(val)) { - this._auctionOptions = val; - } - }, + maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, }; + Object.defineProperties(newConfig, + Object.fromEntries(Object.entries(props) + .map(([k, def]) => [k, Object.assign({ + get: getProp.bind(null, k), + set: setProp.bind(null, k), + enumerable: true, + }, def)])) + ); + if (config) { callSubscribers( Object.keys(config).reduce((memo, topic) => { diff --git a/test/spec/modules/ooloAnalyticsAdapter_spec.js b/test/spec/modules/ooloAnalyticsAdapter_spec.js index a6030e972ce..2515c713b14 100644 --- a/test/spec/modules/ooloAnalyticsAdapter_spec.js +++ b/test/spec/modules/ooloAnalyticsAdapter_spec.js @@ -194,7 +194,7 @@ describe('oolo Prebid Analytic', () => { }) const conf = {} - const pbjsConfig = config.getConfig() + const pbjsConfig = JSON.parse(JSON.stringify(config.getConfig())) Object.keys(pbjsConfig).forEach(key => { if (key[0] !== '_') { From 24da871933e81a019bd673aa0b984069a378e748 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 21 Apr 2023 22:46:09 +0000 Subject: [PATCH 344/375] Prebid 7.46.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 54442c348e0..163e686e15b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.46.0-pre", + "version": "7.46.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 7ebcb655ed7..6652a702019 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.46.0-pre", + "version": "7.46.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 21b9bbe7f948a539f918c425c71d57e17a071626 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 21 Apr 2023 22:46:10 +0000 Subject: [PATCH 345/375] Increment version to 7.47.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 163e686e15b..7baf4ab3094 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.46.0", + "version": "7.47.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6652a702019..df9a5551c50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.46.0", + "version": "7.47.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From dc7b3c634c68cd0c1463072f33e1811b370c2364 Mon Sep 17 00:00:00 2001 From: Nima Sarayan <36764654+nimasrn@users.noreply.github.com> Date: Mon, 24 Apr 2023 17:28:28 +0330 Subject: [PATCH 346/375] vidoomy adapter: added block module (#9825) --- modules/vidoomyBidAdapter.js | 25 ++++++++--- modules/vidoomyBidAdapter.md | 14 +++++- test/spec/modules/vidoomyBidAdapter_spec.js | 49 +++++++++++++++++++++ 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index 6cc953c9eb3..c9ac9fae0f4 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -1,9 +1,9 @@ -import { logError, deepAccess, parseSizesInput } from '../src/utils.js'; +import {deepAccess, logError, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import { Renderer } from '../src/Renderer.js'; -import { INSTREAM, OUTSTREAM } from '../src/video.js'; +import {Renderer} from '../src/Renderer.js'; +import {INSTREAM, OUTSTREAM} from '../src/video.js'; const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; const BIDDER_CODE = 'vidoomy'; @@ -129,6 +129,14 @@ const buildRequests = (validBidRequests, bidderRequest) => { const bidfloor = deepAccess(bid, `params.bidfloor`, 0); const floor = getBidFloor(bid, adType, sizes, bidfloor); + const ortb2 = bidderRequest.ortb2 || { + bcat: [], + badv: [], + bapp: [], + btype: [], + battr: [] + }; + let eids; const userEids = deepAccess(bid, 'userIdAsEids'); if (Array.isArray(userEids) && userEids.length > 0) { @@ -155,7 +163,12 @@ const buildRequests = (validBidRequests, bidderRequest) => { sp: encodeURIComponent(bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation), usp: bidderRequest.uspConsent || '', coppa: !!config.getConfig('coppa'), - videoContext: videoContext || '' + videoContext: videoContext || '', + bcat: ortb2.bcat || bid.params.bcat || [], + badv: ortb2.badv || bid.params.badv || [], + bapp: ortb2.bapp || bid.params.bapp || [], + btype: ortb2.btype || bid.params.btype || [], + battr: ortb2.battr || bid.params.battr || [] }; if (bidderRequest.gdprConsent) { @@ -261,7 +274,7 @@ const interpretResponse = (serverResponse, bidRequest) => { } }; -function getUserSyncs (syncOptions, responses, gdprConsent, uspConsent) { +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { const pixelType = syncOptions.pixelEnabled ? 'image' : 'iframe'; const urls = deepAccess(responses, '0.body.pixels') || COOKIE_SYNC_FALLBACK_URLS; @@ -288,7 +301,7 @@ export const spec = { registerBidder(spec); -function getDomainWithoutSubdomain (hostname) { +function getDomainWithoutSubdomain(hostname) { const parts = hostname.split('.'); const newParts = []; for (let i = parts.length - 1; i >= 0; i--) { diff --git a/modules/vidoomyBidAdapter.md b/modules/vidoomyBidAdapter.md index d91ace5a7b9..b4095606d9b 100644 --- a/modules/vidoomyBidAdapter.md +++ b/modules/vidoomyBidAdapter.md @@ -27,7 +27,12 @@ var adUnits = [ params: { id: '123123', pid: '123123', - bidfloor: 0.5 // This is optional + bidfloor: 0.5, // This is optional + bcat: ['IAB1-1'], // Optional - default is [] + badv: ['example.com'], // Optional - default is [] + bapp: ['app.com'], // Optional - default is [] + btype: [1, 2, 3], // Optional - default is [] + battr: [1, 2, 3] // Optional - default is [] } } ] @@ -52,7 +57,12 @@ var adUnits = [ params: { id: '123123', pid: '123123', - bidfloor: 0.5 // This is optional + bidfloor: 0.5, // This is optional + bcat: ['IAB1-1'], // Optional - default is [] + badv: ['example.com'], // Optional - default is [] + bapp: ['app.com'], // Optional - default is [] + btype: [1, 2, 3], // Optional - default is [] + battr: [1, 2, 3] // Optional - default is [] } } ] diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 2886800d001..38fa872e6b8 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -219,6 +219,55 @@ describe('vidoomyBidAdapter', function() { expect(bidRequest.data.bidfloor).to.equal(bidfloor); }); }); + + describe('badv, bcat, bapp, btype, battr', function () { + const bidderRequestNew = { + ...bidderRequest, + bcat: ['EX1', 'EX2', 'EX3'], + badv: ['site.com'], + bapp: ['app.com'], + btype: [1, 2, 3], + battr: [1, 2, 3] + } + const request = spec.buildRequests(bidRequests, bidderRequestNew); + it('should have badv, bcat, bapp, btype, battr in request', function () { + expect(request[0].data).to.include.any.keys('badv'); + expect(request[0].data).to.include.any.keys('bcat'); + expect(request[0].data).to.include.any.keys('bapp'); + expect(request[0].data).to.include.any.keys('btype'); + expect(request[0].data).to.include.any.keys('battr'); + }) + + it('should have equal badv, bcat, bapp, btype, battr in request', function () { + expect(request[0].badv).to.deep.equal(bidderRequest.refererInfo.badv); + expect(request[0].bcat).to.deep.equal(bidderRequest.refererInfo.bcat); + expect(request[0].bapp).to.deep.equal(bidderRequest.refererInfo.bapp); + expect(request[0].btype).to.deep.equal(bidderRequest.refererInfo.btype); + expect(request[0].battr).to.deep.equal(bidderRequest.refererInfo.battr); + }) + }) + + describe('first party data', function () { + const bidderRequest2 = { + ...bidderRequest, + ortb2: { + bcat: ['EX1', 'EX2', 'EX3'], + badv: ['site.com'], + bapp: ['app.com'], + btype: [1, 2, 3], + battr: [1, 2, 3] + } + } + const request = spec.buildRequests(bidRequests, bidderRequest2); + + it('should have badv, bcat, bapp, btype, battr in request and equal to bidderRequest.ortb2', function () { + expect(request[0].data.bcat).to.deep.equal(bidderRequest2.ortb2.bcat) + expect(request[0].data.badv).to.deep.equal(bidderRequest2.ortb2.badv) + expect(request[0].data.bapp).to.deep.equal(bidderRequest2.ortb2.bapp); + expect(request[0].data.btype).to.deep.equal(bidderRequest2.ortb2.btype); + expect(request[0].data.battr).to.deep.equal(bidderRequest2.ortb2.battr); + }); + }); }); describe('interpretResponse', function () { From ecbb2235dd1ced8fc19f787cff9fac70d2345d8b Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Mon, 24 Apr 2023 10:04:04 -0700 Subject: [PATCH 347/375] Sending empty array instead of string. (#9846) --- modules/yieldmoBidAdapter.js | 4 ++-- test/spec/modules/yieldmoBidAdapter_spec.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 508b94853e4..bef453cb4ad 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -83,7 +83,7 @@ export const spec = { gdprApplies: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', cmp: deepAccess(bidderRequest, 'gdprConsent.consentString') || '', gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || '', - gpp_sid: deepAccess(bidderRequest, 'gppConsent.applicableSections') || '' + gpp_sid: deepAccess(bidderRequest, 'gppConsent.applicableSections') || [] }), us_privacy: deepAccess(bidderRequest, 'uspConsent') || '' }; @@ -525,7 +525,7 @@ function populateOpenRtbGdpr(openRtbRequest, bidderRequest) { deepSetValue(openRtbRequest, 'regs.ext.gdpr', gdpr && gdpr.gdprApplies ? 1 : 0); deepSetValue(openRtbRequest, 'user.ext.consent', gdpr && gdpr.consentString ? gdpr.consentString : ''); } - if (gppsid) { + if (gppsid && gppsid.length > 0) { deepSetValue(openRtbRequest, 'regs.ext.gpp_sid', gppsid); } const uspConsent = deepAccess(bidderRequest, 'uspConsent'); diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 2199e4d613b..3706f770da8 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -212,7 +212,7 @@ describe('YieldmoAdapter', function () { expect(data.hasOwnProperty('h')).to.be.true; expect(data.hasOwnProperty('w')).to.be.true; expect(data.hasOwnProperty('pubcid')).to.be.true; - expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":"","gpp":"","gpp_sid":""}'); + expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":"","gpp":"","gpp_sid":[]}'); expect(data.us_privacy).to.equal(''); }); @@ -263,7 +263,7 @@ describe('YieldmoAdapter', function () { gdprApplies: true, cmp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', gpp: '', - gpp_sid: '', + gpp_sid: [], }) ); }); From 5ffd88a7b7c5a5249460e1c83681a1187299250b Mon Sep 17 00:00:00 2001 From: kmr <130826954+kmrnpu@users.noreply.github.com> Date: Wed, 26 Apr 2023 01:08:11 +0900 Subject: [PATCH 348/375] RTB house Bid Adapter: fix encoding bug for bid response for native ads (#9850) * RTB hose Bid Adapter: encode URI corecctly using encodeURI instead of encodeURIComponent --- modules/rtbhouseBidAdapter.js | 6 +++--- test/spec/modules/rtbhouseBidAdapter_spec.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 5c3d430aadf..c4264d2920d 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -479,7 +479,7 @@ function interpretNativeBid(serverBid) { function interpretNativeAd(adm) { const native = JSON.parse(adm).native; const result = { - clickUrl: encodeURIComponent(native.link.url), + clickUrl: encodeURI(native.link.url), impressionTrackers: native.imptrackers }; native.assets.forEach(asset => { @@ -489,14 +489,14 @@ function interpretNativeAd(adm) { break; case OPENRTB.NATIVE.ASSET_ID.IMAGE: result.image = { - url: encodeURIComponent(asset.img.url), + url: encodeURI(asset.img.url), width: asset.img.w, height: asset.img.h }; break; case OPENRTB.NATIVE.ASSET_ID.ICON: result.icon = { - url: encodeURIComponent(asset.img.url), + url: encodeURI(asset.img.url), width: asset.img.w, height: asset.img.h }; diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 792d3ab0a9e..70011cbf244 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -631,10 +631,10 @@ describe('RTBHouseAdapter', () => { expect(bids[0].meta.advertiserDomains).to.deep.equal(['rtbhouse.com']); expect(bids[0].native).to.deep.equal({ title: 'Title text', - clickUrl: encodeURIComponent('https://example.com'), + clickUrl: encodeURI('https://example.com'), impressionTrackers: ['https://example.com/imptracker'], image: { - url: encodeURIComponent('https://example.com/image.jpg'), + url: encodeURI('https://example.com/image.jpg'), width: 150, height: 50 }, From cff43642cb30546b6dbf714e3f32045c150cce73 Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Tue, 25 Apr 2023 19:26:26 +0300 Subject: [PATCH 349/375] Missena Bid Adapter: fix alias (#9849) --- modules/missenaBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index f461440c5a1..33fa6857e85 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -8,7 +8,7 @@ const EVENTS_DOMAIN = 'events.missena.io'; const EVENTS_DOMAIN_DEV = 'events.staging.missena.xyz'; export const spec = { - aliases: [BIDDER_CODE], + aliases: ['msna'], code: BIDDER_CODE, gvlid: 687, supportedMediaTypes: [BANNER], From d0bb0f58bd6210b05be34cbfcec7ef7e8ae08517 Mon Sep 17 00:00:00 2001 From: EngageMediaHB <130045707+EngageMediaHB@users.noreply.github.com> Date: Tue, 25 Apr 2023 23:08:11 +0300 Subject: [PATCH 350/375] add EMTV adapter (#9783) --- modules/emtvBidAdapter.js | 211 ++++++++++++ modules/emtvBidAdapter.md | 79 +++++ test/spec/modules/emtvBidAdapter_spec.js | 400 +++++++++++++++++++++++ 3 files changed, 690 insertions(+) create mode 100644 modules/emtvBidAdapter.js create mode 100644 modules/emtvBidAdapter.md create mode 100644 test/spec/modules/emtvBidAdapter_spec.js diff --git a/modules/emtvBidAdapter.js b/modules/emtvBidAdapter.js new file mode 100644 index 00000000000..7a2fdae8adf --- /dev/null +++ b/modules/emtvBidAdapter.js @@ -0,0 +1,211 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'emtv'; +const AD_URL = 'https://us-east-ep.engagemedia.tv/pbjs'; +const SYNC_URL = 'https://cs.engagemedia.tv'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/emtvBidAdapter.md b/modules/emtvBidAdapter.md new file mode 100644 index 00000000000..ed58ca43294 --- /dev/null +++ b/modules/emtvBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: EMTV Bidder Adapter +Module Type: EMTV Bidder Adapter +Maintainer: support@engagemedia.tv +``` + +# Description + +Connects to EMTV exchange for bids. +EMTV bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'emtv', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'emtv', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'emtv', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/emtvBidAdapter_spec.js b/test/spec/modules/emtvBidAdapter_spec.js new file mode 100644 index 00000000000..4f95a0cc094 --- /dev/null +++ b/test/spec/modules/emtvBidAdapter_spec.js @@ -0,0 +1,400 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/emtvBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'emtv' +const adUrl = 'https://us-east-ep.engagemedia.tv/pbjs'; +const syncUrl = 'https://cs.engagemedia.tv'; + +describe('EMTVBidAdapter', 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`) + }); + }); +}); From bff1f412f7e91e6eccc87b0779c8dce26288b0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Wed, 26 Apr 2023 15:01:55 +0200 Subject: [PATCH 351/375] Criteo Id Module: ensure all kind of privacy strings are sent to backend (#9845) Also adding missing gdpr applies flag --- modules/criteoIdSystem.js | 41 ++++++--- test/spec/modules/criteoIdSystem_spec.js | 108 +++++++++++++++++++++-- 2 files changed, 126 insertions(+), 23 deletions(-) diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index e379fde0c5f..b48612203f8 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -9,12 +9,13 @@ import { timestamp, parseUrl, triggerPixel, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { submodule } from '../src/hook.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; const gvlid = 91; const bidderCode = 'criteo'; -export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: bidderCode}); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: bidderCode }); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; @@ -77,17 +78,33 @@ function getCriteoDataFromAllStorages() { } } -function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) { - const url = 'https://gum.criteo.com/sid/json?origin=prebid' + +function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent) { + let url = 'https://gum.criteo.com/sid/json?origin=prebid' + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + `${dnaBundle ? '&info=' + encodeURIComponent(dnaBundle) : ''}` + - `${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` + `${areCookiesWriteable ? '&cw=1' : ''}` + `${isPublishertagPresent ? '&pbt=1' : ''}` + `${isLocalStorageWritable ? '&lsw=1' : ''}`; + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString) { + url = url + `&us_privacy=${encodeURIComponent(usPrivacyString)}`; + } + + const gdprConsent = gdprDataHandler.getConsentData() + if (gdprConsent) { + url = url + `${gdprConsent.consentString ? '&gdprString=' + encodeURIComponent(gdprConsent.consentString) : ''}`; + url = url + `&gdpr=${gdprConsent.gdprApplies === true ? 1 : 0}`; + } + + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + url = url + `${gppConsent.gppString ? '&gpp=' + encodeURIComponent(gppConsent.gppString) : ''}`; + url = url + `${gppConsent.applicableSections ? '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections) : ''}`; + } + return url; } @@ -116,7 +133,7 @@ function callSyncPixel(domain, pixel) { } } -function callCriteoUserSync(parsedCriteoData, gdprString, callback) { +function callCriteoUserSync(parsedCriteoData, callback) { const cw = storage.cookiesAreEnabled(); const lsw = storage.localStorageIsEnabled(); const topUrl = extractProtocolHost(getRefererInfo().page); @@ -131,8 +148,7 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) { parsedCriteoData.dnaBundle, cw, lsw, - isPublishertagPresent, - gdprString + isPublishertagPresent ); const callbacks = { @@ -191,13 +207,10 @@ export const criteoIdSubmodule = { * @param {ConsentData} [consentData] * @returns {{id: {criteoId: string} | undefined}}} */ - getId(config, consentData) { - const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; - const gdprConsentString = hasGdprData ? consentData.consentString : undefined; - + getId() { let localData = getCriteoDataFromAllStorages(); - const result = (callback) => callCriteoUserSync(localData, gdprConsentString, callback); + const result = (callback) => callCriteoUserSync(localData, callback); return { id: localData.bidId ? { criteoId: localData.bidId } : undefined, diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 508a22e8baa..aaf63873d93 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -1,5 +1,6 @@ import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js'; import * as utils from 'src/utils.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; import { server } from '../../mocks/xhr'; const pastDateString = new Date(0).toString() @@ -17,6 +18,9 @@ describe('CriteoId module', function () { let timeStampStub; let parseUrlStub; let triggerPixelStub; + let gdprConsentDataStub; + let uspConsentDataStub; + let gppConsentDataStub; beforeEach(function (done) { getCookieStub = sinon.stub(storage, 'getCookie'); @@ -27,6 +31,9 @@ describe('CriteoId module', function () { timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); parseUrlStub = sinon.stub(utils, 'parseUrl').returns({ protocol: 'https', hostname: 'testdev.com' }) triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); done(); }); @@ -39,6 +46,9 @@ describe('CriteoId module', function () { timeStampStub.restore(); triggerPixelStub.restore(); parseUrlStub.restore(); + gdprConsentDataStub.restore(); + uspConsentDataStub.restore(); + gppConsentDataStub.restore(); }); const storageTestCases = [ @@ -136,11 +146,11 @@ describe('CriteoId module', function () { })); const gdprConsentTestCases = [ - { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: 'expectedConsentString' }, - { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: undefined }, - { consentData: { gdprApplies: true, consentString: undefined }, expected: undefined }, - { consentData: { gdprApplies: 'oui', consentString: 'expectedConsentString' }, expected: undefined }, - { consentData: undefined, expected: undefined } + { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '1' }, + { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '0' }, + { consentData: { gdprApplies: true, consentString: undefined }, expectedGdprConsent: undefined, expectedGdpr: '1' }, + { consentData: { gdprApplies: 'oui', consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '0' }, + { consentData: undefined, expectedGdprConsent: undefined, expectedGdpr: undefined } ]; it('should call sync pixels if request by backend', function () { @@ -225,14 +235,94 @@ describe('CriteoId module', function () { gdprConsentTestCases.forEach(testCase => it('should call user sync url with the gdprConsent', function () { let callBackSpy = sinon.spy(); - let result = criteoIdSubmodule.getId(undefined, testCase.consentData); + + gdprConsentDataStub.returns(testCase.consentData); + + let result = criteoIdSubmodule.getId(undefined); + result.callback(callBackSpy); + + let request = server.requests[0]; + + if (testCase.expectedGdprConsent) { + expect(request.url).to.have.string(`gdprString=${testCase.expectedGdprConsent}`); + } else { + expect(request.url).to.not.have.string('gdprString='); + } + + if (testCase.expectedGdpr) { + expect(request.url).to.have.string(`gdpr=${testCase.expectedGdpr}`); + } else { + expect(request.url).to.not.have.string('gdpr='); + } + + request.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + expect(callBackSpy.calledOnce).to.be.true; + })); + + [undefined, 'abc'].forEach(usPrivacy => it('should call user sync url with the us privacy string', function () { + let callBackSpy = sinon.spy(); + + uspConsentDataStub.returns(usPrivacy); + + let result = criteoIdSubmodule.getId(undefined); result.callback(callBackSpy); let request = server.requests[0]; - if (testCase.expected) { - expect(request.url).to.have.string(`gdprString=${testCase.expected}`); + + if (usPrivacy) { + expect(request.url).to.have.string(`us_privacy=${usPrivacy}`); + } else { + expect(request.url).to.not.have.string('us_privacy='); + } + + request.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + expect(callBackSpy.calledOnce).to.be.true; + })); + + [ + { + consentData: { + gppString: 'abc', + applicableSections: [1] + }, + expectedGpp: 'abc', + expectedGppSid: '1' + }, + { + consentData: undefined, + expectedGpp: undefined, + expectedGppSid: undefined + } + ].forEach(testCase => it('should call user sync url with the gpp string', function () { + let callBackSpy = sinon.spy(); + + gppConsentDataStub.returns(testCase.consentData); + + let result = criteoIdSubmodule.getId(undefined); + result.callback(callBackSpy); + + let request = server.requests[0]; + + if (testCase.expectedGpp) { + expect(request.url).to.have.string(`gpp=${testCase.expectedGpp}`); + } else { + expect(request.url).to.not.have.string('gpp='); + } + + if (testCase.expectedGppSid) { + expect(request.url).to.have.string(`gpp_sid=${testCase.expectedGppSid}`); } else { - expect(request.url).to.not.have.string('gdprString'); + expect(request.url).to.not.have.string('gpp_sid='); } request.respond( From 3430e8db022881f34914a7de2d9319c0424fdffd Mon Sep 17 00:00:00 2001 From: JacobKlein26 <42449375+JacobKlein26@users.noreply.github.com> Date: Wed, 26 Apr 2023 20:06:37 -0400 Subject: [PATCH 352/375] NextMillennium Bd Adapter : add gvlid (#9858) * add gvlid * no change --- modules/nextMillenniumBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 6f9385094e7..89c6e677007 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -31,6 +31,7 @@ const VIDEO_PARAMS = [ 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'protocols', 'startdelay' ]; +const GVLID = 1060; const sendingDataStatistic = initSendingDataStatistic(); events.on(CONSTANTS.EVENTS.AUCTION_INIT, auctionInitHandler); @@ -44,6 +45,7 @@ events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], + gvlid: GVLID, isBidRequestValid: function(bid) { return !!( From 51884fe11db3014b2d65f3b8675dd0ebb0e06b8b Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 27 Apr 2023 12:12:04 +0000 Subject: [PATCH 353/375] Prebid 7.47.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7baf4ab3094..4fb7d420386 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.47.0-pre", + "version": "7.47.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index df9a5551c50..1afb5dda61a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.47.0-pre", + "version": "7.47.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 50fda605b14641b232efa7332ff06f69886f5482 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 27 Apr 2023 12:12:05 +0000 Subject: [PATCH 354/375] Increment version to 7.48.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4fb7d420386..614efa22f2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.47.0", + "version": "7.48.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 1afb5dda61a..9b870eceef3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.47.0", + "version": "7.48.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a6d0b308a6e58c12477b9bb4f7dc0d98f025cbf5 Mon Sep 17 00:00:00 2001 From: TM Date: Thu, 27 Apr 2023 14:28:43 +0200 Subject: [PATCH 355/375] Adrino Bid Adapter: banner support added (#9860) * banner support added * test name change * not my test failed * not my test failed --------- Co-authored-by: Tomasz Mielcarz --- modules/adrinoBidAdapter.js | 15 +++++-- test/spec/modules/adrinoBidAdapter_spec.js | 52 +++++++++++++++++++++- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/modules/adrinoBidAdapter.js b/modules/adrinoBidAdapter.js index efd23761bfd..9825c5701d7 100644 --- a/modules/adrinoBidAdapter.js +++ b/modules/adrinoBidAdapter.js @@ -1,6 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {triggerPixel} from '../src/utils.js'; -import {NATIVE} from '../src/mediaTypes.js'; +import {NATIVE, BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -12,7 +12,7 @@ const GVLID = 1072; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [NATIVE], + supportedMediaTypes: [NATIVE, BANNER], getBidderConfig: function (property) { return config.getConfig(`${BIDDER_CODE}.${property}`); @@ -24,7 +24,7 @@ export const spec = { !!(bid.params.hash) && (typeof bid.params.hash === 'string') && !!(bid.mediaTypes) && - Object.keys(bid.mediaTypes).includes(NATIVE) && + (Object.keys(bid.mediaTypes).includes(NATIVE) || Object.keys(bid.mediaTypes).includes(BANNER)) && (bid.bidder === BIDDER_CODE); }, @@ -36,13 +36,20 @@ export const spec = { for (let i = 0; i < validBidRequests.length; i++) { let requestData = { bidId: validBidRequests[i].bidId, - nativeParams: validBidRequests[i].nativeParams, placementHash: validBidRequests[i].params.hash, userId: validBidRequests[i].userId, referer: bidderRequest.refererInfo.page, userAgent: navigator.userAgent, } + if (validBidRequests[i].sizes != null && validBidRequests[i].sizes.length > 0) { + requestData.bannerParams = { sizes: validBidRequests[i].sizes }; + } + + if (validBidRequests[i].nativeParams != null) { + requestData.nativeParams = validBidRequests[i].nativeParams; + } + if (bidderRequest && bidderRequest.gdprConsent) { requestData.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, diff --git a/test/spec/modules/adrinoBidAdapter_spec.js b/test/spec/modules/adrinoBidAdapter_spec.js index 577dd3e9164..72f006d4e4a 100644 --- a/test/spec/modules/adrinoBidAdapter_spec.js +++ b/test/spec/modules/adrinoBidAdapter_spec.js @@ -43,7 +43,7 @@ describe('adrinoBidAdapter', function () { it('should return false when unsupported media type is requested', function () { const bid = { ...validBid }; - bid.mediaTypes = { banner: { sizes: [[300, 250]] } }; + bid.mediaTypes = { video: {} }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -54,7 +54,46 @@ describe('adrinoBidAdapter', function () { }); }); - describe('buildRequests', function () { + describe('buildBannerRequest', function () { + const bidRequest = { + bidder: 'adrino', + params: { + hash: 'abcdef123456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [970, 250]] + } + }, + sizes: [[300, 250], [970, 250]], + userId: { criteoId: '2xqi3F94aHdwWnM3', pubcid: '3ec0b202-7697' }, + adUnitCode: 'adunit-code-2', + bidId: '12345678901234', + bidderRequestId: '98765432109876', + auctionId: '01234567891234', + }; + + it('should build the request correctly', function () { + const result = spec.buildRequests( + [ bidRequest ], + { refererInfo: { page: 'http://example.com/' } } + ); + expect(result.length).to.equal(1); + expect(result[0].method).to.equal('POST'); + expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('bannerParams'); + expect(result[0].data[0].bannerParams.sizes.length).to.equal(2); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); + }); + }); + + describe('buildNativeRequest', function () { const bidRequest = { bidder: 'adrino', params: { @@ -71,6 +110,15 @@ describe('adrinoBidAdapter', function () { } } }, + nativeParams: { + title: { + required: true + }, + image: { + required: true, + sizes: [[300, 150], [300, 210]] + } + }, userId: { criteoId: '2xqi3F94aHdwWnM3', pubcid: '3ec0b202-7697' }, adUnitCode: 'adunit-code', bidId: '12345678901234', From 3f51102ac46480ef18f6b2c5b1af7bc066912fae Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 27 Apr 2023 16:02:28 +0200 Subject: [PATCH 356/375] Criteo Bid Adapter: Map native assets to slot.ext.assets (#9851) Co-authored-by: v.raybaud --- modules/criteoBidAdapter.js | 8 ++-- test/spec/modules/criteoBidAdapter_spec.js | 56 ++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index a27f77349bf..8f24dd6c1aa 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -6,10 +6,8 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { find } from '../src/polyfill.js'; import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 import { getStorageManager } from '../src/storageManager.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { hasPurpose1Consent } from '../src/utils/gpdr.js'; - const GVLID = 91; export const ADAPTER_VERSION = 35; const BIDDER_CODE = 'criteo'; @@ -146,9 +144,6 @@ export const spec = { * @return {ServerRequest} */ buildRequests: (bidRequests, bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); - let url; let data; let fpd = bidderRequest.ortb2 || {}; @@ -439,6 +434,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.ext) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); } + if (bidRequest.nativeOrtbRequest?.assets) { + slot.ext = Object.assign({}, slot.ext, {assets: bidRequest.nativeOrtbRequest.assets}); + } if (bidRequest.params.publisherSubId) { slot.publishersubid = bidRequest.params.publisherSubId; } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 36d04d4b2e0..a1d738c3657 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -728,6 +728,62 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].native).to.equal(true); }); + it('should map ortb native assets to slot ext assets', function () { + const assets = [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, + { + required: 1, + id: 2, + title: { + len: 140, + } + }, + { + required: 1, + id: 3, + data: { + type: 1, + } + }, + { + required: 0, + id: 4, + data: { + type: 2, + } + }, + { + required: 0, + id: 5, + img: { + type: 1, + wmin: 20, + hmin: 20, + } + }]; + const bidRequests = [ + { + nativeOrtbRequest: { + assets: assets + }, + params: { + nativeCallback: function () { } + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].native).to.equal(true); + expect(ortbRequest.slots[0].ext.assets).to.deep.equal(assets); + }); + it('should properly build a networkId request', function () { const bidderRequest = { refererInfo: { From 22ff9c276e35e92087e5708d292b21a2ae1d3c1a Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 27 Apr 2023 11:04:01 -0700 Subject: [PATCH 357/375] Realvu analytics adapter: fix test failures on Edge (#9857) * Realvu analytics adapter: fix test failures on Edge * Remove realVu tests --- .../modules/realvuAnalyticsAdapter_spec.js | 189 ------------------ 1 file changed, 189 deletions(-) delete mode 100644 test/spec/modules/realvuAnalyticsAdapter_spec.js diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js deleted file mode 100644 index aa17a2c7c75..00000000000 --- a/test/spec/modules/realvuAnalyticsAdapter_spec.js +++ /dev/null @@ -1,189 +0,0 @@ -import { expect } from 'chai'; -import realvuAnalyticsAdapter, { lib } from 'modules/realvuAnalyticsAdapter.js'; -import CONSTANTS from 'src/constants.json'; - -function addDiv(id) { - let dv = document.createElement('div'); - dv.id = id; - dv.style.width = '728px'; - dv.style.height = '90px'; - dv.style.display = 'block'; - document.body.appendChild(dv); - let f = document.createElement('iframe'); - f.width = 728; - f.height = 90; - dv.appendChild(f); - let d = null; - if (f.contentDocument) d = f.contentDocument; // DOM - else if (f.contentWindow) d = f.contentWindow.document; // IE - d.open() - d.write(''); - d.close(); - return dv; -} - -describe('RealVu', function() { - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - addDiv('ad1'); - addDiv('ad2'); - sandbox.stub(lib, 'scr'); - }); - - afterEach(function () { - let a1 = document.getElementById('ad1'); - document.body.removeChild(a1); - let a2 = document.getElementById('ad2'); - document.body.removeChild(a2); - sandbox.restore(); - realvuAnalyticsAdapter.disableAnalytics(); - }); - - after(function () { - delete window.top1; - delete window.realvu_aa_fifo; - delete window.realvu_aa; - clearInterval(window.boost_poll); - delete window.boost_poll; - }); - - describe('Analytics Adapter.', function () { - it('enableAnalytics', function () { - const config = { - options: { - partnerId: '1Y', - regAllUnits: true - // unitIds: ['ad1', 'ad2'] - } - }; - let p = realvuAnalyticsAdapter.enableAnalytics(config); - expect(p).to.equal('1Y'); - }); - - it('checkIn', function () { - const bid = { - adUnitCode: 'ad1', - sizes: [ - [728, 90], - [970, 250], - [970, 90] - ] - }; - let result = realvuAnalyticsAdapter.checkIn(bid, '1Y'); - const b = Object.assign({}, window.top1.realvu_aa); - let a = b.ads[0]; - // console.log('a: ' + a.x + ', ' + a.y + ', ' + a.w + ', ' + a.h); - // console.log('b: ' + b.x1 + ', ' + b.y1 + ', ' + b.x2 + ', ' + b.y2); - expect(result).to.equal('yes'); - - result = realvuAnalyticsAdapter.checkIn(bid); // test invalid partnerId 'undefined' - result = realvuAnalyticsAdapter.checkIn(bid, ''); // test invalid partnerId '' - }); - - it('isInView return "NA"', function () { - const adUnitCode = '1234'; - let result = realvuAnalyticsAdapter.isInView(adUnitCode); - expect(result).to.equal('NA'); - }); - - it('bid response event', function () { - const config = { - options: { - partnerId: '1Y', - regAllUnits: true - // unitIds: ['ad1', 'ad2'] - } - }; - realvuAnalyticsAdapter.enableAnalytics(config); - const args = { - 'biddercode': 'realvu', - 'adUnitCode': 'ad1', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '7ba299eba818c1', - 'mediaType': 'banner', - 'creative_id': 85792851, - 'cpm': 0.4308 - }; - realvuAnalyticsAdapter.track({ - eventType: CONSTANTS.EVENTS.BID_RESPONSE, - args: args - }); - const boost = Object.assign({}, window.top1.realvu_aa); - expect(boost.ads[boost.len - 1].bids.length).to.equal(1); - - realvuAnalyticsAdapter.track({ - eventType: CONSTANTS.EVENTS.BID_WON, - args: args - }); - expect(boost.ads[boost.len - 1].bids[0].winner).to.equal(1); - }); - }); - - describe('Boost.', function () { - // const boost = window.top1.realvu_aa; - let boost; - beforeEach(function() { - boost = Object.assign({}, window.top1.realvu_aa); - }); - it('brd', function () { - let a1 = document.getElementById('ad1'); - let p = boost.brd(a1, 'Left'); - expect(typeof p).to.not.equal('undefined'); - }); - - it('addUnitById', function () { - let a1 = document.getElementById('ad1'); - let p = boost.addUnitById('1Y', 'ad1'); - expect(typeof p).to.not.equal('undefined'); - }); - - it('questA', function () { - const dv = document.getElementById('ad1'); - let q = boost.questA(dv); - expect(q).to.not.equal(null); - }); - - it('render', function () { - let dv = document.getElementById('ad1'); - // dv.style.width = '728px'; - // dv.style.height = '90px'; - // dv.style.display = 'block'; - dv.getBoundingClientRect = false; - // document.body.appendChild(dv); - let q = boost.findPosG(dv); - expect(q).to.not.equal(null); - }); - - it('readPos', function () { - const a = boost.ads[boost.len - 1]; - let r = boost.readPos(a); - expect(r).to.equal(true); - }); - - it('send_track', function () { - const a = boost.ads[boost.len - 1]; - boost.track(a, 'show', ''); - boost.sr = 'a'; - boost.send_track(); - expect(boost.beacons.length).to.equal(0); - }); - - it('questA text', function () { - let p = document.createElement('p'); - p.innerHTML = 'ABC'; - document.body.appendChild(p); - let r = boost.questA(p.firstChild); - document.body.removeChild(p); - expect(r).to.not.equal(null); - }); - - it('_f=conf', function () { - const a = boost.ads[boost.len - 1]; - let r = boost.tru(a, 'conf'); - expect(r).to.not.include('_ps='); - }); - }); -}); From 1ac35727e2220c4afd36807d73e6ab6f55a7ea4c Mon Sep 17 00:00:00 2001 From: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Fri, 28 Apr 2023 17:42:38 +0200 Subject: [PATCH 358/375] ORTB2 bcat & badv support + ORTB2 tests (#9871) --- modules/rtbhouseBidAdapter.js | 15 ++++++------- test/spec/modules/rtbhouseBidAdapter_spec.js | 22 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index c4264d2920d..5ebf51b6e0d 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -85,15 +85,12 @@ export const spec = { } const ortb2Params = bidderRequest?.ortb2 || {}; - if (ortb2Params.site) { - mergeDeep(request, { site: ortb2Params.site }); - } - if (ortb2Params.user) { - mergeDeep(request, { user: ortb2Params.user }); - } - if (ortb2Params.device) { - mergeDeep(request, { device: ortb2Params.device }); - } + ['site', 'user', 'device', 'bcat', 'badv'].forEach(entry => { + const ortb2Param = ortb2Params[entry]; + if (ortb2Param) { + mergeDeep(request, { [entry]: ortb2Param }); + } + }); let computedEndpointUrl = ENDPOINT_URL; diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 70011cbf244..78c700f3804 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -282,6 +282,28 @@ describe('RTBHouseAdapter', () => { expect(data.source).to.not.have.property('ext'); }); + it('should include first party data', function () { + const bidRequest = Object.assign([], bidRequests); + const localBidderRequest = { + ...bidderRequest, + ortb2: { + bcat: ['IAB1', 'IAB2-1'], + badv: ['domain1.com', 'domain2.com'], + site: { ext: { data: 'some site data' } }, + device: { ext: { data: 'some device data' } }, + user: { ext: { data: 'some user data' } } + } + }; + + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + expect(data.bcat).to.deep.equal(localBidderRequest.ortb2.bcat); + expect(data.badv).to.deep.equal(localBidderRequest.ortb2.badv); + expect(data.site).to.nested.include({'ext.data': 'some site data'}); + expect(data.device).to.nested.include({'ext.data': 'some device data'}); + expect(data.user).to.nested.include({'ext.data': 'some user data'}); + }); + context('FLEDGE', function() { afterEach(function () { config.resetConfig(); From 0cf7c75d2019d0be566a15bd3af951c298781801 Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Mon, 1 May 2023 14:27:13 +0300 Subject: [PATCH 359/375] AdMatic Bid Adapter: added Video / Banner params (#9856) * 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 * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update --- modules/admaticBidAdapter.js | 37 ++++- test/spec/modules/admaticBidAdapter_spec.js | 175 ++++++++++++++++++-- 2 files changed, 196 insertions(+), 16 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index c216c66c78e..027f924ac5d 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -34,7 +34,7 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const bids = validBidRequests.map(buildRequestObject); - const blacklist = bidderRequest.ortb2 || {}; + const blacklist = bidderRequest.ortb2; const networkId = getValue(validBidRequests[0].params, 'networkId'); const host = getValue(validBidRequests[0].params, 'host'); const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; @@ -93,7 +93,7 @@ export const spec = { * @return {Bid[]} */ interpretResponse: (response, request) => { - const body = response.body || response; + const body = response.body; const bidResponses = []; if (body && body?.data && isArray(body.data)) { body.data.forEach(bid => { @@ -104,13 +104,23 @@ export const spec = { height: bid.height, currency: body.cur || 'TRY', netRevenue: true, - ad: bid.party_tag, creativeId: bid.creative_id, meta: { advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, - ttl: 360, - bidder: bid.bidder + bidder: bid.bidder, + mediaType: bid.type, + ttl: 60 + }; + + if (resbid.mediaType === 'video' && isUrl(bid.party_tag)) { + resbid.vastUrl = bid.party_tag; + resbid.vastImpUrl = bid.iurl; + } else if (resbid.mediaType === 'video') { + resbid.vastXml = bid.party_tag; + resbid.vastImpUrl = bid.iurl; + } else if (resbid.mediaType === 'banner') { + resbid.ad = bid.party_tag; }; bidResponses.push(resbid); @@ -120,6 +130,15 @@ export const spec = { } }; +function isUrl(str) { + try { + URL(str); + return true; + } catch (error) { + return false; + } +}; + function enrichSlotWithFloors(slot, bidRequest) { try { const slotFloors = {}; @@ -168,6 +187,14 @@ function parseSize(size) { function buildRequestObject(bid) { const reqObj = {}; reqObj.size = getSizes(bid); + if (bid.mediaTypes?.banner) { + reqObj.type = 'banner'; + reqObj.mediatype = {}; + } + if (bid.mediaTypes?.video) { + reqObj.type = 'video'; + reqObj.mediatype = bid.mediaTypes.video; + } reqObj.id = getBidIdParameter('bidId', bid); enrichSlotWithFloors(reqObj, bid); diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index ce4a4a2f0fb..1d2fb1e79cb 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -22,11 +22,13 @@ describe('admaticBidAdapter', () => { 'host': 'layer.serve.admatic.com.tr' }, 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', - 'creativeId': 'er2ee' + 'creativeId': 'er2ee', + 'ortb2': { 'badv': ['admatic.com.tr'] } }; it('should return true when required params found', function() { @@ -34,15 +36,11 @@ describe('admaticBidAdapter', () => { }); it('should return false when required params are not passed', function() { - let bid = Object.assign({}, bid); - delete bid.params; - - bid.params = { - 'networkId': 0, - 'host': 'layer.serve.admatic.com.tr' + let bid2 = {}; + bid2.params = { + 'someIncorrectParam': 0 }; - - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); }); }); @@ -54,6 +52,7 @@ describe('admaticBidAdapter', () => { 'networkId': 10433394, 'host': 'layer.serve.admatic.com.tr' }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, 'mediaTypes': { 'banner': { 'sizes': [[300, 250], [728, 90]] @@ -98,6 +97,8 @@ describe('admaticBidAdapter', () => { 'h': 90 } ], + 'mediatype': {}, + 'type': 'banner', 'id': '2205da7a81846b', 'floors': { 'banner': { @@ -105,6 +106,49 @@ describe('admaticBidAdapter', () => { '728x90': { 'currency': 'USD', 'floor': 2 } } } + }, + { + 'size': [ + { + 'w': 338, + 'h': 280 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 338, + 280 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 1, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2 + }, + 'id': '45e86fc7ce7fc93' } ], 'ext': { @@ -118,6 +162,7 @@ describe('admaticBidAdapter', () => { 'networkId': 10433394, 'host': 'layer.serve.admatic.com.tr' }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, 'mediaTypes': { 'banner': { 'sizes': [[300, 250], [728, 90]] @@ -163,12 +208,57 @@ describe('admaticBidAdapter', () => { } ], 'id': '2205da7a81846b', + 'mediatype': {}, + 'type': 'banner', 'floors': { 'banner': { '300x250': { 'currency': 'USD', 'floor': 1 }, '728x90': { 'currency': 'USD', 'floor': 2 } } } + }, + { + 'size': [ + { + 'w': 338, + 'h': 280 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 338, + 280 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 1, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2 + }, + 'id': '45e86fc7ce7fc93' } ], 'ext': { @@ -194,6 +284,7 @@ describe('admaticBidAdapter', () => { 'sizes': [[300, 250], [728, 90]] } }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, getFloor: inputParams => { if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { return { @@ -217,6 +308,7 @@ describe('admaticBidAdapter', () => { 'networkId': 10433394, 'host': 'layer.serve.admatic.com.tr' }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [728, 90]], 'bidId': '30b31c1838de1e', @@ -249,9 +341,35 @@ describe('admaticBidAdapter', () => { 'width': 300, 'height': 250, 'price': 0.01, + 'type': 'banner', 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], - 'party_tag': '
' + 'party_tag': '
', + 'iurl': 'https://www.admatic.com.tr' + }, + { + 'id': 2, + 'creative_id': '3741', + 'width': 300, + 'height': 250, + 'price': 0.01, + 'type': 'video', + 'bidder': 'admatic', + 'adomain': ['admatic.com.tr'], + 'party_tag': '', + 'iurl': 'https://www.admatic.com.tr' + }, + { + 'id': 3, + 'creative_id': '3741', + 'width': 300, + 'height': 250, + 'price': 0.01, + 'type': 'video', + 'bidder': 'admatic', + 'adomain': ['admatic.com.tr'], + 'party_tag': 'https://www.admatic.com.tr', + 'iurl': 'https://www.admatic.com.tr' } ], 'queryId': 'cdnbh24rlv0hhkpfpln0', @@ -265,13 +383,48 @@ describe('admaticBidAdapter', () => { width: 300, height: 250, currency: 'TRY', + mediaType: 'banner', netRevenue: true, ad: '
', creativeId: '374', meta: { advertiserDomains: ['admatic.com.tr'] }, - ttl: 360, + ttl: 60, + bidder: 'admatic' + }, + { + requestId: 2, + cpm: 0.01, + width: 300, + height: 250, + currency: 'TRY', + mediaType: 'video', + netRevenue: true, + vastImpUrl: 'https://www.admatic.com.tr', + vastXml: '', + creativeId: '3741', + meta: { + advertiserDomains: ['admatic.com.tr'] + }, + ttl: 60, + bidder: 'admatic' + }, + { + requestId: 3, + cpm: 0.01, + width: 300, + height: 250, + currency: 'TRY', + mediaType: 'video', + netRevenue: true, + vastImpUrl: 'https://www.admatic.com.tr', + vastXml: 'https://www.admatic.com.tr', + creativeId: '3741', + meta: { + advertiserDomains: ['admatic.com.tr'] + }, + ttl: 60, bidder: 'admatic' } ]; From 48cd24551a633d1421e10a8dad4f8a957f14d87a Mon Sep 17 00:00:00 2001 From: Patrick Loughrey Date: Mon, 1 May 2023 09:24:54 -0400 Subject: [PATCH 360/375] Triplelift Bid Adapter: copying ad unit impression data (#9865) * TL-35335: Cast playbackmethod as array * TL-36204: Copy tid to imp extension obj * Added support for entire ortb2Imp obj * Only setting what exists in ortb2Imp.ext * Added additional test to check copy of entire ext obj * Revert "TL-36204: Copy tid to imp extension object" * TL-36204: Copying ortb2Imp.ext to impression ext obj * Added edge case logic and additional test * recos for tid change * Added spread operator to replace deepClone --------- Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> Co-authored-by: Nick Llerandi --- modules/tripleliftBidAdapter.js | 7 +++ .../spec/modules/tripleliftBidAdapter_spec.js | 61 ++++++++++++++++--- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 038cd7d757d..45931bce27e 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -130,8 +130,15 @@ function _buildPostBody(bidRequests, bidderRequest) { } if (!isEmpty(bidRequest.ortb2Imp)) { + // legacy method for extracting ortb2Imp.ext imp.fpd = _getAdUnitFpd(bidRequest.ortb2Imp); + + // preferred method for extracting ortb2Imp.ext + if (!isEmpty(bidRequest.ortb2Imp.ext)) { + imp.ext = { ...bidRequest.ortb2Imp.ext }; + } } + return imp; }); diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 4debf516f89..718d030be91 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -139,15 +139,13 @@ describe('triplelift adapter', function () { sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', auctionId: '1d1a030790a475', userId: {}, schain, ortb2Imp: { ext: { - data: { - pbAdSlot: 'homepage-top-rect', - adUnitSpecificAttribute: 123 - } + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' } } }, @@ -178,6 +176,15 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + ortb2Imp: { + ext: { + data: { + pbAdSlot: 'homepage-top-rect', + adUnitSpecificAttribute: 123 + }, + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' + } + } }, // banner and outstream video { @@ -245,6 +252,11 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + ortb2Imp: { + misc: { + test: 1 + } + } }, // incomplete banner and incomplete video { @@ -689,6 +701,39 @@ describe('triplelift adapter', function () { expect(payload.imp[13].video.placement).to.equal(3); }); + it('should add tid to imp.ext if transactionId exists', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.tid).to.exist.and.be.a('string'); + expect(request.data.imp[0].ext.tid).to.equal('173f49a8-7549-4218-a23c-e7ba59b47229'); + }); + + it('should not add impression ext object if ortb2Imp does not exist', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[2].ext).to.not.exist; + }); + + it('should not add impression ext object if ortb2Imp.ext does not exist', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[3].ext).to.not.exist; + }); + + it('should copy entire impression ext object', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[1].ext).to.haveOwnProperty('tid'); + expect(request.data.imp[1].ext).to.haveOwnProperty('data'); + expect(request.data.imp[1].ext.data).to.haveOwnProperty('adUnitSpecificAttribute'); + expect(request.data.imp[1].ext.data).to.haveOwnProperty('pbAdSlot'); + expect(request.data.imp[1].ext).to.deep.equal( + { + data: { + pbAdSlot: 'homepage-top-rect', + adUnitSpecificAttribute: 123 + }, + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' + } + ); + }); + it('should add tdid to the payload if included', function () { const id = '6bca7f6b-a98a-46c0-be05-6020f7604598'; bidRequests[0].userId.tdid = id; @@ -1103,10 +1148,10 @@ describe('triplelift adapter', function () { }); it('should send ad unit fpd if kvps are available', function() { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - expect(request.data.imp[0].fpd.context).to.haveOwnProperty('data'); - expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('pbAdSlot'); - expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); - expect(request.data.imp[1].fpd).to.not.exist; + expect(request.data.imp[1].fpd.context).to.haveOwnProperty('data'); + expect(request.data.imp[1].fpd.context.data).to.haveOwnProperty('pbAdSlot'); + expect(request.data.imp[1].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); + expect(request.data.imp[2].fpd).to.not.exist; }); it('should send 1PlusX data as fpd if localStorage is available and no other fpd is defined', function() { sandbox.stub(storage, 'getDataFromLocalStorage').callsFake(() => '{"kid":1,"s":"ySRdArquXuBolr/cVv0UNqrJhTO4QZsbNH/t+2kR3gXjbA==","t":"/yVtBrquXuBolr/cVv0UNtx1mssdLYeKFhWFI3Dq1dJnug=="}'); From e0e77cf8d7959295c6ce631941f1b6aa9a4419cb Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 1 May 2023 08:53:30 -0700 Subject: [PATCH 361/375] Multiple modules: do not use bidderTimeout as TTL (#9880) * Multiple modules: do not use bidderTimeout as TTL * Fix lint --- modules/ablidaBidAdapter.js | 9 ++++----- modules/axonixBidAdapter.js | 12 ++++++------ modules/dspxBidAdapter.js | 5 ++--- modules/iqmBidAdapter.js | 3 +-- modules/orbitsoftBidAdapter.js | 3 +-- modules/radsBidAdapter.js | 7 +++---- modules/stvBidAdapter.js | 9 ++++----- modules/underdogmediaBidAdapter.js | 21 +++++++++------------ modules/vdoaiBidAdapter.js | 5 ++--- modules/wipesBidAdapter.js | 5 ++--- modules/yieldoneBidAdapter.js | 7 +++---- 11 files changed, 37 insertions(+), 49 deletions(-) diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js index ca489a10a90..805a2020fb4 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -1,8 +1,7 @@ -import { triggerPixel } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'ablida'; const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; @@ -77,7 +76,7 @@ export const spec = { const response = serverResponse.body; response.forEach(function(bid) { - bid.ttl = config.getConfig('_bidderTimeout'); + bid.ttl = 60 bidResponses.push(bid); }); return bidResponses; diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index 5435bf09059..87c3aff444a 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -1,8 +1,8 @@ -import { isArray, logError, deepAccess, isEmpty, triggerPixel, replaceAuctionPrice } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { ajax } from '../src/ajax.js'; +import {deepAccess, isArray, isEmpty, logError, replaceAuctionPrice, triggerPixel} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'axonix'; const BIDDER_VERSION = '1.0.2'; @@ -150,7 +150,7 @@ export const spec = { for (const resp of response) { if (resp.requestId) { responses.push(Object.assign(resp, { - ttl: config.getConfig('_bidderTimeout') + ttl: 60 })); } } diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index 3bfb660cd7e..74a93ce086e 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -1,5 +1,4 @@ -import {deepAccess, getBidIdParameter, logError, logMessage, logWarn, isFn} from '../src/utils.js'; -import {config} from '../src/config.js'; +import {deepAccess, getBidIdParameter, isFn, logError, logMessage, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; @@ -183,7 +182,7 @@ export const spec = { currency: currency, netRevenue: netRevenue, type: response.type, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, meta: { advertiserDomains: response.adomain || [] } diff --git a/modules/iqmBidAdapter.js b/modules/iqmBidAdapter.js index 1c36bafd3ce..51cd8b3bdca 100644 --- a/modules/iqmBidAdapter.js +++ b/modules/iqmBidAdapter.js @@ -1,6 +1,5 @@ import {_each, deepAccess, getBidIdParameter, isArray} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {INSTREAM} from '../src/video.js'; @@ -155,7 +154,7 @@ export const spec = { auctionId: bidRequest.data.auctionId, mediaType: bidRequest.data.imp.mediatype, - ttl: bid.ttl || config.getConfig('_bidderTimeout') + ttl: bid.ttl || 60 }; if (bidRequest.data.imp.mediatype === VIDEO) { diff --git a/modules/orbitsoftBidAdapter.js b/modules/orbitsoftBidAdapter.js index d72a8719bd8..4c3f2e38c58 100644 --- a/modules/orbitsoftBidAdapter.js +++ b/modules/orbitsoftBidAdapter.js @@ -1,6 +1,5 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; const BIDDER_CODE = 'orbitsoft'; let styleParamsMap = { @@ -122,7 +121,7 @@ export const spec = { const HEIGHT = serverBody.height; const CREATIVE = serverBody.content_url; const CALLBACK_UID = serverBody.callback_uid; - const TIME_TO_LIVE = config.getConfig('_bidderTimeout'); + const TIME_TO_LIVE = 60; const REFERER = utils.getWindowTop(); let bidRequest = request.bidRequest; if (CPM > 0 && WIDTH > 0 && HEIGHT > 0) { diff --git a/modules/radsBidAdapter.js b/modules/radsBidAdapter.js index fcb8c3a8c2a..ae16bcf9d83 100644 --- a/modules/radsBidAdapter.js +++ b/modules/radsBidAdapter.js @@ -1,7 +1,6 @@ -import { deepAccess } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {deepAccess} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'rads'; const ENDPOINT_URL = 'https://rads.recognified.net/md.request.php'; @@ -86,7 +85,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, meta: { advertiserDomains: response.adomain || [] } diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js index 32a3d289c61..ec88ee9c620 100644 --- a/modules/stvBidAdapter.js +++ b/modules/stvBidAdapter.js @@ -1,7 +1,6 @@ -import { deepAccess } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {deepAccess} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; const BIDDER_CODE = 'stv'; @@ -128,7 +127,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, meta: { advertiserDomains: response.adomain || [] } diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 233d5f080a9..bdc5c04279f 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -1,21 +1,18 @@ import { - logMessage, + deepAccess, flatten, - parseSizesInput, + getWindowSelf, + getWindowTop, isGptPubadsDefined, isSlotMatchingAdUnitCode, logInfo, + logMessage, logWarn, - getWindowSelf, - getWindowTop, - deepAccess + parseSizesInput } from '../src/utils.js'; -import { - config -} from '../src/config.js'; -import { - registerBidder -} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; + const BIDDER_CODE = 'underdogmedia'; const UDM_ADAPTER_VERSION = '7.30V'; const UDM_VENDOR_ID = '159'; @@ -377,7 +374,7 @@ function makeNotification(bid, mid, bidParam) { url += `;version=${UDM_ADAPTER_VERSION}`; url += ';cb=' + Math.random(); url += ';qqq=' + (1 / bid.cpm); - url += ';hbt=' + config.getConfig('_bidderTimeout'); + url += ';hbt=' + config.getConfig('bidderTimeout'); url += ';style=adapter'; url += ';vis=' + encodeURIComponent(document.visibilityState); diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index a57eae5f328..167c378ac6d 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -1,5 +1,4 @@ -import { getAdUnitSizes } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {getAdUnitSizes} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -87,7 +86,7 @@ export const spec = { // dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, // referrer: referrer, // ad: response.adm // ad: adCreative, diff --git a/modules/wipesBidAdapter.js b/modules/wipesBidAdapter.js index 3d040fee8d3..56a4aeecd71 100644 --- a/modules/wipesBidAdapter.js +++ b/modules/wipesBidAdapter.js @@ -1,5 +1,4 @@ -import { logWarn } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -50,7 +49,7 @@ function interpretResponse(serverResponse, bidRequest) { dealId: response.deal_id, currency: 'JPY', netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, referrer: bidRequest.data.r || '', mediaType: BANNER, ad: response.ad_tag, diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index fd804eed2e7..2ff747a238a 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -1,8 +1,7 @@ import {deepAccess, isEmpty, isStr, logWarn, parseSizesInput} from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; /** * @typedef {import('../src/adapters/bidderFactory').Bid} Bid @@ -147,7 +146,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, referrer: referrer, meta: { advertiserDomains: response.adomain ? response.adomain : [] From 2edd264082b2baf0511def2b1fb5c326bad3b3cf Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 1 May 2023 11:56:31 -0400 Subject: [PATCH 362/375] Grid Bid Adapter: add support for video.plcmt (#9763) * Update gridBidAdapter.js * Update gridBidAdapter_spec.js --- modules/gridBidAdapter.js | 1 + test/spec/modules/gridBidAdapter_spec.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index a043483d9b0..8d6c22285f3 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -980,6 +980,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { api: bidRequest.mediaTypes.video.api, skip: bidRequest.mediaTypes.video.skip, placement: bidRequest.mediaTypes.video.placement, + plcmt: bidRequest.mediaTypes.video.plcmt, minduration: bidRequest.mediaTypes.video.minduration, playbackmethod: bidRequest.mediaTypes.video.playbackmethod, startdelay: bidRequest.mediaTypes.video.startdelay diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 2ef604dd097..b1f9d3edb30 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -423,6 +423,7 @@ describe('TheMediaGrid Adapter', function () { api: [1, 2], skip: 1, placement: 1, + plcmt: 2, minduration: 0, playbackmethod: 1, startdelay: 0 @@ -539,6 +540,7 @@ describe('TheMediaGrid Adapter', function () { ], 'minduration': 0, 'placement': 1, + 'plcmt': 2, 'playbackmethod': 1, 'playersizes': [ '400x600' From 962d8165ced2390b050efc368cab3b89dede6d32 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 1 May 2023 12:12:54 -0400 Subject: [PATCH 363/375] Revert "Grid Bid Adapter: add support for video.plcmt (#9763)" (#9882) This reverts commit 2edd264082b2baf0511def2b1fb5c326bad3b3cf. --- modules/gridBidAdapter.js | 1 - test/spec/modules/gridBidAdapter_spec.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 8d6c22285f3..a043483d9b0 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -980,7 +980,6 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { api: bidRequest.mediaTypes.video.api, skip: bidRequest.mediaTypes.video.skip, placement: bidRequest.mediaTypes.video.placement, - plcmt: bidRequest.mediaTypes.video.plcmt, minduration: bidRequest.mediaTypes.video.minduration, playbackmethod: bidRequest.mediaTypes.video.playbackmethod, startdelay: bidRequest.mediaTypes.video.startdelay diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index b1f9d3edb30..2ef604dd097 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -423,7 +423,6 @@ describe('TheMediaGrid Adapter', function () { api: [1, 2], skip: 1, placement: 1, - plcmt: 2, minduration: 0, playbackmethod: 1, startdelay: 0 @@ -540,7 +539,6 @@ describe('TheMediaGrid Adapter', function () { ], 'minduration': 0, 'placement': 1, - 'plcmt': 2, 'playbackmethod': 1, 'playersizes': [ '400x600' From ffb76a722acabbdaee4358d66ea5e6160d103d82 Mon Sep 17 00:00:00 2001 From: Alejandro Villanueva Date: Mon, 1 May 2023 10:25:53 -0600 Subject: [PATCH 364/375] UPDATE setOrtb user ids to avoid passing an empty array when no user ids present (#9875) Co-authored-by: Alejandro Villanueva --- modules/userId/index.js | 2 +- test/spec/ortbConverter/userId_spec.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index d16d341fd2d..a6b7bb25db0 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -1085,7 +1085,7 @@ module('userId', attachIdSystem); export function setOrtbUserExtEids(ortbRequest, bidderRequest, context) { const eids = deepAccess(context, 'bidRequests.0.userIdAsEids'); - if (eids) { + if (eids && Object.keys(eids).length > 0) { deepSetValue(ortbRequest, 'user.ext.eids', eids); } } diff --git a/test/spec/ortbConverter/userId_spec.js b/test/spec/ortbConverter/userId_spec.js index 0b1c8878edf..04a4d39ee48 100644 --- a/test/spec/ortbConverter/userId_spec.js +++ b/test/spec/ortbConverter/userId_spec.js @@ -18,4 +18,16 @@ describe('pbjs - ortb user eids', () => { setOrtbUserExtEids(req, {}, [{}]); expect(req).to.eql({}); }) + + it('has no effect if user.ext.eids is an empty array', () => { + const req = {}; + setOrtbUserExtEids(req, {}, { + bidRequests: [ + { + userIdAsEids: [] + } + ] + }); + expect(req).to.eql({}); + }); }) From 7d61a53d1deaf3e765636f46448bff9424d4b9e4 Mon Sep 17 00:00:00 2001 From: smounitz Date: Mon, 1 May 2023 12:41:46 -0400 Subject: [PATCH 365/375] Sovrn Bid Adapter: add gpp support (#9811) * feat: update Sovrn bid adapter to send gpp * style: eslint --------- Co-authored-by: feat: add video request fields <> --- modules/sovrnBidAdapter.js | 4 ++ test/spec/modules/sovrnBidAdapter_spec.js | 65 +++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 9982c9afa45..b825a554e4d 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -173,6 +173,10 @@ export const spec = { if (bidderRequest.uspConsent) { deepSetValue(sovrnBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + if (bidderRequest.gppConsent) { + deepSetValue(sovrnBidReq, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(sovrnBidReq, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } if (eids) { deepSetValue(sovrnBidReq, 'user.ext.eids', eids) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 0fedd4b9f49..831d3ae0315 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -248,6 +248,71 @@ describe('sovrnBidAdapter', function() { expect(data.regs.ext['us_privacy']).to.equal(bidderRequest.uspConsent) }) + it('should send gpp info in OpenRTB 2.6 location when gppConsent defined', function () { + const bidderRequest = { + ...baseBidderRequest, + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gppConsent: { + gppString: 'gppstring', + applicableSections: [8] + }, + bids: [baseBidRequest] + } + const { regs } = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + expect(regs.gpp).to.equal('gppstring') + expect(regs.gpp_sid).to.be.an('array') + expect(regs.gpp_sid).to.include(8) + }) + + it('should not send gpp info when gppConsent is not defined', function () { + const bidderRequest = { + ...baseBidderRequest, + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + bids: [baseBidRequest], + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true + }, + } + const { regs } = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + expect(regs.gpp).to.be.undefined + }) + + it('should send gdpr info even when gppConsent defined', function () { + const bidderRequest = { + ...baseBidderRequest, + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true + }, + gppConsent: { + gppString: 'gppstring', + applicableSections: [8] + }, + bids: [baseBidRequest] + } + + const { regs, user } = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + + expect(regs.ext.gdpr).to.exist.and.to.be.a('number') + expect(regs.ext.gdpr).to.equal(1) + expect(user.ext.consent).to.exist.and.to.be.a('string') + expect(user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString) + expect(regs.gpp).to.equal('gppstring') + expect(regs.gpp_sid).to.be.an('array') + expect(regs.gpp_sid).to.include(8) + }) + it('should add schain if present', function() { const schainRequest = { ...baseBidRequest, From cb2536b47a59bcdb958515afa9b6b52129179b3e Mon Sep 17 00:00:00 2001 From: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> Date: Mon, 1 May 2023 12:52:04 -0400 Subject: [PATCH 366/375] Triplelift Bid Adapter: relax check on consentString before appending gdprApplies to usersyncs (#9885) * TL-35335: Cast playbackmethod as array * TL-36204: Copy tid to imp extension obj * Added support for entire ortb2Imp obj * Only setting what exists in ortb2Imp.ext * Added additional test to check copy of entire ext obj * Revert "TL-36204: Copy tid to imp extension object" * TL-36204: Copying ortb2Imp.ext to impression ext obj * Added edge case logic and additional test * recos for tid change * Added spread operator to replace deepClone * addresses gdprApplies --------- Co-authored-by: Patrick Loughrey --- modules/tripleliftBidAdapter.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 45931bce27e..df00495b88b 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -9,7 +9,7 @@ const BIDDER_CODE = 'triplelift'; const STR_ENDPOINT = 'https://tlx.3lift.com/header/auction?'; const BANNER_TIME_TO_LIVE = 300; const VIDEO_TIME_TO_LIVE = 3600; -let gdprApplies = true; +let gdprApplies = null; let consentString = null; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -40,8 +40,12 @@ export const tripleliftAdapterSpec = { if (bidderRequest && bidderRequest.gdprConsent) { if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { gdprApplies = bidderRequest.gdprConsent.gdprApplies; - tlCall = tryAppendQueryString(tlCall, 'gdpr', gdprApplies.toString()); + } else { + gdprApplies = true; } + + tlCall = tryAppendQueryString(tlCall, 'gdpr', gdprApplies.toString()); + if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { consentString = bidderRequest.gdprConsent.consentString; tlCall = tryAppendQueryString(tlCall, 'cmp_cs', consentString); @@ -87,7 +91,7 @@ export const tripleliftAdapterSpec = { syncEndpoint = tryAppendQueryString(syncEndpoint, 'src', 'prebid'); } - if (consentString !== null) { + if (consentString !== null || gdprApplies) { syncEndpoint = tryAppendQueryString(syncEndpoint, 'gdpr', gdprApplies); syncEndpoint = tryAppendQueryString(syncEndpoint, 'cmp_cs', consentString); } From e77512db06aeecb2f65bbf90ff07a192ad92b793 Mon Sep 17 00:00:00 2001 From: radubarbos Date: Tue, 2 May 2023 08:11:28 +0300 Subject: [PATCH 367/375] Yahoo connect id storage fixes. (#9854) Co-authored-by: dumitrubarbos --- modules/connectIdSystem.js | 7 +++++-- test/spec/modules/connectIdSystem_spec.js | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 82669f12623..d4bf0356242 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -33,7 +33,7 @@ function storeObject(obj) { setEtldPlusOneCookie(MODULE_NAME, JSON.stringify(obj), new Date(expires), getSiteHostname()); } else if (storage.localStorageIsEnabled()) { obj.__expires = expires; - storage.setDataInLocalStorage(MODULE_NAME, obj); + storage.setDataInLocalStorage(MODULE_NAME, JSON.stringify(obj)); } } @@ -71,8 +71,11 @@ function getIdFromCookie() { function getIdFromLocalStorage() { if (storage.localStorageIsEnabled()) { - const storedIdData = storage.getDataFromLocalStorage(MODULE_NAME); + let storedIdData = storage.getDataFromLocalStorage(MODULE_NAME); if (storedIdData) { + try { + storedIdData = JSON.parse(storedIdData); + } catch {} if (isPlainObject(storedIdData) && storedIdData.__expires && storedIdData.__expires <= Date.now()) { storage.removeDataFromLocalStorage(MODULE_NAME); diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 72acfa38e0a..405e7c5e8b9 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -86,7 +86,7 @@ describe('Yahoo ConnectID Submodule', () => { { detail: 'cookie data over local storage data', cookie: '{"connectId":"foo"}', - localStorage: {connectId: 'bar'}, + localStorage: JSON.stringify({connectId: 'bar'}), expected: {connectId: 'foo'} }, { @@ -98,7 +98,7 @@ describe('Yahoo ConnectID Submodule', () => { { detail: 'local storage data if only it local storage data exists', cookie: undefined, - localStorage: {connectId: 'bar'}, + localStorage: JSON.stringify({connectId: 'bar'}), expected: {connectId: 'bar'} }, { @@ -441,10 +441,10 @@ describe('Yahoo ConnectID Submodule', () => { const dateNowStub = sinon.stub(Date, 'now'); dateNowStub.returns(0); const upsResponse = {connectid: 'barfoo'}; - const expectedStoredData = { + const expectedStoredData = JSON.stringify({ connectid: 'barfoo', __expires: 60 * 60 * 24 * 14 * 1000 - }; + }); invokeGetIdAPI({ puid: PUBLISHER_USER_ID, pixelId: PIXEL_ID From 16d863964f456adfd6278a5b9a93dc2ff612e0ce Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 2 May 2023 14:37:36 +0200 Subject: [PATCH 368/375] Remove syncstore from adapter (#9890) Co-authored-by: Thomas De Stefano --- modules/impactifyBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index a44e1d4dd3f..d35d4498136 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -91,7 +91,6 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { if (bidderRequest.uspConsent) { deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); - this.syncStore.uspConsent = bidderRequest.uspConsent; } if (GETCONFIG('coppa') == true) deepSetValue(request, 'regs.coppa', 1); From af44f4b5929ce5e4844b0e9c02e7580f7d6deaec Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 2 May 2023 09:07:45 -0700 Subject: [PATCH 369/375] Adloox server video: mock out blob URLs in tests (#9881) --- test/spec/modules/adlooxAdServerVideo_spec.js | 53 +++++++------------ 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/test/spec/modules/adlooxAdServerVideo_spec.js b/test/spec/modules/adlooxAdServerVideo_spec.js index ba9261e1aba..a071c6bbe3f 100644 --- a/test/spec/modules/adlooxAdServerVideo_spec.js +++ b/test/spec/modules/adlooxAdServerVideo_spec.js @@ -118,6 +118,16 @@ describe('Adloox Ad Server Video', function () { }); describe('buildVideoUrl', function () { + beforeEach(() => { + sinon.stub(URL, 'createObjectURL'); + sinon.stub(URL, 'revokeObjectURL'); + }); + + afterEach(() => { + URL.createObjectURL.restore() + URL.revokeObjectURL.restore() + }); + describe('invalid arguments', function () { it('should require callback', function (done) { const ret = buildVideoUrl(); @@ -235,7 +245,6 @@ describe('Adloox Ad Server Video', function () { it('should fetch, retry on withoutCredentials, follow and return a wrapped blob that expires', function (done) { BID.responseTimestamp = utils.timestamp(); BID.ttl = 30; - this.timeout(5000) const clock = sandbox.useFakeTimers(BID.responseTimestamp); @@ -244,42 +253,16 @@ describe('Adloox Ad Server Video', function () { url: wrapperUrl, bid: BID }; + + URL.createObjectURL.callsFake(() => 'mock-blob-url'); + const ret = buildVideoUrl(options, function (url) { expect(url.substr(0, options.url_vast.length)).is.equal(options.url_vast); - - const match = url.match(/[?&]vast=(blob%3A[^&]+)/); - expect(match).is.not.null; - - const blob = decodeURIComponent(match[1]); - - const xfr = sandbox.useFakeXMLHttpRequest(); - xfr.useFilters = true; - xfr.addFilter(x => true); // there is no network traffic for Blob URLs here - - ajax(blob, { - success: (responseText, q) => { - expect(q.status).is.equal(200); - expect(q.getResponseHeader('content-type')).is.equal(vastHeaders['content-type']); - - clock.runAll(); - - ajax(blob, { - success: (responseText, q) => { - xfr.useFilters = false; // .restore() does not really work - if (q.status == 0) return done(); - done(new Error('Blob should have expired')); - }, - error: (statusText, q) => { - xfr.useFilters = false; - done(); - } - }); - }, - error: (statusText, q) => { - xfr.useFilters = false; - done(new Error(statusText)); - } - }); + expect(url).to.match(/[?&]vast=mock-blob-url/); + sinon.assert.calledWith(URL.createObjectURL, sinon.match((val) => val.type === vastHeaders['content-type'])); + clock.runAll(); + sinon.assert.calledWith(URL.revokeObjectURL, 'mock-blob-url'); + done(); }); expect(ret).is.true; From f076c8bbc4eaea2559965221436de4d1a9d96e6f Mon Sep 17 00:00:00 2001 From: samuel-palmer-relevant-digital <77437973+samuel-palmer-relevant-digital@users.noreply.github.com> Date: Wed, 3 May 2023 15:04:24 +0200 Subject: [PATCH 370/375] Relevant Digital Bid Adapter: initial release (#9685) * Relevant Digital Bid Adapter * More tests + comments * Use the recommended onBidWon callback + live-placements in .md file * Remove unused imports + adjust example-parameters in .md file * Renamed files + rewritten test-cases * Added documentation for 'pbsBufferMs' setting * Added 'useSourceBidderCode' setting to use S2S bidder's code instead of the client-side code in responses --- modules/relevantdigitalBidAdapter.js | 198 ++++++++++++ modules/relevantdigitalBidAdapter.md | 117 +++++++ .../modules/relevantdigitalBidAdapter_spec.js | 295 ++++++++++++++++++ 3 files changed, 610 insertions(+) create mode 100644 modules/relevantdigitalBidAdapter.js create mode 100644 modules/relevantdigitalBidAdapter.md create mode 100644 test/spec/modules/relevantdigitalBidAdapter_spec.js diff --git a/modules/relevantdigitalBidAdapter.js b/modules/relevantdigitalBidAdapter.js new file mode 100644 index 00000000000..ad9ee5e1e14 --- /dev/null +++ b/modules/relevantdigitalBidAdapter.js @@ -0,0 +1,198 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {pbsExtensions} from '../libraries/pbsExtensions/pbsExtensions.js' +import {deepSetValue, isEmpty, deepClone, shuffle, triggerPixel, deepAccess} from '../src/utils.js'; + +const BIDDER_CODE = 'relevantdigital'; + +/** Global settings per bidder-code for this adapter (which might be > 1 if using aliasing) */ +let configByBidder = {}; + +/** Used by the tests */ +export const resetBidderConfigs = () => { + configByBidder = {}; +}; + +/** Settings ber bidder-code. checkParams === true means that it can optionally be set in bid-params */ +const FIELDS = [ + { name: 'pbsHost', checkParams: true, required: true }, + { name: 'accountId', checkParams: true, required: true }, + { name: 'pbsBufferMs', checkParams: false, required: false, default: 250 }, + { name: 'useSourceBidderCode', checkParams: false, required: false, default: false }, +]; + +const SYNC_HTML = 'https://cdn.relevant-digital.com/resources/load-cookie.html'; +const MAX_SYNC_COUNT = 10; // Max server-side bidder to sync at once via the iframe + +/** Get settings for a bidder-code via config and, if needed, bid parameters */ +const getBidderConfig = (bids) => { + const { bidder } = bids[0]; + const cfg = configByBidder[bidder] || { + ...Object.fromEntries(FIELDS.filter((f) => 'default' in f).map((f) => [f.name, f.default])), + syncedBidders: {}, // To keep track of S2S-bidders we already (started to) synced + }; + if (cfg.complete) { + return cfg; // Most common case, we already have the settings we need (and we won't re-read them) + } + configByBidder[bidder] = cfg; + const bidderConfiguration = config.getConfig(bidder) || {}; + + // Read settings set by setConfig({ [bidder]: { ... }}) and if not available - from bid params + FIELDS.forEach(({ name, checkParams }) => { + cfg[name] = bidderConfiguration[name] || cfg[name]; + if (!cfg[name] && checkParams) { + bids.forEach((bid) => { + cfg[name] = cfg[name] || bid.params?.[name]; + }); + } + }); + cfg.complete = FIELDS.every((field) => !field.required || cfg[field.name]); + if (cfg.complete) { + cfg.pbsHost = cfg.pbsHost.trim().replace('http://', 'https://'); + if (cfg.pbsHost.indexOf('https://') < 0) { + cfg.pbsHost = `https://${cfg.pbsHost}`; + } + } + return cfg; +} + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300 + }, + processors: pbsExtensions, + imp(buildImp, bidRequest, context) { + // Set stored request id from placementId + const imp = buildImp(bidRequest, context); + const { placementId } = bidRequest.params; + deepSetValue(imp, 'ext.prebid.storedrequest.id', placementId); + delete imp.ext.prebid.bidder; + return imp; + }, + overrides: { + bidResponse: { + bidderCode(orig, bidResponse, bid, { bidRequest }) { + const { bidder, params = {} } = bidRequest || {}; + let useSourceBidderCode = configByBidder[bidder]?.useSourceBidderCode; + if ('useSourceBidderCode' in params) { + useSourceBidderCode = params.useSourceBidderCode; + } + // Only use the orignal function when useSourceBidderCode is true, else our own bidder code will be used + if (useSourceBidderCode) { + orig.apply(this, [...arguments].slice(1)); + } + }, + }, + } +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: 1100, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** We need both params.placementId + a complete configuration (pbsHost + accountId) to continue **/ + isBidRequestValid: (bid) => bid.params?.placementId && getBidderConfig([bid]).complete, + + /** Trigger impression-pixel */ + onBidWon: ({pbsWurl}) => pbsWurl && triggerPixel(pbsWurl), + + /** Build BidRequest for PBS */ + buildRequests(bidRequests, bidderRequest) { + const { bidder } = bidRequests[0]; + const cfg = getBidderConfig(bidRequests); + const data = converter.toORTB({bidRequests, bidderRequest}); + + /** Set tmax, in general this will be timeout - pbsBufferMs */ + const pbjsTimeout = bidderRequest.timeout || 1000; + data.tmax = Math.min(Math.max(pbjsTimeout - cfg.pbsBufferMs, cfg.pbsBufferMs), pbjsTimeout); + + delete data.ext?.prebid?.aliases; // We don't need/want to send aliases to PBS + deepSetValue(data, 'ext.relevant', { + ...data.ext?.relevant, + adapter: true, // For internal analytics + }); + deepSetValue(data, 'ext.prebid.storedrequest.id', cfg.accountId); + data.ext.prebid.passthrough = { + ...data.ext.prebid.passthrough, + relevant: { bidder }, // to find config for the right bidder-code in interpretResponse / getUserSyncs + }; + return [{ + method: 'POST', + url: `${cfg.pbsHost}/openrtb2/auction`, + data + }]; + }, + + /** Read BidResponse from PBS and make necessary adjustments to not make it appear to come from unknown bidders */ + interpretResponse(response, request) { + const resp = deepClone(response.body); + const { bidder } = request.data.ext.prebid.passthrough.relevant; + + // Modify response times / errors for actual PBS bidders into a single value + const MODIFIERS = { + responsetimemillis: (values) => Math.max(...values), + errors: (values) => [].concat(...values), + }; + Object.entries(MODIFIERS).forEach(([field, combineFn]) => { + const obj = resp.ext?.[field]; + if (!isEmpty(obj)) { + resp.ext[field] = {[bidder]: combineFn(Object.values(obj))}; + } + }); + + const bids = converter.fromORTB({response: resp, request: request.data}).bids; + return bids; + }, + + /** Do syncing, but avoid running the sync > 1 time for S2S bidders */ + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return []; + } + const syncs = []; + serverResponses.forEach(({ body }) => { + const { pbsHost, syncedBidders } = configByBidder[body.ext.prebid.passthrough.relevant.bidder] || {}; + if (!pbsHost) { + return; + } + const { gdprApplies, consentString } = gdprConsent || {}; + let bidders = Object.keys(body.ext?.responsetimemillis || {}); + bidders = bidders.reduce((acc, curr) => { + if (!syncedBidders[curr]) { + acc.push(curr); + syncedBidders[curr] = true; + } + return acc; + }, []); + bidders = shuffle(bidders).slice(0, MAX_SYNC_COUNT); // Shuffle to not always leave out the same bidders + if (!bidders.length) { + return; // All bidders already synced + } + if (syncOptions.iframeEnabled) { + const params = { + endpoint: `${pbsHost}/cookie_sync`, + max_sync_count: bidders.length, + gdpr: gdprApplies ? 1 : 0, + gdpr_consent: consentString, + us_privacy: uspConsent, + bidders: bidders.join(','), + }; + const qs = Object.entries(params) + .filter(([k, v]) => ![null, undefined, ''].includes(v)) + .map(([k, v]) => `${k}=${encodeURIComponent(v.toString())}`) + .join('&'); + syncs.push({ type: 'iframe', url: `${SYNC_HTML}?${qs}` }); + } else { // Else, try to pixel-sync (for future-compatibility) + const pixels = deepAccess(body, `ext.relevant.sync`, []).filter(({ type }) => type === 'redirect'); + syncs.push(...pixels.map(({ url }) => ({ type: 'image', url }))); + } + }); + return syncs; + }, +}; + +registerBidder(spec); diff --git a/modules/relevantdigitalBidAdapter.md b/modules/relevantdigitalBidAdapter.md new file mode 100644 index 00000000000..d54e3e95137 --- /dev/null +++ b/modules/relevantdigitalBidAdapter.md @@ -0,0 +1,117 @@ +# Overview + +``` +Module Name: Relevant Digital Bid Adapter +Module Type: Bidder Adapter +Maintainer: support@relevant-digital.com +``` + +# Description + +This adapter is used for integration with providers using the **[Relevant Yield](https://www.relevant-digital.com/relevantyield)** platform. The provider will supply the necessary **pbsHost** and **accountId** settings along with the **placementId** bid parameters per ad unit. + +# Example setup using pbjs.setConfig() +This is the recommended method to set the global configuration parameters. +```javascript +pbjs.setConfig({ + relevantdigital: { + pbsHost: 'pbs-example.relevant-digital.com', + accountId: '6204e5fa70e3ad10821b84ff', + }, +}); + +var adUnits = [ + { + code: 'test-div', + mediaTypes: { banner: { sizes: [[300, 250], [320, 320]] }}, + bids: [ + { + bidder: 'relevantdigital', + params: { + placementId: '6204e83a077c5825441b8508_620f9e8e4fe67c1f87cd30ed', + } + } + ], + } +]; +``` +# Example setup using only bid params +This method to set the global configuration parameters (like **pbsHost**) in **params** could simplify integration of a provider for some publishers. Setting different global config-parameters on different bids is not supported in general*, as the first settings found will be used and any subsequent global settings will be ignored. + +  * _The exception is `useSourceBidderCode` which can be overriden individually per ad unit._ +```javascript +var adUnits = [ + { + code: 'test-div', + mediaTypes: { banner: { sizes: [[300, 250], [320, 320]] }}, + bids: [ + { + bidder: 'relevantdigital', + params: { + placementId: '6204e83a077c5825441b8508_620f9e8e4fe67c1f87cd30ed', + pbsHost: 'pbs-example.relevant-digital.com', + accountId: '6204e5fa70e3ad10821b84ff', + } + } + ], + } +]; +``` + +# Example setup with multiple providers +**Notice:** Placements below are _not_ live test placements +```javascript + +pbjs.aliasBidder('relevantdigital', 'providerA'); +pbjs.aliasBidder('relevantdigital', 'providerB'); + +pbjs.setConfig({ + providerA: { + pbsHost: 'pbs-example-a.relevant-digital.com', + accountId: '620533ae7f5bbe1691bbb815', + }, + providerB: { + pbsHost: 'pbs-example-b.relevant-digital.com', + accountId: '990533ae7f5bbe1691bbb815', + }, +}); + +var adUnits = [ + { + code: 'test-div', + mediaTypes: { banner: { sizes: [[300, 250], [320, 320]] }}, + bids: [ + { + bidder: 'providerA', + params: { + placementId: '610525862d7517bfd4bbb81e_620523b7d1dbed6b0fbbb817', + } + }, + { + bidder: 'providerB', + params: { + placementId: '990525862d7517bfd4bbb81e_770523b7d1dbed6b0fbbb817', + } + }, + ], + } +]; +``` + +# Bid Parameters + +| Name | Scope | Description | Example | Type | +|---------------|----------|---------------------------------------------------------|----------------------------|--------------| +| `placementId` | required | The placement id. | `'6204e83a077c5825441b8508_620f9e8e4fe67c1f87cd30ed'` | `String` | +| `pbsHost` | required if not set in config | Host name of the server. | `'pbs-example.relevant-digital.com'` | `String` | +| `accountId` | required if not set in config | The account id. | `'6204e5fa70e3ad10821b84ff'` | `String` | +| `useSourceBidderCode` | optional | Set to `true` in order to use the bidder code of the actual server-side bidder in bid responses. You **MUST** also use `allowAlternateBidderCodes: true` in `bidderSettings` if you enabled this - as otherwise the bids will be rejected.| `true` | `Boolean` | + +# Config Parameters + +| Name | Scope | Description | Example | Type | +|---------------|----------|---------------------------------------------------------|----------------------------|--------------| +| `pbsHost` | required if not set in bid parameters | Host name of the server. | `'pbs-example.relevant-digital.com'` | `String` | +| `accountId` | required if not set in bid parameters | The account id. | `'6204e5fa70e3ad10821b84ff'` | `String` | +| `pbsBufferMs` | optional | How much less in *milliseconds* the server's internal timeout should be compared to the normal Prebid timeout. Default is *250*. To be increased in cases of frequent timeouts. | `250` | `Integer` | +| `useSourceBidderCode` | optional | Set to `true` in order to use the bidder code of the actual server-side bidder in bid responses. You **MUST** also use `allowAlternateBidderCodes: true` in `bidderSettings` if you enabled this - as otherwise the bids will be rejected.| `true` | `Boolean` | diff --git a/test/spec/modules/relevantdigitalBidAdapter_spec.js b/test/spec/modules/relevantdigitalBidAdapter_spec.js new file mode 100644 index 00000000000..b2a5495b3cb --- /dev/null +++ b/test/spec/modules/relevantdigitalBidAdapter_spec.js @@ -0,0 +1,295 @@ +import {spec, resetBidderConfigs} from 'modules/relevantdigitalBidAdapter.js'; +import { parseUrl, deepClone } from 'src/utils.js'; + +const expect = require('chai').expect; + +const PBS_HOST = 'dev-api.relevant-digital.com'; +const PLACEMENT_ID = 'example_placement_id'; +const ACCOUNT_ID = 'example_account_id'; +const TEST_DOMAIN = 'example.com'; +const TEST_PAGE = `https://${TEST_DOMAIN}/page.html`; + +const BID_REQUEST = +{ + 'bidder': 'relevantdigital', + 'params': { + 'placementId': PLACEMENT_ID, + 'accountId': ACCOUNT_ID, + 'pbsHost': PBS_HOST, + }, + 'ortb2Imp': { + 'ext': { + 'tid': 'e13391ea-00f3-495d-99a6-d937990d73a9' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': 'e13391ea-00f3-495d-99a6-d937990d73a9', + 'sizes': [ + [ + 300, + 250 + ], + ], + 'bidId': '2d69406037a662', + 'bidderRequestId': '1decd098c76ed2', + 'auctionId': '251a6a36-a5c5-4b82-b2b3-538c148a29dd', + 'src': 'client', + 'metrics': { + 'requestBids.validate': 0.7, + 'requestBids.makeRequests': 2.9, + 'adapter.client.validate': 0.4, + 'adapters.client.relevantdigital.validate': 0.4 + }, + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'page': TEST_PAGE, + 'domain': TEST_DOMAIN, + 'publisher': { + 'domain': 'relevant-digital.com' + } + }, + 'device': { + 'w': 1848, + 'h': 1007, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', + 'language': 'en', + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Linux', + 'version': [ + '5', + '4', + '0' + ] + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '111', + '0', + '5563', + '146' + ] + }, + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } +}; + +const BIDDER_REQUEST = { + 'bidderCode': BID_REQUEST.bidder, + 'auctionId': BID_REQUEST.auctionId, + 'bidderRequestId': BID_REQUEST.bidderRequestId, + 'bids': [BID_REQUEST], + 'metrics': BID_REQUEST.metrics, + 'ortb2': BID_REQUEST.ortb2, + 'auctionStart': 1681224591370, + 'timeout': 1000, + 'refererInfo': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + TEST_PAGE + ], + 'topmostLocation': TEST_PAGE, + 'location': TEST_PAGE, + 'canonicalUrl': null, + 'page': TEST_PAGE, + 'domain': TEST_DOMAIN, + 'ref': null, + 'legacy': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + TEST_PAGE + ], + 'referer': TEST_PAGE, + 'canonicalUrl': null + } + }, + 'start': 1681224591375 +}; + +const BID_RESPONSE = { + 'seatbid': [ + { + 'bid': [ + { + 'id': '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + 'impid': BID_REQUEST.bidId, + 'price': 10.76091063668997, + 'adm': '', + 'adomain': [ + 'www.addomain.com' + ], + 'iurl': 'http://localhost11', + 'crid': 'creative111', + 'w': 300, + 'h': 250, + 'ext': { + 'bidtype': 0, + 'dspid': 6, + 'origbidcpm': 1, + 'origbidcur': 'USD', + 'prebid': { + 'meta': { + 'adaptercode': 'pubmatic' + }, + 'targeting': { + 'hb_bidder': 'pubmatic', + 'hb_cache_host': PBS_HOST, + 'hb_cache_path': '/analytics_cache/read', + 'hb_format': 'banner', + 'hb_pb': '10.70', + 'hb_size': '300x250' + }, + 'type': 'banner', + 'video': { + 'duration': 0, + 'primary_category': '' + }, + 'events': { + 'win': `https://${PBS_HOST}/event?t=win&b=fed970f7-4295-456d-a251-38013faab795&a=620523ae7f4bbe1691bbb815&bidder=pubmatic&ts=1678646619765`, + 'imp': `https://${PBS_HOST}/event?t=imp&b=fed970f7-4295-456d-a251-38013faab795&a=620523ae7f4bbe1691bbb815&bidder=pubmatic&ts=1678646619765` + }, + 'bidid': 'fed970f7-4295-456d-a251-38013faab795' + } + } + } + ], + 'seat': 'pubmatic' + } + ], + 'cur': 'SEK', + 'ext': { + 'responsetimemillis': { + 'appnexus': 305, + 'pubmatic': 156 + }, + 'tmaxrequest': 750, + 'relevant': { + 'sync': [ + { 'type': 'redirect', 'url': 'https://example1.com/sync' }, + { 'type': 'redirect', 'url': 'https://example2.com/sync' }, + ], + }, + 'prebid': { + 'auctiontimestamp': 1678646619765, + 'passthrough': { + 'relevant': { + 'bidder': spec.code + } + } + } + } +}; + +const S2S_RESPONSE_BIDDER = BID_RESPONSE.seatbid[0].seat; + +const resetAndBuildRequest = (params) => { + resetBidderConfigs(); + const bidRequest = { + ...BID_REQUEST, + params: { + ...BID_REQUEST.params, + ...params, + }, + }; + return spec.buildRequests([bidRequest], BIDDER_REQUEST); +}; + +describe('Relevant Digital Bid Adaper', function () { + describe('buildRequests', () => { + const [request] = resetAndBuildRequest(); + const {data, url} = request + it('should give the correct URL', () => { + expect(url).equal(`https://${PBS_HOST}/openrtb2/auction`); + }); + it('should set the correct stored request ids', () => { + expect(data.ext.prebid.storedrequest.id).equal(ACCOUNT_ID); + expect(data.imp[0].ext.prebid.storedrequest.id).equal(PLACEMENT_ID); + }); + it('should include bidder code in passthrough object', () => { + expect(data.ext.prebid.passthrough.relevant.bidder).equal(spec.code); + }); + it('should set tmax to something below the timeout', () => { + expect(data.tmax).be.greaterThan(0); + expect(data.tmax).be.lessThan(BIDDER_REQUEST.timeout) + }); + }); + describe('interpreteResponse', () => { + const [request] = resetAndBuildRequest(); + const [bid] = spec.interpretResponse({ body: BID_RESPONSE }, request); + it('should not have S2S bidder\'s bidder code', () => { + expect(bid.bidderCode).not.equal(S2S_RESPONSE_BIDDER); + }); + it('should return the right creative content', () => { + const respBid = BID_RESPONSE.seatbid[0].bid[0]; + expect(bid.cpm).equal(respBid.price); + expect(bid.ad).equal(respBid.adm); + expect(bid.width).equal(respBid.w); + expect(bid.height).equal(respBid.h); + }); + }); + describe('interpreteResponse with useSourceBidderCode', () => { + const [request] = resetAndBuildRequest({ useSourceBidderCode: true }); + const [bid] = spec.interpretResponse({ body: BID_RESPONSE }, request); + it('should have S2S bidder\'s code', () => { + expect(bid.bidderCode).equal(S2S_RESPONSE_BIDDER); + }); + }); + describe('getUserSyncs with iframeEnabled', () => { + resetAndBuildRequest() + const allSyncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: BID_RESPONSE }], null, null); + const [{ url, type }] = allSyncs; + const { bidders, endpoint } = parseUrl(url).search; + it('should return a single sync object', () => { + expect(allSyncs.length).equal(1); + }); + it('should use iframe sync when available', () => { + expect(type).equal('iframe'); + }); + it('should sync to all s2s bidders', () => { + expect(bidders.split(',').sort()).to.deep.equal(['appnexus', 'pubmatic']); + }); + it('should sync to the right endpoint', () => { + expect(endpoint).equal(`https://${PBS_HOST}/cookie_sync`); + }); + it('should not sync to the same s2s bidders when called again', () => { + const newSyncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: BID_RESPONSE }], null, null); + expect(newSyncs).to.deep.equal([]); + }); + }); + describe('getUserSyncs with pixelEnabled', () => { + resetAndBuildRequest() + const responseSyncs = BID_RESPONSE.ext.relevant.sync; + const allSyncs = spec.getUserSyncs({ pixelEnabled: true }, [{ body: BID_RESPONSE }], null, null); + it('should return one sync object per pixel', () => { + const expectedResult = responseSyncs.map(({ url }) => ({url, type: 'image'})); + expect(allSyncs).to.deep.equal(expectedResult) + }); + }); +}); From 16fe0cf405db2b233596f89687c8dbfc7ab0fd83 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 4 May 2023 13:30:47 +0200 Subject: [PATCH 371/375] Greenbids RTD provider (#9848) --- modules/greenbidsRtdProvider.js | 110 +++++++++ modules/greenbidsRtdProvider.md | 65 ++++++ .../spec/modules/greenbidsRtdProvider_spec.js | 210 ++++++++++++++++++ 3 files changed, 385 insertions(+) create mode 100644 modules/greenbidsRtdProvider.js create mode 100644 modules/greenbidsRtdProvider.md create mode 100644 test/spec/modules/greenbidsRtdProvider_spec.js diff --git a/modules/greenbidsRtdProvider.js b/modules/greenbidsRtdProvider.js new file mode 100644 index 00000000000..ef12326cf18 --- /dev/null +++ b/modules/greenbidsRtdProvider.js @@ -0,0 +1,110 @@ +import { logError } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; + +const MODULE_NAME = 'greenbidsRtdProvider'; +const MODULE_VERSION = '1.0.0'; +const ENDPOINT = 'https://europe-west1-greenbids-357713.cloudfunctions.net/partner-selection'; + +const auctionInfo = {}; +const rtdOptions = {}; + +function init(moduleConfig) { + let params = moduleConfig?.params; + if (!params?.pbuid) { + logError('Greenbids pbuid is not set!'); + return false; + } else { + rtdOptions.pbuid = params?.pbuid; + rtdOptions.targetTPR = params?.targetTPR || 0.99; + rtdOptions.timeout = params?.timeout || 200; + return true; + } +} + +function onAuctionInitEvent(auctionDetails) { + auctionInfo.auctionId = auctionDetails.auctionId; +} + +function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + let promise = createPromise(reqBidsConfigObj); + promise.then(callback); +} + +function createPromise(reqBidsConfigObj) { + return new Promise((resolve) => { + const timeoutId = setTimeout(() => { + resolve(reqBidsConfigObj); + }, rtdOptions.timeout); + ajax( + ENDPOINT, + { + success: (response) => { + processSuccessResponse(response, timeoutId, reqBidsConfigObj); + resolve(reqBidsConfigObj); + }, + error: () => { + clearTimeout(timeoutId); + resolve(reqBidsConfigObj); + }, + }, + createPayload(reqBidsConfigObj), + { contentType: 'application/json' } + ); + }); +} + +function processSuccessResponse(response, timeoutId, reqBidsConfigObj) { + clearTimeout(timeoutId); + const responseAdUnits = JSON.parse(response); + + updateAdUnitsBasedOnResponse(reqBidsConfigObj.adUnits, responseAdUnits); +} + +function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits) { + adUnits.forEach((adUnit) => { + const matchingAdUnit = findMatchingAdUnit(responseAdUnits, adUnit.code); + if (matchingAdUnit) { + removeFalseBidders(adUnit, matchingAdUnit); + } + }); +} + +function findMatchingAdUnit(responseAdUnits, adUnitCode) { + return responseAdUnits.find((responseAdUnit) => responseAdUnit.code === adUnitCode); +} + +function removeFalseBidders(adUnit, matchingAdUnit) { + const falseBidders = getFalseBidders(matchingAdUnit.bidders); + adUnit.bids = adUnit.bids.filter((bidRequest) => !falseBidders.includes(bidRequest.bidder)); +} + +function getFalseBidders(bidders) { + return Object.entries(bidders) + .filter(([bidder, shouldKeep]) => !shouldKeep) + .map(([bidder]) => bidder); +} + +function createPayload(reqBidsConfigObj) { + return JSON.stringify({ + auctionId: auctionInfo.auctionId, + version: MODULE_VERSION, + referrer: window.location.href, + prebid: '$prebid.version$', + rtdOptions: rtdOptions, + adUnits: reqBidsConfigObj.adUnits, + }); +} + +export const greenbidsSubmodule = { + name: MODULE_NAME, + init: init, + onAuctionInitEvent: onAuctionInitEvent, + getBidRequestData: getBidRequestData, + updateAdUnitsBasedOnResponse: updateAdUnitsBasedOnResponse, + findMatchingAdUnit: findMatchingAdUnit, + removeFalseBidders: removeFalseBidders, + getFalseBidders: getFalseBidders, +}; + +submodule('realTimeData', greenbidsSubmodule); diff --git a/modules/greenbidsRtdProvider.md b/modules/greenbidsRtdProvider.md new file mode 100644 index 00000000000..85b8f5a7859 --- /dev/null +++ b/modules/greenbidsRtdProvider.md @@ -0,0 +1,65 @@ +# Overview + +``` +Module Name: Greenbids RTD Provider +Module Type: RTD Provider +Maintainer: jb@greenbids.ai +``` + +# Description + +The Greenbids RTD adapter allows to dynamically filter calls to SSP to reduce outgoing call to the programmatics chain, reducing ad serving carbon impact + +## Configuration + +This module is configured as part of the `realTimeData.dataProviders` object. + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +|------------|----------|----------------------------------------|---------------|----------| +| `name ` | required | Real time data module name | `'greenbidsRtdProvider'` | `string` | +| `waitForIt ` | required (mandatory true value) | Tells prebid auction to wait for the result of this module | `'true'` | `boolean` | +| `params` | required | | | `Object` | +| `params.pbuid` | required | The client site id provided by Greenbids. | `'TEST_FROM_GREENBIDS'` | `string` | +| `params.targetTPR` | optional (default 0.95) | Target True positive rate for the throttling model | `0.99` | `[0-1]` | +| `params.timeout` | optional (default 200) | Maximum amount of milliseconds allowed for module to finish working (has to be <= to the realTimeData.auctionDelay property) | `200` | `number` | + +#### Example + +```javascript +const greenbidsDataProvider = { + name: 'greenbidsRtdProvider', + waitForIt: true, + params: { + pbuid: 'TEST_FROM_GREENBIDS', + timeout: 200 + } +}; + +pbjs.setConfig({ + realTimeData: { + auctionDelay: 200, + dataProviders: [greenbidsDataProvider] + } +}); +``` + +## Integration +To install the module, follow these instructions: + +#### Step 1: Contact Greenbids to get a pbuid and account + +#### Step 2: Integrate the Greenbids Analytics Adapter + +Greenbids RTD module works hand in hand with Greenbids Analytics module +See prebid Analytics modules -> Greenbids Analytics module + +#### Step 3: Prepare the base Prebid file + +- Option 1: Use Prebid [Download](/download.html) page to build the prebid package. Ensure that you do check *Greenbids RTD Provider* module + +- Option 2: From the command line, run `gulp build --modules=greenbidsRtdProvider,...` + +#### Step 4: Set configuration + +Enable Greenbids Real Time Module using `pbjs.setConfig`. Example is provided in Configuration section. diff --git a/test/spec/modules/greenbidsRtdProvider_spec.js b/test/spec/modules/greenbidsRtdProvider_spec.js new file mode 100644 index 00000000000..6b7d826ab06 --- /dev/null +++ b/test/spec/modules/greenbidsRtdProvider_spec.js @@ -0,0 +1,210 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { + deepClone, +} from '../../../src/utils.js'; +import { + greenbidsSubmodule +} from 'modules/greenbidsRtdProvider.js'; + +describe('greenbidsRtdProvider', () => { + let server; + + beforeEach(() => { + server = sinon.createFakeServer(); + }); + + afterEach(() => { + server.restore(); + }); + + const endPoint = 'europe-west1-greenbids-357713.cloudfunctions.net'; + + const SAMPLE_MODULE_CONFIG = { + params: { + pbuid: '12345', + timeout: 200, + targetTPR: 0.95 + } + }; + + const SAMPLE_REQUEST_BIDS_CONFIG_OBJ = { + adUnits: [ + { + code: 'adUnit1', + bids: [ + { bidder: 'appnexus', params: {} }, + { bidder: 'rubicon', params: {} }, + { bidder: 'ix', params: {} } + ] + }, + { + code: 'adUnit2', + bids: [ + { bidder: 'appnexus', params: {} }, + { bidder: 'rubicon', params: {} }, + { bidder: 'openx', params: {} } + ] + }] + }; + + const SAMPLE_RESPONSE_ADUNITS = [ + { + code: 'adUnit1', + bidders: { + 'appnexus': true, + 'rubicon': false, + 'ix': true + } + }, + { + code: 'adUnit2', + bidders: { + 'appnexus': false, + 'rubicon': true, + 'openx': true + } + }]; + + describe('init', () => { + it('should return true and set rtdOptions if pbuid is present', () => { + const result = greenbidsSubmodule.init(SAMPLE_MODULE_CONFIG); + expect(result).to.be.true; + }); + + it('should return false if pbuid is not present', () => { + const result = greenbidsSubmodule.init({ params: {} }); + expect(result).to.be.false; + }); + }); + + describe('updateAdUnitsBasedOnResponse', () => { + it('should update ad units based on response', () => { + const adUnits = JSON.parse(JSON.stringify(SAMPLE_REQUEST_BIDS_CONFIG_OBJ.adUnits)); + greenbidsSubmodule.updateAdUnitsBasedOnResponse(adUnits, SAMPLE_RESPONSE_ADUNITS); + + expect(adUnits[0].bids).to.have.length(2); + expect(adUnits[1].bids).to.have.length(2); + }); + }); + + describe('findMatchingAdUnit', () => { + it('should find matching ad unit by code', () => { + const matchingAdUnit = greenbidsSubmodule.findMatchingAdUnit(SAMPLE_RESPONSE_ADUNITS, 'adUnit1'); + expect(matchingAdUnit).to.deep.equal(SAMPLE_RESPONSE_ADUNITS[0]); + }); + it('should return undefined if no matching ad unit is found', () => { + const matchingAdUnit = greenbidsSubmodule.findMatchingAdUnit(SAMPLE_RESPONSE_ADUNITS, 'nonexistent'); + expect(matchingAdUnit).to.be.undefined; + }); + }); + + describe('removeFalseBidders', () => { + it('should remove bidders with false value', () => { + const adUnit = JSON.parse(JSON.stringify(SAMPLE_REQUEST_BIDS_CONFIG_OBJ.adUnits[0])); + const matchingAdUnit = SAMPLE_RESPONSE_ADUNITS[0]; + greenbidsSubmodule.removeFalseBidders(adUnit, matchingAdUnit); + expect(adUnit.bids).to.have.length(2); + expect(adUnit.bids.map((bid) => bid.bidder)).to.not.include('rubicon'); + }); + }); + + describe('getFalseBidders', () => { + it('should return an array of false bidders', () => { + const bidders = { + appnexus: true, + rubicon: false, + ix: true, + openx: false + }; + const falseBidders = greenbidsSubmodule.getFalseBidders(bidders); + expect(falseBidders).to.have.length(2); + expect(falseBidders).to.include('rubicon'); + expect(falseBidders).to.include('openx'); + }); + }); + + describe('getBidRequestData', () => { + it('Callback is called if the server responds a 200 within the time limit', (done) => { + let requestBids = deepClone(SAMPLE_REQUEST_BIDS_CONFIG_OBJ); + let callback = sinon.stub(); + + greenbidsSubmodule.getBidRequestData(requestBids, callback, SAMPLE_MODULE_CONFIG); + + setTimeout(() => { + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(SAMPLE_RESPONSE_ADUNITS) + ); + done(); + }, 50); + + setTimeout(() => { + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq(endPoint); + expect(requestBids.adUnits[0].bids).to.have.length(2); + expect(requestBids.adUnits[0].bids.map((bid) => bid.bidder)).to.not.include('rubicon'); + expect(requestBids.adUnits[0].bids.map((bid) => bid.bidder)).to.include('ix'); + expect(requestBids.adUnits[0].bids.map((bid) => bid.bidder)).to.include('appnexus'); + expect(requestBids.adUnits[1].bids).to.have.length(2); + expect(requestBids.adUnits[1].bids.map((bid) => bid.bidder)).to.not.include('appnexus'); + expect(requestBids.adUnits[1].bids.map((bid) => bid.bidder)).to.include('rubicon'); + expect(requestBids.adUnits[1].bids.map((bid) => bid.bidder)).to.include('openx'); + expect(callback.calledOnce).to.be.true; + }, 60); + }); + }); + + describe('getBidRequestData', () => { + it('Nothing changes if the server times out but still the callback is called', (done) => { + let requestBids = deepClone(SAMPLE_REQUEST_BIDS_CONFIG_OBJ); + let callback = sinon.stub(); + + greenbidsSubmodule.getBidRequestData(requestBids, callback, SAMPLE_MODULE_CONFIG); + + setTimeout(() => { + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(SAMPLE_RESPONSE_ADUNITS) + ); + done(); + }, 300); + + setTimeout(() => { + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq(endPoint); + expect(requestBids.adUnits[0].bids).to.have.length(3); + expect(requestBids.adUnits[1].bids).to.have.length(3); + expect(callback.calledOnce).to.be.true; + }, 200); + }); + }); + + describe('getBidRequestData', () => { + it('callback is called if the server responds a 500 error within the time limit and no changes are made', (done) => { + let requestBids = deepClone(SAMPLE_REQUEST_BIDS_CONFIG_OBJ); + let callback = sinon.stub(); + + greenbidsSubmodule.getBidRequestData(requestBids, callback, SAMPLE_MODULE_CONFIG); + + setTimeout(() => { + server.requests[0].respond( + 500, + {'Content-Type': 'application/json'}, + JSON.stringify({'failure': 'fail'}) + ); + done(); + }, 50); + + setTimeout(() => { + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq(endPoint); + expect(requestBids.adUnits[0].bids).to.have.length(3); + expect(requestBids.adUnits[1].bids).to.have.length(3); + expect(callback.calledOnce).to.be.true; + }, 60); + }); + }); +}); From 27c817ed6cbd7db1496771cd703d80145210d06e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 May 2023 04:45:10 -0700 Subject: [PATCH 372/375] Bump engine.io and socket.io (#9903) Bumps [engine.io](https://github.com/socketio/engine.io) and [socket.io](https://github.com/socketio/socket.io). These dependencies needed to be updated together. Updates `engine.io` from 6.2.1 to 6.4.2 - [Release notes](https://github.com/socketio/engine.io/releases) - [Changelog](https://github.com/socketio/engine.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/engine.io/compare/6.2.1...6.4.2) Updates `socket.io` from 4.5.3 to 4.6.1 - [Release notes](https://github.com/socketio/socket.io/releases) - [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io/compare/4.5.3...4.6.1) --- updated-dependencies: - dependency-name: engine.io dependency-type: indirect - dependency-name: socket.io dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 110 +++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index 614efa22f2b..c2333b83807 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "7.42.0-pre", + "version": "7.48.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -2175,10 +2175,13 @@ "dev": true }, "node_modules/@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/debug": { "version": "4.1.7", @@ -8885,9 +8888,9 @@ } }, "node_modules/engine.io": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", - "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", + "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", "dev": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -8899,16 +8902,16 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" + "ws": "~8.11.0" }, "engines": { "node": ">=10.0.0" } }, "node_modules/engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -21639,27 +21642,30 @@ } }, "node_modules/socket.io": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz", - "integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", "dev": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.2", - "engine.io": "~6.2.0", - "socket.io-adapter": "~2.4.0", - "socket.io-parser": "~4.2.0" + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", - "dev": true + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "dependencies": { + "ws": "~8.11.0" + } }, "node_modules/socket.io-parser": { "version": "4.2.1", @@ -24999,9 +25005,9 @@ } }, "node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "dev": true, "engines": { "node": ">=10.0.0" @@ -26730,10 +26736,13 @@ "dev": true }, "@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/debug": { "version": "4.1.7", @@ -32095,9 +32104,9 @@ } }, "engine.io": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", - "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", + "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -32109,7 +32118,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" + "ws": "~8.11.0" }, "dependencies": { "cookie": { @@ -32121,9 +32130,9 @@ } }, "engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", "dev": true }, "enhanced-resolve": { @@ -41990,24 +41999,27 @@ } }, "socket.io": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz", - "integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", "dev": true, "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.2", - "engine.io": "~6.2.0", - "socket.io-adapter": "~2.4.0", - "socket.io-parser": "~4.2.0" + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" } }, "socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", - "dev": true + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "requires": { + "ws": "~8.11.0" + } }, "socket.io-parser": { "version": "4.2.1", @@ -44627,9 +44639,9 @@ } }, "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "dev": true, "requires": {} }, From 0ac5d66a9807f60f0de81c34c349162b954979d5 Mon Sep 17 00:00:00 2001 From: Matt Crute <872334+mbcrute@users.noreply.github.com> Date: Thu, 4 May 2023 08:01:12 -0400 Subject: [PATCH 373/375] support VIDEO feature flag in TTD bid adapter (#9878) --- modules/ttdBidAdapter.js | 144 ++++++++++++------------ test/spec/modules/ttdBidAdapter_spec.js | 20 ++++ 2 files changed, 93 insertions(+), 71 deletions(-) diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index b9e209f7cd7..ce803aa72ad 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -185,7 +185,7 @@ function getImpression(bidRequest) { if (mediaTypesBanner) { mediaTypes[BANNER] = banner(bidRequest); } - if (mediaTypesVideo) { + if (FEATURES.VIDEO && mediaTypesVideo) { mediaTypes[VIDEO] = video(bidRequest); } @@ -248,79 +248,81 @@ function banner(bid) { } function video(bid) { - let minduration = utils.deepAccess(bid, 'mediaTypes.video.minduration'); - const maxduration = utils.deepAccess(bid, 'mediaTypes.video.maxduration'); - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - const api = utils.deepAccess(bid, 'mediaTypes.video.api'); - const mimes = utils.deepAccess(bid, 'mediaTypes.video.mimes'); - const placement = utils.deepAccess(bid, 'mediaTypes.video.placement'); - const plcmt = utils.deepAccess(bid, 'mediaTypes.video.plcmt'); - const protocols = utils.deepAccess(bid, 'mediaTypes.video.protocols'); - const playbackmethod = utils.deepAccess(bid, 'mediaTypes.video.playbackmethod'); - const pos = utils.deepAccess(bid, 'mediaTypes.video.pos'); - const startdelay = utils.deepAccess(bid, 'mediaTypes.video.startdelay'); - const skip = utils.deepAccess(bid, 'mediaTypes.video.skip'); - const skipmin = utils.deepAccess(bid, 'mediaTypes.video.skipmin'); - const skipafter = utils.deepAccess(bid, 'mediaTypes.video.skipafter'); - const minbitrate = utils.deepAccess(bid, 'mediaTypes.video.minbitrate'); - const maxbitrate = utils.deepAccess(bid, 'mediaTypes.video.maxbitrate'); - - if (!minduration || !utils.isInteger(minduration)) { - minduration = 0; - } - let video = { - minduration: minduration, - maxduration: maxduration, - api: api, - mimes: mimes, - placement: placement, - protocols: protocols - }; + if (FEATURES.VIDEO) { + let minduration = utils.deepAccess(bid, 'mediaTypes.video.minduration'); + const maxduration = utils.deepAccess(bid, 'mediaTypes.video.maxduration'); + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const api = utils.deepAccess(bid, 'mediaTypes.video.api'); + const mimes = utils.deepAccess(bid, 'mediaTypes.video.mimes'); + const placement = utils.deepAccess(bid, 'mediaTypes.video.placement'); + const plcmt = utils.deepAccess(bid, 'mediaTypes.video.plcmt'); + const protocols = utils.deepAccess(bid, 'mediaTypes.video.protocols'); + const playbackmethod = utils.deepAccess(bid, 'mediaTypes.video.playbackmethod'); + const pos = utils.deepAccess(bid, 'mediaTypes.video.pos'); + const startdelay = utils.deepAccess(bid, 'mediaTypes.video.startdelay'); + const skip = utils.deepAccess(bid, 'mediaTypes.video.skip'); + const skipmin = utils.deepAccess(bid, 'mediaTypes.video.skipmin'); + const skipafter = utils.deepAccess(bid, 'mediaTypes.video.skipafter'); + const minbitrate = utils.deepAccess(bid, 'mediaTypes.video.minbitrate'); + const maxbitrate = utils.deepAccess(bid, 'mediaTypes.video.maxbitrate'); + + if (!minduration || !utils.isInteger(minduration)) { + minduration = 0; + } + let video = { + minduration: minduration, + maxduration: maxduration, + api: api, + mimes: mimes, + placement: placement, + protocols: protocols + }; - if (typeof playerSize !== 'undefined') { - if (utils.isArray(playerSize[0])) { - video.w = parseInt(playerSize[0][0]); - video.h = parseInt(playerSize[0][1]); - } else if (utils.isNumber(playerSize[0])) { - video.w = parseInt(playerSize[0]); - video.h = parseInt(playerSize[1]); + if (typeof playerSize !== 'undefined') { + if (utils.isArray(playerSize[0])) { + video.w = parseInt(playerSize[0][0]); + video.h = parseInt(playerSize[0][1]); + } else if (utils.isNumber(playerSize[0])) { + video.w = parseInt(playerSize[0]); + video.h = parseInt(playerSize[1]); + } } - } - if (playbackmethod) { - video.playbackmethod = playbackmethod; - } - if (plcmt) { - video.plcmt = plcmt; - } - if (pos) { - video.pos = pos; - } - if (startdelay && utils.isInteger(startdelay)) { - video.startdelay = startdelay; - } - if (skip && (skip === 0 || skip === 1)) { - video.skip = skip; - } - if (skipmin && utils.isInteger(skipmin)) { - video.skipmin = skipmin; - } - if (skipafter && utils.isInteger(skipafter)) { - video.skipafter = skipafter; - } - if (minbitrate && utils.isInteger(minbitrate)) { - video.minbitrate = minbitrate; - } - if (maxbitrate && utils.isInteger(maxbitrate)) { - video.maxbitrate = maxbitrate; - } + if (playbackmethod) { + video.playbackmethod = playbackmethod; + } + if (plcmt) { + video.plcmt = plcmt; + } + if (pos) { + video.pos = pos; + } + if (startdelay && utils.isInteger(startdelay)) { + video.startdelay = startdelay; + } + if (skip && (skip === 0 || skip === 1)) { + video.skip = skip; + } + if (skipmin && utils.isInteger(skipmin)) { + video.skipmin = skipmin; + } + if (skipafter && utils.isInteger(skipafter)) { + video.skipafter = skipafter; + } + if (minbitrate && utils.isInteger(minbitrate)) { + video.minbitrate = minbitrate; + } + if (maxbitrate && utils.isInteger(maxbitrate)) { + video.maxbitrate = maxbitrate; + } - const battr = utils.deepAccess(bid, 'ortb2Imp.battr'); - if (battr) { - video.battr = battr; - } + const battr = utils.deepAccess(bid, 'ortb2Imp.battr'); + if (battr) { + video.battr = battr; + } - return video; + return video; + } } export const spec = { @@ -379,7 +381,7 @@ export const spec = { return false; } - if (mediaTypesVideo) { + if (FEATURES.VIDEO && mediaTypesVideo) { if (!mediaTypesVideo.maxduration || !utils.isInteger(mediaTypesVideo.maxduration)) { utils.logWarn(BIDDER_CODE + ': mediaTypes.video.maxduration must be set to the maximum video ad duration in seconds'); return false; @@ -506,7 +508,7 @@ export const spec = { mediaType: BANNER } ); - } else if (bid.ext.mediatype === MEDIA_TYPE.VIDEO) { + } else if (FEATURES.VIDEO && bid.ext.mediatype === MEDIA_TYPE.VIDEO) { Object.assign( bidResponse, { diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 8c0a9db8fbd..37d1bac40ee 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -104,6 +104,10 @@ describe('ttdBidAdapter', function () { }); describe('video', function () { + if (!FEATURES.VIDEO) { + return; + } + function makeBid() { return { 'bidder': 'ttd', @@ -683,6 +687,10 @@ describe('ttdBidAdapter', function () { }); describe('buildRequests-display-video-multiformat', function () { + if (!FEATURES.VIDEO) { + return; + } + const baseMultiformatBidRequests = [{ 'bidder': 'ttd', 'params': { @@ -751,6 +759,10 @@ describe('ttdBidAdapter', function () { }); describe('buildRequests-video', function () { + if (!FEATURES.VIDEO) { + return; + } + const baseVideoBidRequests = [{ 'bidder': 'ttd', 'params': { @@ -1186,6 +1198,10 @@ describe('ttdBidAdapter', function () { }); describe('interpretResponse-simple-video', function () { + if (!FEATURES.VIDEO) { + return; + } + const incoming = { 'body': { 'cur': 'USD', @@ -1318,6 +1334,10 @@ describe('ttdBidAdapter', function () { }); describe('interpretResponse-display-and-video', function () { + if (!FEATURES.VIDEO) { + return; + } + const incoming = { 'body': { 'id': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', From 045b5d204583c56921343f7e934de56c911b60ff Mon Sep 17 00:00:00 2001 From: xwang202 <57196235+xwang202@users.noreply.github.com> Date: Thu, 4 May 2023 20:44:13 +0800 Subject: [PATCH 374/375] FreeWheel SSP Bid Adapter: add GPP support (#9859) * 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 * FreeWheel SSP Bid Adapter: support video context and placement * update test * FreeWheel SSP Bid Adapter: add GPP support * Freewheel SSP Bid Adapter: test update --- modules/freewheel-sspBidAdapter.js | 39 +++++++++++++++---- .../modules/freewheel-sspBidAdapter_spec.js | 33 +++++++++++++++- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 8f5af48d16b..c0524ca3cd6 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -1,4 +1,4 @@ -import { logWarn, isArray, isFn, deepAccess } from '../src/utils.js'; +import { logWarn, isArray, isFn, deepAccess, formatQS } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; @@ -372,6 +372,15 @@ export const spec = { requestParams._fw_us_privacy = bidderRequest.uspConsent; } + // Add GPP consent + if (bidderRequest && bidderRequest.gppConsent) { + requestParams.gpp = bidderRequest.gppConsent.gppString; + requestParams.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.regs && bidderRequest.ortb2.regs.gpp) { + requestParams.gpp = bidderRequest.ortb2.regs.gpp; + requestParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + // Add schain object var schain = currentBidRequest.schain; if (schain) { @@ -526,26 +535,42 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { - var gdprParams = ''; + getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy, gppConsent) { + const params = {}; + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + params.gdpr = Number(gdprConsent.gdprApplies); + params.gdpr_consent = gdprConsent.consentString; } else { - gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + params.gdpr_consent = gdprConsent.consentString; } } + if (gppConsent) { + if (typeof gppConsent.gppString === 'string') { + params.gpp = gppConsent.gppString; + } + if (gppConsent.applicableSections) { + params.gpp_sid = gppConsent.applicableSections; + } + } + + var queryString = ''; + if (params) { + queryString = '?' + `${formatQS(params)}`; + } + const syncs = []; if (syncOptions && syncOptions.pixelEnabled) { syncs.push({ type: 'image', - url: USER_SYNC_URL + gdprParams + url: USER_SYNC_URL + queryString }); } else if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: USER_SYNC_URL + gdprParams + url: USER_SYNC_URL + queryString }); } diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 123981825dc..d8dc6b18b8f 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -224,12 +224,41 @@ describe('freewheelSSP BidAdapter Test', () => { let syncOptions = { 'pixelEnabled': true } - const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null, null); expect(userSyncs).to.deep.equal([{ type: 'image', url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' }]); }); + + it('should add gpp information to the request via bidderRequest.gppConsent', function () { + let consentString = 'abc1234'; + let bidderRequest = { + 'gppConsent': { + 'gppString': consentString, + 'applicableSections': [8] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request[0].data; + + expect(payload.gpp).to.equal(consentString); + expect(payload.gpp_sid).to.deep.equal([8]); + + let gppConsent = { + 'applicableSections': [8], + 'gppString': consentString + } + let syncOptions = { + 'pixelEnabled': true + } + const userSyncs = spec.getUserSyncs(syncOptions, null, null, null, gppConsent); + expect(userSyncs).to.deep.equal([{ + type: 'image', + url: 'https://ads.stickyadstv.com/auto-user-sync?gpp=abc1234&gpp_sid[]=8' + }]); + }); }) describe('buildRequestsForVideo', () => { @@ -318,7 +347,7 @@ describe('freewheelSSP BidAdapter Test', () => { let syncOptions = { 'pixelEnabled': true } - const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null, null); expect(userSyncs).to.deep.equal([{ type: 'image', url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' From b5cfcd96a30dd2d0fa1966cb7cf474e6f2472295 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 4 May 2023 14:02:32 +0000 Subject: [PATCH 375/375] Prebid 7.48.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c2333b83807..c4139f11afb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.48.0-pre", + "version": "7.48.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9b870eceef3..1a8348013e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.48.0-pre", + "version": "7.48.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": {