From ab9a8d276867182ff2fd191b6cd25c6182d55f55 Mon Sep 17 00:00:00 2001 From: Lemma Dev <54662130+lemmadev@users.noreply.github.com> Date: Mon, 8 Feb 2021 22:05:21 +0530 Subject: [PATCH 001/198] Lemma:set mediaType key value (#6006) * lemmaBidAdapter.js Added lemma bid adapter file * lemmaBidAdapter.md Added lemma bid adapter md file * lemmaBidAdapter_spec.js Added lemma bid adapter test spec file * Update lemmaBidAdapter.js Fixed automated code review alert comparison between inconvertible types * Update lemmaBidAdapter.js Fixed review changes * Update lemmaBidAdapter.md Correct parameter value. * Update lemmaBidAdapter.js Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter_spec.js Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter.md Lemma Bid Adapter - v3.0 compliance * Update lemmaBidAdapter.js Added user sync support into bid adapter. * updated include modules file extension. updated include modules js file extension. * Update lemmaBidAdapter_spec.js Added unit test for user sync feature. * Update lemmaBidAdapter.js Fixed format error. * Update lemmaBidAdapter_spec.js Fixed format error and typo error. * Set mediaType key value into bid object Set mediaType key value into the bid object. * Update lemmaBidAdapter.js remove duplicate function * Update lemmaBidAdapter.js Remove non supported code. * Update lemmaBidAdapter_spec.js Remove GDPR test cases. --- modules/lemmaBidAdapter.js | 27 +++++++++-------------- test/spec/modules/lemmaBidAdapter_spec.js | 18 --------------- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/modules/lemmaBidAdapter.js b/modules/lemmaBidAdapter.js index 5941802f97d..926761e5ab2 100644 --- a/modules/lemmaBidAdapter.js +++ b/modules/lemmaBidAdapter.js @@ -62,18 +62,6 @@ export var spec = { }, getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { let syncurl = USER_SYNC + 'pid=' + pubId; - - // Attaching GDPR Consent Params in UserSync url - if (gdprConsent) { - syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); - syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); - } - - // CCPA - if (uspConsent) { - syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); - } - if (syncOptions.iframeEnabled) { return [{ type: 'iframe', @@ -122,9 +110,9 @@ function parseRTBResponse(request, response) { newBid.dealId = bid.dealid; } if (req.imp && req.imp.length > 0) { - newBid.mediaType = req.mediaType; req.imp.forEach(robj => { if (bid.impid === robj.id) { + _checkMediaType(bid.adm, newBid); switch (newBid.mediaType) { case BANNER: break; @@ -277,10 +265,6 @@ function _getDeviceObject(request) { function setOtherParams(request, ortbRequest) { var params = request && request.params ? request.params : null; - if (request && request.gdprConsent) { - ortbRequest.regs = { ext: { gdpr: request.gdprConsent.gdprApplies ? 1 : 0 } }; - ortbRequest.user = { ext: { consent: request.gdprConsent.consentString } }; - } if (params) { ortbRequest.tmax = params.tmax; ortbRequest.bcat = params.bcat; @@ -424,4 +408,13 @@ function parse(rawResp) { return null; } +function _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/test/spec/modules/lemmaBidAdapter_spec.js b/test/spec/modules/lemmaBidAdapter_spec.js index a00c25d126c..a22f5650e50 100644 --- a/test/spec/modules/lemmaBidAdapter_spec.js +++ b/test/spec/modules/lemmaBidAdapter_spec.js @@ -346,24 +346,6 @@ describe('lemmaBidAdapter', function() { type: 'iframe', url: syncurl_iframe }]); }); - - it('CCPA/USP', function() { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&us_privacy=1NYN` - }]); - }); - - it('GDPR', function() { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=0&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=` - }]); - }); }); }); }); From 19b4885892a14a8177c3b45284351b3492b1b8ad Mon Sep 17 00:00:00 2001 From: ix-certification Date: Mon, 8 Feb 2021 11:41:28 -0500 Subject: [PATCH 002/198] added support for addtlConsent (#6005) Co-authored-by: Ix-Prebid-Support --- modules/ixBidAdapter.js | 6 ++++++ test/spec/modules/ixBidAdapter_spec.js | 27 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 97341fbfd78..48a2e741c5a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -341,6 +341,12 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.user.ext = { consent: gdprConsent.consentString || '' }; + + if (gdprConsent.hasOwnProperty('addtlConsent') && gdprConsent.addtlConsent) { + r.user.ext.consented_providers_settings = { + consented_providers: gdprConsent.addtlConsent + } + } } } diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index edbcc5725ac..fca3f8f992e 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -1772,5 +1772,32 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.regs.ext.gdpr).to.equal(1); expect(requestWithConsent.regs.ext.us_privacy).to.equal('1YYN'); }); + + it('should contain `consented_providers_settings.consented_providers` & consent on user.ext when both are provided', function () { + const options = { + gdprConsent: { + consentString: '3huaa11=qu3198ae', + addtlConsent: '1~1.35.41.101', + } + }; + + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + expect(requestWithConsent.user.ext.consented_providers_settings.consented_providers).to.equal('1~1.35.41.101'); + expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); + }); + + it('should not contain `consented_providers_settings.consented_providers` on user.ext when consent is not provided', function () { + const options = { + gdprConsent: { + addtlConsent: '1~1.35.41.101', + } + }; + + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings')).to.not.exist; + expect(utils.deepAccess(requestWithConsent, 'user.ext.consent')).to.not.exist; + }); }); }); From 1654d53b8c7676d652c3bd35c74ca7c5413bf3a7 Mon Sep 17 00:00:00 2001 From: Missena <78362128+dev-missena@users.noreply.github.com> Date: Mon, 8 Feb 2021 22:49:01 +0100 Subject: [PATCH 003/198] Add bid adapter for Missena (#6247) * adds support for getFloor of video mediaTypes * adds test for calling getFloor with correct mediaType * checks that _getFloor converts string floors to float * Add bid adapter for Missena * Use publisher demo token in tests * Add Missena global vendor ID to spec * Use apiKey in the current bidRequest * Add referer info to payload Co-authored-by: Nick Llerandi Co-authored-by: Brandon Ling <51931757+blingster7@users.noreply.github.com> Co-authored-by: Brandon Ling --- modules/missenaBidAdapter.js | 94 ++++++++++++++ modules/missenaBidAdapter.md | 70 +++++++++++ test/spec/modules/missenaBidAdapter_spec.js | 131 ++++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 modules/missenaBidAdapter.js create mode 100644 modules/missenaBidAdapter.md create mode 100644 test/spec/modules/missenaBidAdapter_spec.js diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js new file mode 100644 index 00000000000..2b1d6bdb118 --- /dev/null +++ b/modules/missenaBidAdapter.js @@ -0,0 +1,94 @@ +import * as utils 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/'; + +export const spec = { + aliases: [BIDDER_CODE], + code: BIDDER_CODE, + gvlid: 687, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return typeof bid == 'object' && !!bid.params.apiKey; + }, + + /** + * 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) { + return validBidRequests.map((bidRequest) => { + const payload = { + request_id: bidRequest.bidId, + timeout: bidderRequest.timeout, + }; + + if (bidderRequest && bidderRequest.refererInfo) { + payload.referer = bidderRequest.refererInfo.referer; + payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.consent_string = bidderRequest.gdprConsent.consentString; + payload.consent_required = bidderRequest.gdprConsent.gdprApplies; + } + + return { + method: 'POST', + url: + ENDPOINT_URL + + '?' + + utils.formatQS({ + t: bidRequest.params.apiKey, + }), + data: JSON.stringify(payload), + }; + }); + }, + + /** + * 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 = []; + const response = serverResponse.body; + + if (response && !response.timeout && !!response.ad) { + bidResponses.push(response); + } + + return bidResponses; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + onTimeout: function onTimeout(timeoutData) { + utils.logInfo('Missena - Timeout from adapter', timeoutData); + }, + + /** + * Register bidder specific code, which@ will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function (bid) { + utils.logInfo('Missena - Bid won', bid); + }, +}; + +registerBidder(spec); diff --git a/modules/missenaBidAdapter.md b/modules/missenaBidAdapter.md new file mode 100644 index 00000000000..d5fcacf04ab --- /dev/null +++ b/modules/missenaBidAdapter.md @@ -0,0 +1,70 @@ +# Overview + +``` +Module Name: Missena Bid Adapter +Module Type: Bidder Adapter +Maintainer: jney@missena.com +``` + +## Introduction + +Connects to Missena for bids. + +**Note:** this adapter doesn't support SafeFrame. + +Useful resources: + +- [README](../README.md#Build) +- [https://docs.prebid.org/dev-docs/bidder-adaptor.html](https://docs.prebid.org/dev-docs/bidder-adaptor.html) + +## Develop + +Setup the missena adapter in `integrationExamples/gpt/userId_example.html`. + +For example: + +```js +const AD_UNIT_CODE = "test-div"; +const PUBLISHER_MISSENA_TOKEN = "PA-34745704"; + +var adUnits = [ + { + code: AD_UNIT_CODE, + mediaTypes: { + banner: { + sizes: [1, 1], + }, + }, + bids: [ + { + bidder: "missena", + params: { + apiKey: PUBLISHER_MISSENA_TOKEN, + }, + }, + ], + }, +]; +``` + +Then start the demo app: + +```shell +gulp serve-fast --modules=missenaBidAdapter +``` + +And open [http://localhost:9999/integrationExamples/gpt/userId_example.html](http://localhost:9999/integrationExamples/gpt/userId_example.html) + +## Test + +```shell +gulp test --file test/spec/modules/missenaBidAdapter_spec.js +``` + +Add the `--watch` option to re-run unit tests whenever the source code changes. + +## Build + +```shell +gulp build --modules=missenaBidAdapter +``` diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js new file mode 100644 index 00000000000..026e79c6d5a --- /dev/null +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -0,0 +1,131 @@ +import { expect } from 'chai'; +import { spec, _getPlatform } from 'modules/missenaBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('Missena Adapter', function () { + const adapter = newBidder(spec); + + const bidId = 'abc'; + + const bid = { + bidder: 'missena', + bidId: bidId, + sizes: [[1, 1]], + params: { + apiKey: 'PA-34745704', + }, + }; + + describe('codes', function () { + it('should return a bidder code of missena', function () { + expect(spec.code).to.equal('missena'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true if the apiKey param is present', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if the apiKey is missing', function () { + expect( + spec.isBidRequestValid(Object.assign(bid, { params: {} })) + ).to.equal(false); + }); + + it('should return false if the apiKey is an empty string', function () { + expect( + spec.isBidRequestValid(Object.assign(bid, { params: { apiKey: '' } })) + ).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const consentString = 'AAAAAAAAA=='; + + const bidderRequest = { + gdprConsent: { + consentString: consentString, + gdprApplies: true, + }, + refererInfo: { + referer: 'https://referer', + canonicalUrl: 'https://canonical', + }, + }; + + const requests = spec.buildRequests([bid, bid], bidderRequest); + const request = requests[0]; + const payload = JSON.parse(request.data); + + it('should return as many server requests as bidder requests', function () { + expect(requests.length).to.equal(2); + }); + + it('should have a post method', function () { + expect(request.method).to.equal('POST'); + }); + + it('should send the bidder id', function () { + expect(payload.request_id).to.equal(bidId); + }); + + it('should send referer information to the request', function () { + expect(payload.referer).to.equal('https://referer'); + expect(payload.referer_canonical).to.equal('https://canonical'); + }); + + it('should send gdpr consent information to the request', function () { + expect(payload.consent_string).to.equal(consentString); + expect(payload.consent_required).to.equal(true); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + requestId: bidId, + cpm: 0.5, + currency: 'USD', + ad: '', + }; + + const serverTimeoutResponse = { + requestId: bidId, + timeout: true, + ad: '', + }; + + const serverEmptyAdResponse = { + requestId: bidId, + cpm: 0.5, + currency: 'USD', + ad: '', + }; + + it('should return a proper bid response', function () { + const result = spec.interpretResponse({ body: serverResponse }, bid); + + expect(result.length).to.equal(1); + + expect(Object.keys(result[0])).to.have.members( + Object.keys(serverResponse) + ); + }); + + it('should return an empty response when the server answers with a timeout', function () { + const result = spec.interpretResponse( + { body: serverTimeoutResponse }, + bid + ); + expect(result).to.deep.equal([]); + }); + + it('should return an empty response when the server answers with an empty ad', function () { + const result = spec.interpretResponse( + { body: serverEmptyAdResponse }, + bid + ); + expect(result).to.deep.equal([]); + }); + }); +}); From c27ff14b31c5a7a08695eda7dc929c768dbbb809 Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Tue, 9 Feb 2021 02:12:10 -0800 Subject: [PATCH 004/198] Gulp test file example added in readme (#6287) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 40df62ccee4..3e541080bf9 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,11 @@ To run the unit tests: gulp test ``` +To run the unit tests for a perticular file (example for pubmaticBidAdapter_spec.js): +```bash +gulp test --file "test/spec/modules/pubmaticBidAdapter_spec.js" +``` + To generate and view the code coverage reports: ```bash From dde585bbd38d16157668a955690b00989dcf100b Mon Sep 17 00:00:00 2001 From: Pooja Pasawala Date: Tue, 9 Feb 2021 02:38:53 -0800 Subject: [PATCH 005/198] Sharethrough: Add support for ID5, Shared ID, and Live Intent ID (#6261) * Update prebid adapter universal ids to include ID5, SharedID, and LiveIntent ID. [#176447070](https://www.pivotaltracker.com/story/show/176447070) Co-authored-by: Mathieu Pheulpin * Addressing review [#176447070] * Quick rewrite [#176447070] * Address ID5 review, forward linkType to adserver * Reformatting SharedID to align with ID5 Co-authored-by: Mathieu Pheulpin --- modules/sharethroughBidAdapter.js | 48 +++++++++++++------ .../modules/sharethroughBidAdapter_spec.js | 42 +++++++++++++++- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 89484b1c68b..24be8673615 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; -const VERSION = '3.3.0'; +const VERSION = '3.3.1'; const BIDDER_CODE = 'sharethrough'; const STR_ENDPOINT = 'https://btlr.sharethrough.com/WYu2BXv1/v1'; const DEFAULT_SIZE = [1, 1]; @@ -31,6 +31,8 @@ export const sharethroughAdapterSpec = { strVersion: VERSION }; + Object.assign(query, handleUniversalIds(bidRequest)); + const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; query.secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1); @@ -46,20 +48,6 @@ export const sharethroughAdapterSpec = { query.us_privacy = bidderRequest.uspConsent } - if (bidRequest.userId && bidRequest.userId.tdid) { - query.ttduid = bidRequest.userId.tdid; - } - - if (bidRequest.userId && bidRequest.userId.pubcid) { - query.pubcid = bidRequest.userId.pubcid; - } else if (bidRequest.crumbs && bidRequest.crumbs.pubcid) { - query.pubcid = bidRequest.crumbs.pubcid; - } - - if (bidRequest.userId && bidRequest.userId.idl_env) { - query.idluid = bidRequest.userId.idl_env; - } - if (bidRequest.schain) { query.schain = JSON.stringify(bidRequest.schain); } @@ -147,6 +135,36 @@ export const sharethroughAdapterSpec = { onSetTargeting: (bid) => {} }; +function handleUniversalIds(bidRequest) { + if (!bidRequest.userId) return {}; + + const universalIds = {}; + + const ttd = utils.deepAccess(bidRequest, 'userId.tdid'); + if (ttd) universalIds.ttduid = ttd; + + const pubc = utils.deepAccess(bidRequest, 'userId.pubcid') || utils.deepAccess(bidRequest, 'crumbs.pubcid'); + if (pubc) universalIds.pubcid = pubc; + + const idl = utils.deepAccess(bidRequest, 'userId.idl_env'); + if (idl) universalIds.idluid = idl; + + const id5 = utils.deepAccess(bidRequest, 'userId.id5id.uid'); + if (id5) { + universalIds.id5uid = { id: id5 }; + const id5link = utils.deepAccess(bidRequest, 'userId.id5id.ext.linkType'); + if (id5link) universalIds.id5uid.linkType = id5link; + } + + const lipb = utils.deepAccess(bidRequest, 'userId.lipb.lipbid'); + if (lipb) universalIds.liuid = lipb; + + const shd = utils.deepAccess(bidRequest, 'userId.sharedid'); + if (shd) universalIds.shduid = shd; // object with keys: id & third + + return universalIds; +} + function getLargestSize(sizes) { function area(size) { return size[0] * size[1]; diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index cd9071a6098..b3451a09dde 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from '../../../src/utils.js'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); const bidRequests = [ @@ -15,7 +16,20 @@ const bidRequests = [ userId: { tdid: 'fake-tdid', pubcid: 'fake-pubcid', - idl_env: 'fake-identity-link' + idl_env: 'fake-identity-link', + id5id: { + uid: 'fake-id5id', + ext: { + linkType: 2 + } + }, + sharedid: { + id: 'fake-sharedid', + third: 'fake-sharedthird' + }, + lipb: { + lipbid: 'fake-lipbid' + } }, crumbs: { pubcid: 'fake-pubcid-in-crumbs-obj' @@ -329,6 +343,15 @@ describe('sharethrough adapter spec', function() { expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); }); + it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + + ' crumbs object of the bidrequest', function() { + const bidData = utils.deepClone(bidRequests); + delete bidData[0].userId.pubcid; + + const bidRequest = spec.buildRequests(bidData)[0]; + expect(bidRequest.data.pubcid).to.eq('fake-pubcid-in-crumbs-obj'); + }); + it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + ' crumbs object of the bidrequest', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; @@ -341,6 +364,23 @@ describe('sharethrough adapter spec', function() { expect(bidRequest.data.idluid).to.eq('fake-identity-link'); }); + it('should add the id5uid parameter if a bid request contains a value for ID5', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.id5uid.id).to.eq('fake-id5id'); + expect(bidRequest.data.id5uid.linkType).to.eq(2); + }); + + it('should add the shduid parameter if a bid request contains a value for Shared ID', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.shduid.id).to.eq('fake-sharedid'); + expect(bidRequest.data.shduid.third).to.eq('fake-sharedthird'); + }); + + it('should add the liuid parameter if a bid request contains a value for LiveIntent ID', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.liuid).to.eq('fake-lipbid'); + }); + it('should add Sharethrough specific parameters', function() { const builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0]).to.deep.include({ From 73cfeb537a9ffbca707e80c1c171d14f57b46869 Mon Sep 17 00:00:00 2001 From: lowendavid <66423906+lowendavid@users.noreply.github.com> Date: Tue, 9 Feb 2021 11:39:14 +0100 Subject: [PATCH 006/198] SmartAdServer Bid Adapter: image sync and noAd (#6236) * SIM-889 Now we have image based sync * SIM-889 Added test to check noad and image sync * SIM-889 Fixing indenting issues --- modules/smartadserverBidAdapter.js | 12 +++- .../modules/smartadserverBidAdapter_spec.js | 69 ++++++++++++++++++- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index ed9003e3b4d..bb9364c72c3 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -135,7 +135,7 @@ export const spec = { const bidResponses = []; let response = serverResponse.body; try { - if (response) { + if (response && !response.isNoAd) { const bidRequest = JSON.parse(bidRequestString.data); let bidResponse = { @@ -147,7 +147,8 @@ export const spec = { dealId: response.dealId, currency: response.currency, netRevenue: response.isNetCpm, - ttl: response.ttl + ttl: response.ttl, + dspPixels: response.dspPixels }; if (bidRequest.mediaType === VIDEO) { @@ -182,6 +183,13 @@ export const spec = { type: 'iframe', url: serverResponses[0].body.cSyncUrl }); + } else if (syncOptions.pixelEnabled && serverResponses.length > 0 && serverResponses[0].body.dspPixels !== undefined) { + serverResponses[0].body.dspPixels.forEach(function(pixel) { + syncs.push({ + type: 'image', + url: pixel + }); + }); } return syncs; } diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index e3bca240a47..749de43b9af 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -115,7 +115,41 @@ describe('Smart bid adapter tests', function () { ttl: 300, adUrl: 'http://awesome.fake.url', ad: '< --- awesome script --- >', - cSyncUrl: 'http://awesome.fake.csync.url' + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: false + } + }; + + var BID_RESPONSE_IS_NO_AD = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: true + } + }; + + var BID_RESPONSE_IMAGE_SYNC = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: false, + dspPixels: ['pixelOne', 'pixelTwo', 'pixelThree'] } }; @@ -149,6 +183,18 @@ describe('Smart bid adapter tests', function () { expect(requestContent).to.have.property('ckid').and.to.equal(42); }); + it('Verify parse response with no ad', function () { + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE_IS_NO_AD, request[0]); + expect(bids).to.have.lengthOf(0); + + expect(function () { + spec.interpretResponse(BID_RESPONSE_IS_NO_AD, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + it('Verify parse response', function () { const request = spec.buildRequests(DEFAULT_PARAMS); const bids = spec.interpretResponse(BID_RESPONSE, request[0]); @@ -258,6 +304,27 @@ describe('Smart bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); + it('Verifies user sync using dspPixels', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE_IMAGE_SYNC]); + expect(syncs).to.have.lengthOf(3); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE_IMAGE_SYNC]); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, []); + expect(syncs).to.have.lengthOf(0); + }); + describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); From fae47aab59b29b54421b57cfc22ab9271b436ebf Mon Sep 17 00:00:00 2001 From: iskmerof Date: Wed, 10 Feb 2021 06:19:01 -0500 Subject: [PATCH 007/198] Add client Alias Adkernel (#6291) Adding "bcm" alias to Adkernel adapter --- modules/adkernelBidAdapter.js | 2 +- test/spec/modules/adkernelBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 20ed65fe2e2..03fa5c2b2d9 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -52,7 +52,7 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { export const spec = { code: 'adkernel', - aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad', 'stringads'], + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad', 'stringads', 'bcm'], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 4454aa00a71..0d772423a22 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -556,7 +556,7 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(11); + expect(spec.aliases).to.have.lengthOf(12); }); }); From 4a7b465d6bd640046b7da3675e203082caed2cd4 Mon Sep 17 00:00:00 2001 From: Ian Flournoy Date: Wed, 10 Feb 2021 06:55:26 -0500 Subject: [PATCH 008/198] [ParrableIdSystem] Add GVLID and handle TC Consent data (#6283) * Added GDPR support * Remove forgotten .only Co-authored-by: Victor --- modules/parrableIdSystem.js | 22 ++++++++++++--- test/spec/modules/parrableIdSystem_spec.js | 32 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index afaf794513e..f072de8736f 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -14,12 +14,13 @@ import { getStorageManager } from '../src/storageManager.js'; const PARRABLE_URL = 'https://h.parrable.com/prebid'; const PARRABLE_COOKIE_NAME = '_parrable_id'; +const PARRABLE_GVLID = 928; const LEGACY_ID_COOKIE_NAME = '_parrable_eid'; const LEGACY_OPTOUT_COOKIE_NAME = '_parrable_optout'; const ONE_YEAR_MS = 364 * 24 * 60 * 60 * 1000; const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT'; -const storage = getStorageManager(); +const storage = getStorageManager(PARRABLE_GVLID); function getExpirationDate() { const oneYearFromNow = new Date(utils.timestamp() + ONE_YEAR_MS); @@ -167,7 +168,7 @@ function shouldFilterImpression(configParams, parrableId) { return !isAllowed() || isBlocked(); } -function fetchId(configParams) { +function fetchId(configParams, gdprConsentData) { if (!isValidConfig(configParams)) return; let parrableId = readCookie(); @@ -183,6 +184,8 @@ function fetchId(configParams) { const eid = (parrableId) ? parrableId.eid : null; const refererInfo = getRefererInfo(); const uspString = uspDataHandler.getConsentData(); + const gdprApplies = (gdprConsentData && typeof gdprConsentData.gdprApplies === 'boolean' && gdprConsentData.gdprApplies); + const gdprConsentString = (gdprConsentData && gdprApplies && gdprConsentData.consentString) || ''; const partners = configParams.partners || configParams.partner const trackers = typeof partners === 'string' ? partners.split(',') @@ -198,6 +201,7 @@ function fetchId(configParams) { const searchParams = { data: encodeBase64UrlSafe(btoa(JSON.stringify(data))), + gdpr: gdprApplies ? 1 : 0, _rand: Math.random() }; @@ -205,6 +209,10 @@ function fetchId(configParams) { searchParams.us_privacy = uspString; } + if (gdprApplies) { + searchParams.gdpr_consent = gdprConsentString; + } + const options = { method: 'GET', withCredentials: true @@ -251,7 +259,7 @@ function fetchId(configParams) { callback, id: parrableId }; -}; +} /** @type {Submodule} */ export const parrableIdSubmodule = { @@ -260,6 +268,12 @@ export const parrableIdSubmodule = { * @type {string} */ name: 'parrableId', + /** + * Global Vendor List ID + * @type {number} + */ + gvlid: PARRABLE_GVLID, + /** * decode the stored id value for passing to bid requests * @function @@ -282,7 +296,7 @@ export const parrableIdSubmodule = { */ getId(config, gdprConsentData, currentStoredId) { const configParams = (config && config.params) || {}; - return fetchId(configParams); + return fetchId(configParams, gdprConsentData); } }; diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 5a10529778c..f0aac7666f9 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -210,6 +210,38 @@ describe('Parrable ID System', function() { removeParrableCookie(); }); }); + + describe('GDPR consent', () => { + let callbackSpy = sinon.spy(); + + const config = { + params: { + partner: 'partner' + } + }; + + const gdprConsentTestCases = [ + { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: { gdpr: 1, gdpr_consent: 'expectedConsentString' } }, + { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, + { consentData: { gdprApplies: true, consentString: undefined }, expected: { gdpr: 1, gdpr_consent: '' } }, + { consentData: { gdprApplies: 'yes', consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, + { consentData: undefined, expected: { gdpr: 0 } } + ]; + + gdprConsentTestCases.forEach((testCase, index) => { + it(`should call user sync url with the gdprConsent - case ${index}`, () => { + parrableIdSubmodule.getId(config, testCase.consentData).callback(callbackSpy); + + if (testCase.expected.gdpr === 1) { + expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); + expect(server.requests[0].url).to.contain('gdpr_consent=' + testCase.expected.gdpr_consent); + } else { + expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); + expect(server.requests[0].url).to.not.contain('gdpr_consent'); + } + }) + }); + }); }); describe('parrableIdSystem.decode()', function() { From 8e2501e881d6f2790d474e1393b56d9af5400478 Mon Sep 17 00:00:00 2001 From: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Date: Wed, 10 Feb 2021 16:24:04 -0500 Subject: [PATCH 009/198] 4.26.0 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8b6af04379..97020126830 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.26.0-pre", + "version": "4.26.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ff0b015c96c1c707ca66a66505a350c935adac62 Mon Sep 17 00:00:00 2001 From: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Date: Wed, 10 Feb 2021 16:38:25 -0500 Subject: [PATCH 010/198] 4.27.0-pre --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97020126830..45493de7aea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.26.0", + "version": "4.27.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a037c5455b038b25c0be07a1625aac4761ef7f3e Mon Sep 17 00:00:00 2001 From: pnh-pubx <73683023+pnh-pubx@users.noreply.github.com> Date: Thu, 11 Feb 2021 04:34:49 +0530 Subject: [PATCH 011/198] Updated data mapping of winning bid and auction logs in pubxai analytics adapter (#6285) Co-authored-by: Phaneendra Hegde --- modules/pubxaiAnalyticsAdapter.js | 13 +- .../modules/pubxaiAnalyticsAdapter_spec.js | 164 ++++++------------ 2 files changed, 64 insertions(+), 113 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 7e2f5061621..894752d607b 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -27,9 +27,13 @@ var pubxaiAnalyticsAdapter = Object.assign(adapter( if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { args.forEach(item => { mapBidResponse(item, 'timeout'); }); } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { - events.auctionInit = args; + events.floorDetail = {}; + if (typeof args.bidderRequests[0].bids[0] !== 'undefined' && typeof args.bidderRequests[0].bids[0].floorData !== 'undefined') { + Object.assign(events.floorDetail, args.bidderRequests[0].bids[0].floorData); + } auctionTimestamp = args.timestamp; } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { + events.bids = []; mapBidRequests(args).forEach(item => { events.bids.push(item) }); } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { mapBidResponse(args, 'response'); @@ -56,6 +60,8 @@ function mapBidRequests(params) { adUnitCode: bid.adUnitCode, requestId: bid.bidderRequestId, auctionId: bid.auctionId, + placementId: bid.params.placementId, + floorData: bid.floorData, transactionId: bid.transactionId, sizes: utils.parseSizesInput(bid.mediaTypes.banner.sizes).toString(), renderStatus: 1, @@ -134,8 +140,9 @@ pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { function send(data, status) { if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { let location = utils.getWindowLocation(); - if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - Object.assign(data.auctionInit, { host: location.host, path: location.pathname, search: location.search }); + if (typeof data !== 'undefined') { + data.pageDetail = {}; + Object.assign(data.pageDetail, { host: location.host, path: location.pathname, search: location.search }); } data.initOptions = initOptions; diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 91c81dcae8d..95245f2c6c9 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -27,6 +27,8 @@ describe('pubxai analytics adapter', function() { pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625' }; + let location = utils.getWindowLocation(); + let prebidEvent = { 'auctionInit': { 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', @@ -219,6 +221,12 @@ describe('pubxai analytics adapter', function() { 'originalCpm': 0.5, 'originalCurrency': 'USD', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -381,6 +389,12 @@ describe('pubxai analytics adapter', function() { 'originalCpm': 0.5, 'originalCurrency': 'USD', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -449,6 +463,12 @@ describe('pubxai analytics adapter', function() { 'originalCpm': 0.5, 'originalCurrency': 'USD', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -488,9 +508,13 @@ describe('pubxai analytics adapter', function() { 'params': [{ 'placementId': 13144370 }] - } + }, + 'pageDetail': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search + }, }; - let location = utils.getWindowLocation(); let expectedAfterBid = { 'bids': [{ @@ -509,6 +533,12 @@ describe('pubxai analytics adapter', function() { 'mediaType': 'banner', 'statusMessage': 'Bid available', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -527,118 +557,21 @@ describe('pubxai analytics adapter', function() { 'timeToRespond': 267, 'responseTimestamp': 1603865707449, 'platform': navigator.platform, + 'placementId': 13144370, 'deviceType': getDeviceType() }], - 'auctionInit': { + 'pageDetail': { 'host': location.host, 'path': location.pathname, - 'search': location.search, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'timestamp': 1603865707180, - 'auctionStatus': 'inProgress', - 'adUnits': [{ - 'code': '/19968336/header-bid-tag-1', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'new model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloor', - 'fetchStatus': 'success' - } - }], - 'sizes': [ - [ - 300, - 250 - ] - ], - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' - }], - 'adUnitCodes': [ - '/19968336/header-bid-tag-1' - ], - 'bidderRequests': [{ - 'bidderCode': 'appnexus', - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'bidderRequestId': '184cbc05bb90ba', - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'new model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloor', - 'fetchStatus': 'success' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', - 'sizes': [ - [ - 300, - 250 - ] - ], - 'bidId': '248f9a4489835e', - 'bidderRequestId': '184cbc05bb90ba', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }], - 'auctionStart': 1603865707180, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://local-pnh.net:8080/stream/', - 'reachedTop': true, - 'isAmp': false, - 'numIframes': 0, - 'stack': [ - 'http://local-pnh.net:8080/stream/' - ], - 'canonicalUrl': null - }, - 'start': 1603865707182 - }], - 'noBids': [], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 1000, - 'config': { - 'samplingRate': '1', - 'pubxId': '6c415fc0-8b0e-4cf5-be73-01526a4db625' - } + 'search': location.search + }, + 'floorDetail': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false }, 'initOptions': initOptions }; @@ -660,6 +593,12 @@ describe('pubxai analytics adapter', function() { 'status': 'rendered', 'statusMessage': 'Bid available', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloor', + 'location': 'fetch', + 'modelVersion': 'new model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -680,6 +619,11 @@ describe('pubxai analytics adapter', function() { 'platform': navigator.platform, 'deviceType': getDeviceType() }, + 'pageDetail': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search + }, 'initOptions': initOptions } From c9e8869ddd867a475efa4531d594ed422252aa8c Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Thu, 11 Feb 2021 14:41:38 +0300 Subject: [PATCH 012/198] Grid Bid Adapter: Added video protocols to the ad request (#6299) --- modules/gridBidAdapter.js | 6 +++++- test/spec/modules/gridBidAdapter_spec.js | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 6e610b67e0e..b296fe39ae4 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -365,7 +365,7 @@ function _addBidResponse(serverBid, bidRequest, bidResponses) { } function createVideoRequest(bid, mediaType) { - const {playerSize, mimes, durationRangeSec} = mediaType; + const {playerSize, mimes, durationRangeSec, protocols} = mediaType; const size = (playerSize || bid.sizes || [])[0]; if (!size) return; @@ -380,6 +380,10 @@ function createVideoRequest(bid, mediaType) { result.maxduration = durationRangeSec[1]; } + if (protocols && protocols.length) { + result.protocols = protocols; + } + return result; } diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 084c67562e6..b4e87119111 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -105,7 +105,8 @@ describe('TheMediaGrid Adapter', function () { 'sizes': [[728, 90]], 'mediaTypes': { 'video': { - 'playerSize': [[400, 600]] + 'playerSize': [[400, 600]], + 'protocols': [1, 2, 3] }, 'banner': { 'sizes': [[728, 90]] @@ -281,7 +282,8 @@ describe('TheMediaGrid Adapter', function () { }, 'video': { 'w': 400, - 'h': 600 + 'h': 600, + 'protocols': [1, 2, 3] } }] }); From f672209110ef30b9cdf96048d4e8285d43716b4a Mon Sep 17 00:00:00 2001 From: bretg Date: Thu, 11 Feb 2021 06:52:17 -0500 Subject: [PATCH 013/198] Rubicon Bid Adapter: updated transactionId to auctionId for OpenRTB (#6298) --- modules/rubiconBidAdapter.js | 4 ++-- test/spec/modules/rubiconBidAdapter_spec.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 395b7a693b2..e9f25c9411e 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -153,7 +153,7 @@ export const spec = { test: config.getConfig('debug') ? 1 : 0, cur: ['USD'], source: { - tid: bidRequest.transactionId + tid: bidRequest.auctionId }, tmax: bidderRequest.timeout, imp: [{ @@ -424,7 +424,7 @@ export const spec = { 'rp_floor': (params.floor = parseFloat(params.floor)) >= 0.01 ? params.floor : undefined, 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, - 'x_source.tid': bidRequest.transactionId, + 'x_source.tid': bidRequest.auctionId, 'x_source.pchain': params.pchain, '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 8c25d97dada..5b881299210 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -415,7 +415,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'x_source.pchain': 'GAM:11111-reseller1:22222', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', @@ -572,7 +572,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -895,7 +895,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2067,7 +2067,7 @@ describe('the rubicon adapter', function () { 'p_pos': 'atf', 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', From 2702e964cd0d540002450ddacd776f7c1b635794 Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Fri, 12 Feb 2021 02:50:29 -0800 Subject: [PATCH 014/198] Fix for Issue 6117: Added Module Name in Build to Comments (#6297) --- gulpfile.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index b7a9e442a8c..51536992bd0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -31,7 +31,7 @@ const execa = require('execa'); var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); -var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n'; +var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + '\nModules: <%= modules %> */\n'; var port = 9999; const FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost'; const FAKE_SERVER_PORT = 4444; @@ -157,12 +157,13 @@ function makeWebpackPkg() { const analyticsSources = helpers.getAnalyticsSources(); const moduleSources = helpers.getModulePaths(externalModules); + const modulesString = (externalModules.length > 0) ? externalModules.join(', ') : 'All available modules in current version.'; return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) .pipe(uglify()) - .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid }))) + .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid, modules: modulesString }))) .pipe(gulp.dest('build/dist')); } From a6c3986c0a306f4dc50b149d4a5f617282741a96 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 12 Feb 2021 06:39:20 -0500 Subject: [PATCH 015/198] map tripleliftBidAdapter.js tl_source to bid.meta.mediaType (#6303) --- modules/tripleliftBidAdapter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index f97165f3d1c..029cdd68331 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -322,6 +322,14 @@ function _buildResponseObject(bidderRequest, bid) { if (bid.adomain && bid.adomain.length) { bidResponse.meta.advertiserDomains = bid.adomain; } + + if (bid.tl_source && bid.tl_source == 'hdx') { + bidResponse.meta.mediaType = 'banner'; + } + + if (bid.tl_source && bid.tl_source == 'tlx') { + bidResponse.meta.mediaType = 'native'; + } }; return bidResponse; } From 62ca969575e82eb1ae5416e412982d562d74c2d5 Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Fri, 12 Feb 2021 15:54:43 +0100 Subject: [PATCH 016/198] Tappx Bid Adapter: new bidder adapter added (#6233) * ADD: tappx bid adapter * FIX: replace .includes by .indexOf * UPDATE: Expand the test coverage * FIX: format spacing tests * FIX: get auctionId from validBidRequests * UPDATE: add bannerMediaType tablet sizes * FIX: get timeout from bidderRequest.timeout * UPDATE: replace the way to get the hostname * UPDATE: adding support multiple bid requests in a single call * UPDATE: remove hardcoded test payload param Co-authored-by: marc_tappx --- modules/tappxBidAdapter.js | 289 ++++++++++++++++++++++ modules/tappxBidAdapter.md | 37 +++ test/spec/modules/tappxBidAdapter_spec.js | 86 +++++++ 3 files changed, 412 insertions(+) create mode 100644 modules/tappxBidAdapter.js create mode 100644 modules/tappxBidAdapter.md create mode 100644 test/spec/modules/tappxBidAdapter_spec.js diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js new file mode 100644 index 00000000000..7782f151802 --- /dev/null +++ b/modules/tappxBidAdapter.js @@ -0,0 +1,289 @@ +'use strict'; + +import * as utils 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 = 'tappx'; +const TTL = 360; +const CUR = 'USD'; +var HOST; +var hostDomain; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + if ((bid.params == null) || (bid.params.endpoint == null) || (bid.params.tappxkey == null)) { + utils.logWarn(`[TAPPX]: Please review the mandatory Tappx parameters. ${JSON.stringify(bid)}`); + return false; + } + return true; + }, + + /** + * Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. + * Make a server request from the list of BidRequests. + * + * @param {*} validBidRequests + * @param {*} bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + let requests = []; + validBidRequests.forEach(oneValidRequest => { + requests.push(buildOneRequest(oneValidRequest, bidderRequest)); + }); + return requests; + }, + + /** + * Parse the response and generate one or more bid objects. + * + * @param {*} serverResponse + * @param {*} originalRequest + */ + interpretResponse: function(serverResponse, originalRequest) { + const responseBody = serverResponse.body; + if (!serverResponse.body) { + utils.logWarn('[TAPPX]: Empty response body HTTP 204, no bids'); + return []; + } + + const bids = []; + responseBody.seatbid.forEach(serverSeatBid => { + serverSeatBid.bid.forEach(serverBid => { + bids.push(interpretBannerBid(serverBid, originalRequest)); + }); + }); + + return bids; + }, + + /** + * If the publisher allows user-sync activity, the platform will call this function and the adapter may register pixels and/or iframe user syncs. + * + * @param {*} syncOptions + * @param {*} serverResponses + * @param {*} gdprConsent + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let url = `https://${hostDomain}/cs/usersync.php?`; + + // GDPR & CCPA + if (gdprConsent) { + url += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + url += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + if (uspConsent) { + url += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + // SyncOptions + if (syncOptions.iframeEnabled) { + url += '&type=iframe' + return [{ + type: 'iframe', + url: url + }]; + } else { + url += '&type=img' + return [{ + type: 'image', + url: url + }]; + } + } +} + +/** + * Parse the response and generate one bid object. + * + * @param {object} serverBid Bid by OpenRTB 2.5 + * @returns {object} Prebid banner bidObject + */ +function interpretBannerBid(serverBid, request) { + return { + requestId: request.bids.bidId, + cpm: serverBid.price, + currency: serverBid.cur ? serverBid.cur : CUR, + width: serverBid.w, + height: serverBid.h, + ad: serverBid.adm, + ttl: TTL, + creativeId: serverBid.crid, + netRevenue: true, + mediaType: BANNER, + } +} + +/** +* Build and makes the request +* +* @param {*} validBidRequests +* @param {*} bidderRequest +* @return response ad +*/ +function buildOneRequest(validBidRequests, bidderRequest) { + HOST = utils.deepAccess(validBidRequests, 'params.host'); + hostDomain = HOST.split('/', 1)[0]; + + const ENDPOINT = utils.deepAccess(validBidRequests, 'params.endpoint'); + const TAPPXKEY = utils.deepAccess(validBidRequests, 'params.tappxkey'); + const BIDFLOOR = utils.deepAccess(validBidRequests, 'params.bidfloor'); + const bannerMediaType = utils.deepAccess(validBidRequests, 'mediaTypes.banner'); + const { refererInfo } = bidderRequest; + + // let requests = []; + let payload = {}; + let publisher = {}; + let tagid; + let api = {}; + + // > App/Site object + if (utils.deepAccess(validBidRequests, 'params.app')) { + let app = {}; + app.name = utils.deepAccess(validBidRequests, 'params.app.name'); + app.bundle = utils.deepAccess(validBidRequests, 'params.app.bundle'); + app.domain = utils.deepAccess(validBidRequests, 'params.app.domain'); + publisher.name = utils.deepAccess(validBidRequests, 'params.app.publisher.name'); + publisher.domain = utils.deepAccess(validBidRequests, 'params.app.publisher.domain'); + tagid = `${app.name}_typeAdBanVid_${getOs()}`; + payload.app = app; + api[0] = utils.deepAccess(validBidRequests, 'params.api') ? utils.deepAccess(validBidRequests, 'params.api') : [3, 5]; + } else { + let site = {}; + site.name = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + site.bundle = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + site.domain = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + publisher.name = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + publisher.domain = (bidderRequest && refererInfo) ? utils.parseUrl(refererInfo.referer).hostname : window.location.hostname; + tagid = `${site.name}_typeAdBanVid_${getOs()}`; + payload.site = site; + } + // < App/Site object + + // > Imp object + let imp = {}; + let w; + let h; + + if (bannerMediaType) { + let banner = {}; + w = bannerMediaType.sizes[0][0]; + h = bannerMediaType.sizes[0][1]; + banner.w = w; + banner.h = h; + if ( + ((bannerMediaType.sizes[0].indexOf(480) >= 0) && (bannerMediaType.sizes[0].indexOf(320) >= 0)) || + ((bannerMediaType.sizes[0].indexOf(768) >= 0) && (bannerMediaType.sizes[0].indexOf(1024) >= 0))) { + banner.pos = 7 + } else { + banner.pos = 4 + } + + banner.api = api; + + let format = {}; + format[0] = {}; + format[0].w = w; + format[0].h = h; + banner.format = format; + + imp.banner = banner; + } + + imp.id = validBidRequests.bidId; + imp.tagid = tagid; + imp.secure = 1; + + imp.bidfloor = utils.deepAccess(validBidRequests, 'params.bidfloor'); + // < Imp object + + // > Device object + let device = {}; + // Mandatory + device.os = getOs(); + device.ip = 'peer'; + device.ua = navigator.userAgent; + device.ifa = validBidRequests.ifa; + + // Optional + device.h = screen.height; + device.w = screen.width; + device.dnt = utils.getDNT() ? 1 : 0; + device.language = getLanguage(); + device.make = navigator.vendor ? navigator.vendor : ''; + + let geo = {}; + geo.country = utils.deepAccess(validBidRequests, 'params.geo.country'); + // < Device object + + // > Params + let params = {}; + params.host = 'tappx.com'; + params.tappxkey = TAPPXKEY; + params.endpoint = ENDPOINT; + params.bidfloor = BIDFLOOR; + // < Params + + // > GDPR + let regs = {}; + regs.gdpr = 0; + if (!(bidderRequest.gdprConsent == null)) { + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { regs.gdpr = bidderRequest.gdprConsent.gdprApplies; } + if (regs.gdpr) { regs.consent = bidderRequest.gdprConsent.consentString; } + } + + // CCPA + regs.ext = {}; + if (!(bidderRequest.uspConsent == null)) { + regs.ext.us_privacy = bidderRequest.uspConsent; + } + + // COPPA compliance + if (config.getConfig('coppa') === true) { + regs.coppa = config.getConfig('coppa') === true ? 1 : 0; + } + // < GDPR + + // > Payload + payload.id = validBidRequests.auctionId; + payload.test = utils.deepAccess(validBidRequests, 'params.test') ? 1 : 0; + payload.at = 1; + payload.tmax = bidderRequest.timeout ? bidderRequest.timeout : 600; + payload.bidder = BIDDER_CODE; + payload.imp = [imp]; + + payload.device = device; + payload.params = params; + payload.regs = regs; + // < Payload + + return { + method: 'POST', + url: `https://${HOST}/${ENDPOINT}?type_cnn=prebidjs`, + data: JSON.stringify(payload), + bids: validBidRequests + }; +} + +function getLanguage() { + const language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; +} + +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'; } +} + +registerBidder(spec); diff --git a/modules/tappxBidAdapter.md b/modules/tappxBidAdapter.md new file mode 100644 index 00000000000..d9ffd98b6c5 --- /dev/null +++ b/modules/tappxBidAdapter.md @@ -0,0 +1,37 @@ +# Overview +``` +Module Name: Tappx Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@tappx.com +``` + +# Description +Module that connects to :tappx demand sources. +Please use ```tappx``` as the bidder code. +Ads sizes available: [320,50], [300,250], [320,480], [1024,768], [728,90] + +# Banner Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[320,50]] + } + }, + bids: [ + { + bidder: "tappx", + params: { + host: "testing.ssp.tappx.com/rtb/v2/", + tappxkey: "pub-1234-android-1234", + endpoint: "ZZ1234PBJS", + bidfloor: 0.005, + test: true // Optional for testing purposes + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js new file mode 100644 index 00000000000..c4410d8ce5e --- /dev/null +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -0,0 +1,86 @@ +import { assert } from 'chai'; +import {spec} from 'modules/tappxBidAdapter'; + +describe('Tappx adapter tests', function () { + describe('isBidRequestValid', function () { + let bid = { bidder: 'tappx', params: { host: 'testing.ssp.tappx.com', tappxkey: 'pub-1234-test-1234', endpoint: 'ZZ1234PBJS', bidfloor: 0.005 } }; + + it('should return true when required params found', function () { + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when required params are missing', function () { + const bid = { + host: 'testing.ssp.tappx.com' + }; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequest', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + // Web Test + let validBidRequests = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'api': [3, 5]}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'div-1', 'transactionId': '713f2c01-61e3-45b5-9e4e-2b163033f3d6', 'sizes': [[320, 480]], 'bidId': '27818d05971607', 'bidderRequestId': '1320551a307df5', 'auctionId': '3f1281d3-9860-4657-808d-3c1d42231ef3', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}] + // App Test + let validAppBidRequests = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'api': [3, 5], 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 50]]}}, 'adUnitCode': 'div-1', 'transactionId': '713f2c01-61e3-45b5-9e4e-2b163033f3d6', 'sizes': [[320, 50]], 'bidId': '27818d05971607', 'bidderRequestId': '1320551a307df5', 'auctionId': '3f1281d3-9860-4657-808d-3c1d42231ef3', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}] + let bidderRequest = {'bidderCode': 'tappx', 'auctionId': '6cca2192-2262-468b-8a3c-d00c58a5d911', 'bidderRequestId': '1ae5c6a02684df', 'bids': [{'bidder': 'tappx', 'params': {'host': 'tests.tappx.com', 'tappxkey': 'pub-1234-test-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'banner-ad-div', 'transactionId': 'c44cdbde-ab6d-47a0-8dde-6b4ff7909a35', 'sizes': [[320, 50]], 'bidId': '2e3a5feb30cfe4', 'bidderRequestId': '1ae5c6a02684df', 'auctionId': '6cca2192-2262-468b-8a3c-d00c58a5d911', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1611308859094, 'timeout': 700, 'refererInfo': {'referer': 'http://tappx.local:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://tappx.local:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': consentString, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; + + it('should add gdpr/usp consent information to the request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.regs.gdpr).to.exist.and.to.be.true; + expect(payload.regs.consent).to.exist.and.to.equal(consentString); + expect(payload.regs.ext.us_privacy).to.exist; + }); + + it('should properly build a banner request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + + it('should properly build a banner request with app params', function () { + const request = spec.buildRequests(validAppBidRequests, bidderRequest); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + }); + + describe('interpretResponse', function () { + const bidRequest = { + data: {}, + bids: [ {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.05, 'api': [3, 5]}, 'crumbs': {'pubcid': 'df2144f7-673f-4440-83f5-cd4a73642d99'}, 'fpd': {'context': {'adServer': {'name': 'gam', 'adSlot': '/19968336/header-bid-tag-0'}, 'pbAdSlot': '/19968336/header-bid-tag-0'}}, 'mediaTypes': {'banner': {'sizes': [[320, 480]]}}, 'adUnitCode': 'div-1', 'transactionId': '47dd44e8-e7db-417c-a8f1-621a2e1a117d', 'sizes': [[320, 480]], 'bidId': '2170932097e505', 'bidderRequestId': '140ba7a1ab7aeb', 'auctionId': '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0} ] + }; + + const serverResponse = {'body': {'id': '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', 'bidid': 'bid3811165568213389257', 'seatbid': [{'seat': '1', 'group': 0, 'bid': [{'id': '3811165568213389257', 'impid': 1, 'price': 0.05, 'adm': "\t", 'w': 320, 'h': 480, 'lurl': 'http://testing.ssp.tappx.com/rtb/RTBv2Loss?id=3811165568213389257&ep=ZZ1234PBJS&au=test&bu=localhost&sz=320x480&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', 'cid': '01744fbb521e9fb10ffea926190effea', 'crid': 'a13cf884e66e7c660afec059c89d98b6', 'adomain': []}]}], 'cur': 'USD'}, 'headers': {}}; + it('receive reponse with single placement', function () { + const bids = spec.interpretResponse(serverResponse, bidRequest); + const bid = bids[0]; + expect(bid.cpm).to.exist; + expect(bid.ad).to.match(/^ ortb2.site.ATTR - fpd.context.data.ATTR --> ortb2.site.ext.data - fpd.user.ATTR --> ortb2.user.ATTR - fpd.user.data.ATTR --> ortb2.user.ext.data 2) gptPreAuction: a) move adunit.fpd to adunit.ortb2 b) adUnit.ortb2Imp.ext.data.adserver.{name, adSlot} c) pbAdSlot moves to AdUnit.ortb2Imp.ext.data.pbAdSlot 3) pbsBidAdapter a) merge the new ortb2 and AdUnit.ortb2Imp.ext objects into the OpenRTB JSON. b) therefore imp[].ext.context.data.pbadslot is now changed to imp[].ext.data.pbadslot (no context) c) read adUnit.ortb2Imp.ext.data.adserver from the new location. Output location is moved to imp[].ext.data.adserver (no context) * FPD 2.0 Update Update to adrelevantis adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to amx adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to avocet adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to criteo adapter to look at config.ortb2 instead of config.fpd * Update to correct imp fpd structure * Update to s2s adapter to coincide with imp fpd alteration * Update to consolidate several lines of duplicate code into one location * Slight modification for ortb2Imp to use ortb2Imp.ext as opposed to ortb2Imp.ext.data * FPD 2.0 Update Update to grid adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to inmar adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to luponmedia adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to smaato adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to triplelift adapter to look at config.ortb2 instead of config.fpd * Update to gptPreAuction to move over to imp level ortb2 * FPD 2.0 Update Update to triplelift adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update * FPD 2.0 Update Update to jwplayerRtd adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to admixer adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to rubicon adapter to look at config.ortb2 instead of config.fpd * Update to fix keyword bug * Added backwards compatibility functions for FPD both global/bidder and adunit level data * Update to utilize new backward functionality for fpd 2.0 * Removed extra new line * Update to include new backward functionality for FPD 2.0 data * Update to utilize new backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Fixed typo in fpd config object location * Uodate to utilize backward functionality to pass FPD 2.0 data * Update to change all ortb2Imp.ext.data.adserver.adSlot references to ortb2Imp.ext.data.adserver.adslot - all lowercase. Corresponding adapter and unit tests to adhere to these changes * Fixed typo * Fixed typo * FPD 2.0 update to rubicon adapter to pass iab values * Updates: 1) Change function name 2) addAdUnits always pass array 3) Remove unecessary comment 4) Bug fix for ortb2.user.data to be filtered on legacy fpd conversion --- modules/admixerBidAdapter.js | 8 +- modules/adrelevantisBidAdapter.js | 6 +- modules/amxBidAdapter.js | 2 +- modules/avocetBidAdapter.js | 2 +- modules/criteoBidAdapter.js | 11 +- modules/gptPreAuction.js | 32 +-- modules/gridBidAdapter.js | 4 +- modules/inmarBidAdapter.js | 2 +- modules/jwplayerRtdProvider.js | 4 +- modules/luponmediaBidAdapter.js | 7 +- modules/prebidServerBidAdapter/index.js | 60 +++--- modules/rubiconBidAdapter.js | 97 +++++---- modules/smaatoBidAdapter.js | 6 +- modules/tripleliftBidAdapter.js | 11 +- src/adapterManager.js | 2 +- src/config.js | 135 ++++++++++++- src/prebid.js | 6 +- test/spec/adUnits_spec.js | 11 + test/spec/config_spec.js | 15 ++ .../modules/adrelevantisBidAdapter_spec.js | 10 +- test/spec/modules/amxBidAdapter_spec.js | 20 +- test/spec/modules/criteoBidAdapter_spec.js | 24 ++- test/spec/modules/gptPreAuction_spec.js | 54 ++--- test/spec/modules/gridBidAdapter_spec.js | 10 + test/spec/modules/jwplayerRtdProvider_spec.js | 48 ++--- .../modules/prebidServerBidAdapter_spec.js | 110 ++++++---- test/spec/modules/rubiconBidAdapter_spec.js | 190 +++++++++++------- test/spec/modules/smaatoBidAdapter_spec.js | 6 +- .../spec/modules/tripleliftBidAdapter_spec.js | 20 +- 29 files changed, 583 insertions(+), 330 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index d2a6f9a639e..6cf738a0086 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -21,7 +21,7 @@ export const spec = { buildRequests: function (validRequest, bidderRequest) { const payload = { imps: [], - fpd: config.getConfig('fpd') + fpd: config.getLegacyFpd(config.getConfig('ortb2')) }; let endpointUrl; if (bidderRequest) { @@ -42,7 +42,11 @@ export const spec = { } } validRequest.forEach((bid) => { - payload.imps.push(bid); + let imp = {}; + Object.keys(bid).forEach(key => { + (key === 'ortb2Imp') ? imp.fpd = config.getLegacyImpFpd(bid[key]) : imp[key] = bid[key]; + }); + payload.imps.push(imp); }); const payloadString = JSON.stringify(payload); return { diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 5da941c65ca..c6298cffde9 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -120,11 +120,11 @@ export const spec = { payload.referrer_detection = refererinfo; } - let fpdcfg = config.getConfig('fpd') + let fpdcfg = config.getLegacyFpd(config.getConfig('ortb2')); if (fpdcfg && fpdcfg.context) { let fdata = { - keywords: fpdcfg.context.keywords, - category: fpdcfg.context.data.category + keywords: fpdcfg.context.keywords || '', + category: utils.deepAccess(fpdcfg, 'context.data.category') || '' } payload.fpd = fdata; } diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index a1fa202c154..497c2142b9b 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -259,7 +259,7 @@ export const spec = { d: '', m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, - fpd: config.getConfig('fpd'), + fpd: config.getLegacyFpd(config.getConfig('ortb2')), eids: values(bidRequests.reduce((all, bid) => { // we only want unique ones in here if (bid == null || bid.userIdAsEids == null) { diff --git a/modules/avocetBidAdapter.js b/modules/avocetBidAdapter.js index 7a9e5062c0f..1283bb865d4 100644 --- a/modules/avocetBidAdapter.js +++ b/modules/avocetBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { const publisherDomain = config.getConfig('publisherDomain'); // First-party data from config - const fpd = config.getConfig('fpd'); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')); // GDPR status and TCF consent string let tcfConsentString; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index e3a6b9eaa12..41cbb0670c8 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -28,7 +28,7 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [ BANNER, VIDEO, NATIVE ], - /** + /** f * @param {object} bid * @return {boolean} */ @@ -56,10 +56,11 @@ export const spec = { buildRequests: (bidRequests, bidderRequest) => { let url; let data; + let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; Object.assign(bidderRequest, { - publisherExt: config.getConfig('fpd.context'), - userExt: config.getConfig('fpd.user'), + publisherExt: fpd.context, + userExt: fpd.user, ceh: config.getConfig('criteo.ceh') }); @@ -280,8 +281,8 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.zoneId) { slot.zoneid = bidRequest.params.zoneId; } - if (bidRequest.fpd && bidRequest.fpd.context) { - slot.ext = bidRequest.fpd.context; + if (utils.deepAccess(bidRequest, 'ortb2Imp.ext')) { + slot.ext = bidRequest.ortb2Imp.ext; } if (bidRequest.params.ext) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 48b72671d6a..ee2b5406453 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -26,46 +26,48 @@ export const appendGptSlots = adUnits => { if (matchingAdUnitCode) { const adUnit = adUnitMap[matchingAdUnitCode]; - adUnit.fpd = adUnit.fpd || {}; - adUnit.fpd.context = adUnit.fpd.context || {}; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; - const context = adUnit.fpd.context; - context.adServer = context.adServer || {}; - context.adServer.name = 'gam'; - context.adServer.adSlot = slot.getAdUnitPath(); + const context = adUnit.ortb2Imp.ext.data; + context.adserver = context.adserver || {}; + context.adserver.name = 'gam'; + context.adserver.adslot = slot.getAdUnitPath(); } }); }; export const appendPbAdSlot = adUnit => { - adUnit.fpd = adUnit.fpd || {}; - adUnit.fpd.context = adUnit.fpd.context || {}; - const context = adUnit.fpd.context; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; + const context = adUnit.ortb2Imp.ext.data; const { customPbAdSlot } = _currentConfig; if (customPbAdSlot) { - context.pbAdSlot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adServer.adSlot')); + context.pbadslot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adserver.adslot')); return; } // use context.pbAdSlot if set - if (context.pbAdSlot) { + if (context.pbadslot) { return; } // use data attribute 'data-adslotid' if set try { const adUnitCodeDiv = document.getElementById(adUnit.code); if (adUnitCodeDiv.dataset.adslotid) { - context.pbAdSlot = adUnitCodeDiv.dataset.adslotid; + context.pbadslot = adUnitCodeDiv.dataset.adslotid; return; } } catch (e) {} // banner adUnit, use GPT adunit if defined - if (context.adServer) { - context.pbAdSlot = context.adServer.adSlot; + if (utils.deepAccess(context, 'adserver.adslot')) { + context.pbadslot = context.adserver.adslot; return; } - context.pbAdSlot = adUnit.code; + context.pbadslot = adUnit.code; }; export const makeBidRequestsHook = (fn, adUnits, ...args) => { diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 8ab2e2b8a95..964b34dcfa2 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -180,8 +180,8 @@ export const spec = { } const configKeywords = utils.transformBidderParamKeywords({ - 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, - 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + 'user': utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('ortb2.site'), 'keywords') || null }); if (configKeywords.length) { diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js index 7583985b23c..e1edd935587 100755 --- a/modules/inmarBidAdapter.js +++ b/modules/inmarBidAdapter.js @@ -40,7 +40,7 @@ export const spec = { uspConsent: bidderRequest.uspConsent, currencyCode: config.getConfig('currency.adServerCurrency'), coppa: config.getConfig('coppa'), - firstPartyData: config.getConfig('fpd'), + firstPartyData: config.getLegacyFpd(config.getConfig('ortb2')), prebidVersion: '$prebid.version$' }; diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index a29c4ce5fa7..8332e720ae7 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -144,7 +144,7 @@ function enrichBidRequest(bidReqConfig, onDone) { * @param {function} onDone */ export function enrichAdUnits(adUnits) { - const fpdFallback = config.getConfig('fpd.context.data.jwTargeting'); + const fpdFallback = config.getConfig('ortb2.site.ext.data.jwTargeting'); adUnits.forEach(adUnit => { const jwTargeting = extractPublisherParams(adUnit, fpdFallback); if (!jwTargeting || !Object.keys(jwTargeting).length) { @@ -170,7 +170,7 @@ function supportsInstreamVideo(mediaTypes) { export function extractPublisherParams(adUnit, fallback) { let adUnitTargeting; try { - adUnitTargeting = adUnit.fpd.context.data.jwTargeting; + adUnitTargeting = adUnit.ortb2Imp.ext.data.jwTargeting; } catch (e) {} if (!adUnitTargeting && !supportsInstreamVideo(adUnit.mediaTypes)) { diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 4f7fd2ae1a0..29b54f77fbb 100644 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -279,8 +279,9 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } - const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); - const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + const siteData = Object.assign({}, bidRequest.params.inventory, fpd.context); + const userData = Object.assign({}, bidRequest.params.visitor, fpd.user); if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { const bidderData = { @@ -301,7 +302,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); } - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + const pbAdSlot = utils.deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 7cfef6a0784..20cf93caae5 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -336,18 +336,18 @@ function addBidderFirstPartyDataToRequest(request) { const bidderConfig = config.getBidderConfig(); const fpdConfigs = Object.keys(bidderConfig).reduce((acc, bidder) => { const currBidderConfig = bidderConfig[bidder]; - if (currBidderConfig.fpd) { - const fpd = {}; - if (currBidderConfig.fpd.context) { - fpd.site = currBidderConfig.fpd.context; + if (currBidderConfig.ortb2) { + const ortb2 = {}; + if (currBidderConfig.ortb2.site) { + ortb2.site = currBidderConfig.ortb2.site; } - if (currBidderConfig.fpd.user) { - fpd.user = currBidderConfig.fpd.user; + if (currBidderConfig.ortb2.user) { + ortb2.user = currBidderConfig.ortb2.user; } acc.push({ bidders: [ bidder ], - config: { fpd } + config: { ortb2 } }); } return acc; @@ -618,23 +618,27 @@ const OPEN_RTB_PROTOCOL = { const imp = { id: adUnit.code, ext, secure: s2sConfig.secure }; - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(adUnit, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(imp, 'ext.context.data.pbadslot', pbAdSlot); - } - - /** - * Copy GAM AdUnit and Name to imp - */ - ['name', 'adSlot'].forEach(name => { - /** @type {(string|undefined)} */ - const value = utils.deepAccess(adUnit, `fpd.context.adserver.${name}`); - if (typeof value === 'string' && value) { - utils.deepSetValue(imp, `ext.context.data.adserver.${name.toLowerCase()}`, value); + const ortb2 = {...utils.deepAccess(adUnit, 'ortb2Imp.ext.data')}; + Object.keys(ortb2).forEach(prop => { + /** + * Prebid AdSlot + * @type {(string|undefined)} + */ + if (prop === 'pbadslot') { + if (typeof ortb2[prop] === 'string' && ortb2[prop]) utils.deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); + } else if (prop === 'adserver') { + /** + * Copy GAM AdUnit and Name to imp + */ + ['name', 'adslot'].forEach(name => { + /** @type {(string|undefined)} */ + const value = utils.deepAccess(ortb2, `adserver.${name}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); + } + }); + } else { + utils.deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); } }); @@ -763,12 +767,12 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'regs.coppa', 1); } - const commonFpd = getConfig('fpd') || {}; - if (commonFpd.context) { - utils.deepSetValue(request, 'site.ext.data', commonFpd.context); + const commonFpd = getConfig('ortb2') || {}; + if (commonFpd.site) { + utils.deepSetValue(request, 'site', commonFpd.site); } if (commonFpd.user) { - utils.deepSetValue(request, 'user.ext.data', commonFpd.user); + utils.deepSetValue(request, 'user', commonFpd.user); } addBidderFirstPartyDataToRequest(request); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 7297c82440f..9905498edee 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -871,21 +871,26 @@ function addVideoParameters(data, bidRequest) { } function applyFPD(bidRequest, mediaType, data) { - const bidFpd = { - user: {...bidRequest.params.visitor}, - context: {...bidRequest.params.inventory} + const BID_FPD = { + user: {ext: {data: {...bidRequest.params.visitor}}}, + site: {ext: {data: {...bidRequest.params.inventory}}} }; - if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; + if (bidRequest.params.keywords) BID_FPD.site.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; - let fpd = utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd); - - const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}, adserver: 'dfp_ad_unit_code'}; - let obj = {}; - let impData = {}; - let keywords = []; + let fpd = utils.mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); + let impData = utils.deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; + const MAP = {user: 'tg_v.', site: 'tg_i.', adserver: 'tg_i.dfp_ad_unit_code', pbadslot: 'tg_i.pbadslot', keywords: 'kw'}; const validate = function(prop, key) { - if (typeof prop === 'object' && !Array.isArray(prop)) { + if (key === 'data' && Array.isArray(prop)) { + return prop.filter(name => name.segment && utils.deepAccess(name, 'ext.taxonomyname').match(/iab/i)).map(value => { + let segments = value.segment.filter(obj => obj.id).reduce((result, obj) => { + result.push(obj.id); + return result; + }, []); + if (segments.length > 0) return segments.toString(); + }).toString(); + } else if (typeof prop === 'object' && !Array.isArray(prop)) { utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); } else if (typeof prop !== 'undefined') { return (Array.isArray(prop)) ? prop.filter(value => { @@ -895,53 +900,43 @@ function applyFPD(bidRequest, mediaType, data) { }).toString() : prop.toString(); } }; - - Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => { - obj[map[type].code] = Object.keys(fpd[type]).filter(value => typeof fpd[type][value] !== 'undefined').reduce((result, key) => { - if (key === 'keywords') { - if (!Array.isArray(fpd[type][key]) && mediaType === BANNER) fpd[type][key] = [fpd[type][key]] - - result[key] = fpd[type][key]; - - if (mediaType === BANNER) keywords = keywords.concat(fpd[type][key]); - } else if (key === 'data') { - utils.mergeDeep(result, {ext: {data: fpd[type][key]}}); - } else if (key === 'adServer' || key === 'pbAdSlot') { - (key === 'adServer') ? ['name', 'adSlot'].forEach(name => { - const value = validate(fpd[type][key][name]); - if (value) utils.deepSetValue(impData, `adserver.${name.toLowerCase()}`, value.replace(/^\/+/, '')) - }) : impData[key.toLowerCase()] = fpd[type][key].replace(/^\/+/, '') - } else { - utils.mergeDeep(result, {ext: {data: {[key]: fpd[type][key]}}}); - } - - return result; - }, {}); - - if (mediaType === BANNER) { - let duplicate = (typeof obj[map[type].code].ext === 'object' && obj[map[type].code].ext.data) || {}; - - Object.keys(duplicate).forEach((key) => { - const val = (key === 'adserver') ? duplicate.adserver.adslot : validate(duplicate[key], key); - - if (val) data[(map[key]) ? `${map[type][BANNER]}${map[key]}` : `${map[type][BANNER]}${key}`] = val; - }); - } - }); + const addBannerData = function(obj, name, key) { + let val = validate(obj, key); + let loc = (MAP[key]) ? `${MAP[key]}` : (key === 'data') ? `${MAP[name]}iab` : `${MAP[name]}${key}`; + data[loc] = (data[loc]) ? data[loc].concat(',', val) : val; + } Object.keys(impData).forEach((key) => { - if (mediaType === BANNER) { - (map[key]) ? data[`tg_i.${map[key]}`] = impData[key].adslot : data[`tg_i.${key.toLowerCase()}`] = impData[key]; - } else { - utils.mergeDeep(data.imp[0], {ext: {context: {data: {[key]: impData[key]}}}}); + if (key === 'adserver') { + ['name', 'adslot'].forEach(prop => { + if (impData[key][prop]) impData[key][prop] = impData[key][prop].replace(/^\/+/, ''); + }); + } else if (key === 'pbadslot') { + impData[key] = impData[key].replace(/^\/+/, ''); } }); if (mediaType === BANNER) { - let kw = validate(keywords, 'keywords'); - if (kw) data.kw = kw; + ['site', 'user'].forEach(name => { + Object.keys(fpd[name]).forEach((key) => { + if (key !== 'ext') { + addBannerData(fpd[name][key], name, key); + } else if (fpd[name][key].data) { + Object.keys(fpd[name].ext.data).forEach((key) => { + addBannerData(fpd[name].ext.data[key], name, key); + }); + } + }); + }); + Object.keys(impData).forEach((key) => { + (key === 'adserver') ? addBannerData(impData[key].adslot, name, key) : addBannerData(impData[key], 'site', key); + }); } else { - utils.mergeDeep(data, obj); + if (Object.keys(impData).length) { + utils.mergeDeep(data.imp[0].ext, {data: impData}); + } + + utils.mergeDeep(data, fpd); } } diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 93915689cee..fdb4d2df984 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -98,8 +98,10 @@ const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { } }; - Object.assign(request.user, config.getConfig('fpd.user')); - Object.assign(request.site, config.getConfig('fpd.context')); + let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + + Object.assign(request.user, fpd.user); + Object.assign(request.site, fpd.context); if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index b681b0980ea..e8d248eea03 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -122,8 +122,8 @@ function _buildPostBody(bidRequests) { } else if (bidRequest.mediaTypes.banner) { imp.banner = { format: _sizes(bidRequest.sizes) }; }; - if (!utils.isEmpty(bidRequest.fpd)) { - imp.fpd = _getAdUnitFpd(bidRequest.fpd); + if (!utils.isEmpty(bidRequest.ortb2Imp)) { + imp.fpd = _getAdUnitFpd(bidRequest.ortb2Imp); } return imp; }); @@ -190,9 +190,10 @@ function _getGlobalFpd() { const fpd = {}; const context = {} const user = {}; + const ortbData = config.getLegacyFpd(config.getConfig('ortb2')) || {}; - const fpdContext = Object.assign({}, config.getConfig('fpd.context')); - const fpdUser = Object.assign({}, config.getConfig('fpd.user')); + const fpdContext = Object.assign({}, ortbData.context); + const fpdUser = Object.assign({}, ortbData.user); _addEntries(context, fpdContext); _addEntries(user, fpdUser); @@ -210,7 +211,7 @@ function _getAdUnitFpd(adUnitFpd) { const fpd = {}; const context = {}; - _addEntries(context, adUnitFpd.context); + _addEntries(context, adUnitFpd.ext); if (!utils.isEmpty(context)) { fpd.context = context; diff --git a/src/adapterManager.js b/src/adapterManager.js index cb84607e130..f7f5d821932 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -68,7 +68,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) } bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'fpd', + 'ortb2Imp', 'mediaType', 'renderer', 'storedAuctionResponse' diff --git a/src/config.js b/src/config.js index daaf739bbbd..51184a8014d 100644 --- a/src/config.js +++ b/src/config.js @@ -324,6 +324,119 @@ export function newConfig() { return bidderConfig; } + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + Object.keys(obj).forEach((type) => { + let prop = (type === 'site') ? 'context' : type; + duplicate[prop] = (prop === 'context' || prop === 'user') ? Object.keys(obj[type]).filter(key => key !== 'data').reduce((result, key) => { + if (key === 'ext') { + utils.mergeDeep(result, obj[type][key]); + } else { + utils.mergeDeep(result, {[key]: obj[type][key]}); + } + + return result; + }, {}) : obj[type]; + }); + + return duplicate; + } + + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyImpFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + if (utils.deepAccess(obj, 'ext.data')) { + Object.keys(obj.ext.data).forEach((key) => { + if (key === 'pbadslot') { + utils.mergeDeep(duplicate, {context: {pbAdSlot: obj.ext.data[key]}}); + } else if (key === 'adserver') { + utils.mergeDeep(duplicate, {context: {adServer: obj.ext.data[key]}}); + } else { + utils.mergeDeep(duplicate, {context: {data: {[key]: obj.ext.data[key]}}}); + } + }); + } + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in config + */ + function convertFpd(opt) { + let duplicate = {}; + + Object.keys(opt).forEach((type) => { + let prop = (type === 'context') ? 'site' : type; + duplicate[prop] = (prop === 'site' || prop === 'user') ? Object.keys(opt[type]).reduce((result, key) => { + if (key === 'data') { + utils.mergeDeep(result, {ext: {data: opt[type][key]}}); + } else { + utils.mergeDeep(result, {[key]: opt[type][key]}); + } + + return result; + }, {}) : opt[type]; + }); + + return duplicate; + } + + /** + * Copy Impression FPD over to OpenRTB standard format in config + * Only accepts bid level context.data values with pbAdSlot and adServer exceptions + */ + function convertImpFpd(opt) { + let duplicate = {}; + + Object.keys(opt).filter(prop => prop === 'context').forEach((type) => { + Object.keys(opt[type]).forEach((key) => { + if (key === 'data') { + utils.mergeDeep(duplicate, {ext: {data: opt[type][key]}}); + } else { + if (typeof opt[type][key] === 'object' && !Array.isArray(opt[type][key])) { + Object.keys(opt[type][key]).forEach(data => { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: {[data.toLowerCase()]: opt[type][key][data]}}}}); + }); + } else { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: opt[type][key]}}}); + } + } + }); + }); + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in each adunit + */ + function convertAdUnitFpd(arr) { + let convert = []; + + arr.forEach((adunit) => { + if (adunit.fpd) { + (adunit['ortb2Imp']) ? utils.mergeDeep(adunit['ortb2Imp'], convertImpFpd(adunit.fpd)) : adunit['ortb2Imp'] = convertImpFpd(adunit.fpd); + convert.push((({ fpd, ...duplicate }) => duplicate)(adunit)); + } else { + convert.push(adunit); + } + }); + + return convert; + } + /* * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function @@ -338,13 +451,14 @@ export function newConfig() { let topicalConfig = {}; topics.forEach(topic => { - let option = options[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(options[topic]) : options[topic]; - if (utils.isPlainObject(defaults[topic]) && utils.isPlainObject(option)) { - option = Object.assign({}, defaults[topic], option); + if (utils.isPlainObject(defaults[prop]) && utils.isPlainObject(option)) { + option = Object.assign({}, defaults[prop], option); } - topicalConfig[topic] = config[topic] = option; + topicalConfig[prop] = config[prop] = option; }); callSubscribers(topicalConfig); @@ -437,11 +551,13 @@ export function newConfig() { bidderConfig[bidder] = {}; } Object.keys(config.config).forEach(topic => { - let option = config.config[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(config.config[topic]) : config.config[topic]; + if (utils.isPlainObject(option)) { - bidderConfig[bidder][topic] = Object.assign({}, bidderConfig[bidder][topic] || {}, option); + bidderConfig[bidder][prop] = Object.assign({}, bidderConfig[bidder][prop] || {}, option); } else { - bidderConfig[bidder][topic] = option; + bidderConfig[bidder][prop] = option; } }); }); @@ -499,7 +615,10 @@ export function newConfig() { runWithBidder, callbackWithBidder, setBidderConfig, - getBidderConfig + getBidderConfig, + convertAdUnitFpd, + getLegacyFpd, + getLegacyImpFpd }; } diff --git a/src/prebid.js b/src/prebid.js index 3452107effd..6565c1610d8 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -595,11 +595,7 @@ $$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - if (utils.isArray(adUnitArr)) { - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, adUnitArr); - } else if (typeof adUnitArr === 'object') { - $$PREBID_GLOBAL$$.adUnits.push(adUnitArr); - } + $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, config.convertAdUnitFpd(utils.isArray(adUnitArr) ? adUnitArr : [adUnitArr])); // emit event events.emit(ADD_AD_UNITS); }; diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 7dd48a13208..baa5b4ac8c4 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -23,6 +23,16 @@ describe('Publisher API _ AdUnits', function () { } ] }, { + fpd: { + context: { + pbAdSlot: 'adSlotTest', + data: { + inventory: [4], + keywords: 'foo,bar', + visitor: [1, 2, 3], + } + } + }, code: '/1996833/slot-2', sizes: [[468, 60]], bids: [ @@ -85,6 +95,7 @@ describe('Publisher API _ AdUnits', function () { it('the second adUnits value should be same with the adUnits that is added by $$PREBID_GLOBAL$$.addAdUnits();', function () { assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.deepEqual(adUnit2['ortb2Imp'], {'ext': {'data': {'pbadslot': 'adSlotTest', 'inventory': [4], 'keywords': 'foo,bar', 'visitor': [1, 2, 3]}}}, 'adUnit2 ortb2Imp'); assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 81ce966efb2..0b8dd6978cf 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -6,6 +6,8 @@ const utils = require('src/utils'); let getConfig; let setConfig; +let getBidderConfig; +let setBidderConfig; let setDefaults; describe('config API', function () { @@ -15,6 +17,8 @@ describe('config API', function () { const config = newConfig(); getConfig = config.getConfig; setConfig = config.setConfig; + getBidderConfig = config.getBidderConfig; + setBidderConfig = config.setBidderConfig; setDefaults = config.setDefaults; logErrorSpy = sinon.spy(utils, 'logError'); logWarnSpy = sinon.spy(utils, 'logWarn'); @@ -57,6 +61,17 @@ describe('config API', function () { expect(getConfig('foo')).to.eql({baz: 'qux'}); }); + it('moves fpd config into ortb2 properties', function () { + setConfig({fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}); + expect(getConfig('ortb2')).to.eql({site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}); + expect(getConfig('fpd')).to.eql(undefined); + }); + + it('moves fpd bidderconfig into ortb2 properties', function () { + setBidderConfig({bidders: ['bidderA'], config: {fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}}); + expect(getBidderConfig()).to.eql({'bidderA': {ortb2: {site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}}}); + }); + it('sets debugging', function () { setConfig({ debug: true }); expect(getConfig('debug')).to.be.true; diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index 11a6a14a353..596f3bade5d 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -224,12 +224,14 @@ describe('AdrelevantisAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0]); sinon .stub(config, 'getConfig') - .withArgs('fpd') + .withArgs('ortb2') .returns({ - context: { + site: { keywords: 'US Open', - data: { - category: 'sports/tennis' + ext: { + data: { + category: 'sports/tennis' + } } } }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 766045b0f3e..0658fe9f33c 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -17,10 +17,26 @@ const embeddedTrackingPixel = `https://1x1.a-mo.net/hbx/g_impression?A=sample&B= const sampleNurl = 'https://example.exchange/nurl'; const sampleFPD = { + site: { + keywords: 'sample keywords', + ext: { + data: { + pageType: 'article' + } + } + }, + user: { + gender: 'O', + yob: 1982, + } +}; + +const legacySampleFPD = { context: { keywords: 'sample keywords', data: { pageType: 'article' + } }, user: { @@ -31,7 +47,7 @@ const sampleFPD = { const stubConfig = (withStub) => { const stub = sinon.stub(config, 'getConfig').callsFake( - (arg) => arg === 'fpd' ? sampleFPD : null + (arg) => arg === 'ortb2' ? sampleFPD : null ) withStub(); @@ -253,7 +269,7 @@ describe('AmxBidAdapter', () => { it('will forward first-party data', () => { stubConfig(() => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.fpd).to.deep.equal(sampleFPD) + expect(data.fpd).to.deep.equal(legacySampleFPD) }); }); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index e91d3b10abb..cad1e3f8114 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -820,14 +820,18 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request with first party data', function () { const contextData = { keywords: ['power tools'], - data: { - pageType: 'article' + ext: { + data: { + pageType: 'article' + } } }; const userData = { gender: 'M', - data: { - registered: true + ext: { + data: { + registered: true + } } }; const bidRequests = [ @@ -842,8 +846,8 @@ describe('The Criteo bidding adapter', function () { bidfloor: 0.75 } }, - fpd: { - context: { + ortb2Imp: { + ext: { data: { someContextAttribute: 'abc' } @@ -854,8 +858,8 @@ describe('The Criteo bidding adapter', function () { sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context: contextData, + ortb2: { + site: contextData, user: userData } }; @@ -863,8 +867,8 @@ describe('The Criteo bidding adapter', function () { }); const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.publisher.ext).to.deep.equal(contextData); - expect(request.data.user.ext).to.deep.equal(userData); + expect(request.data.publisher.ext).to.deep.equal({keywords: ['power tools'], data: {pageType: 'article'}}); + expect(request.data.user.ext).to.deep.equal({gender: 'M', data: {registered: true}}); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 16b84467af2..c4a81c21d5c 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -30,34 +30,34 @@ describe('GPT pre-auction module', () => { '
test2
'; it('should be unchanged if already defined on adUnit', () => { - const adUnit = { fpd: { context: { pbAdSlot: '12345' } } }; + const adUnit = { ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('12345'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('12345'); }); it('should use adUnit.code if matching id exists', () => { - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('bar1'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('bar1'); }); it('should use the gptSlot.adUnitPath if the adUnit.code matches a div id but does not have a data-adslotid', () => { - const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, fpd: { context: { adServer: { name: 'gam', adSlot: '/baz' } } } }; + const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: { adserver: { name: 'gam', adslot: '/baz' } } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('/baz'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('/baz'); }); it('should use the video adUnit.code (which *should* match the configured "adSlotName", but is not being tested) if there is no matching div with "data-adslotid" defined', () => { - const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, fpd: { context: {} } }; + const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: {} } } }; adUnit.code = 'foo5'; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo5'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo5'); }); it('should use the adUnit.code if all other sources failed', () => { - const adUnit = { code: 'foo4', fpd: { context: {} } }; + const adUnit = { code: 'foo4', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo4'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo4'); }); it('should use the customPbAdSlot function if one is given', () => { @@ -67,32 +67,32 @@ describe('GPT pre-auction module', () => { } }); - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('customPbAdSlotName'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('customPbAdSlotName'); }); }); describe('appendGptSlots', () => { it('should not add adServer object to context if no slots defined', () => { - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should not add adServer object to context if no slot matches', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should add adServer object to context if matching slot is found', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'slotCode2', fpd: { context: {} } }; + const adUnit = { code: 'slotCode2', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode2' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode2' }); }); it('should use the customGptSlotMatching function if one is given', () => { @@ -104,10 +104,10 @@ describe('GPT pre-auction module', () => { }); window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'SlOtCoDe1', fpd: { context: {} } }; + const adUnit = { code: 'SlOtCoDe1', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode1' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode1' }); }); }); @@ -164,23 +164,23 @@ describe('GPT pre-auction module', () => { it('should append PB Ad Slot and GPT Slot info to first-party data in each ad unit', () => { const testAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890' } } + ortb2Imp: { ext: { data: { pbadslot: '67890' } } } }, { code: 'slotCode3', }]; const expectedAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890', adServer: { name: 'gam', adSlot: 'slotCode1' } } } + ortb2Imp: { ext: { data: { pbadslot: '67890', adserver: { name: 'gam', adslot: 'slotCode1' } } } } }, { code: 'slotCode3', - fpd: { context: { pbAdSlot: 'slotCode3', adServer: { name: 'gam', adSlot: 'slotCode3' } } } + ortb2Imp: { ext: { data: { pbadslot: 'slotCode3', adserver: { name: 'gam', adslot: 'slotCode3' } } } } }]; window.googletag.pubads().setSlots(testSlots); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index df119bb689d..2e8601bddf6 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -400,6 +400,16 @@ describe('TheMediaGrid Adapter', function () { expect(payload.site.content).to.deep.equal(jsContent); }); + it('should contain the keyword values if it present in ortb2.(site/user)', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user' ? {'keywords': 'foo'} : (arg === 'ortb2.site' ? {'keywords': 'bar'} : null)); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.ext.keywords).to.deep.equal([{'key': 'user', 'value': ['foo']}, {'key': 'context', 'value': ['bar']}]); + getConfigStub.restore(); + }); + it('shold be right tmax when timeout in config is less then timeout in bidderRequest', function() { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'bidderTimeout' ? 2000 : null); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index f7beb6ba486..458e45e8ae7 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -229,8 +229,8 @@ describe('jwplayerRtdProvider', function() { const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: mediaIdWithSegment, @@ -298,8 +298,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -345,8 +345,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -392,8 +392,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -435,19 +435,19 @@ describe('jwplayerRtdProvider', function() { }); it('should include banner ad units that specify jwTargeting', function() { - const adUnit = { mediaTypes: { banner: {} }, fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { mediaTypes: { banner: {} }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); it('should include outstream ad units that specify jwTargeting', function() { - const adUnit = { mediaTypes: { video: { context: 'outstream' } }, fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { mediaTypes: { video: { context: 'outstream' } }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); it('should fallback to config when empty jwTargeting is defined in ad unit', function () { - const adUnit = { fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); @@ -457,7 +457,7 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: 'bad_id', mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); @@ -468,7 +468,7 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: expectedPlayerID, mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); @@ -577,8 +577,8 @@ describe('jwplayerRtdProvider', function() { bidReqConfig = { adUnits: [ { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[0] @@ -591,8 +591,8 @@ describe('jwplayerRtdProvider', function() { ] }, { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[1] @@ -658,8 +658,8 @@ describe('jwplayerRtdProvider', function() { it('sets targeting data in proper structure', function () { const bid = {}; const adUnitWithMediaId = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -690,8 +690,8 @@ describe('jwplayerRtdProvider', function() { const adUnitCode = 'test_ad_unit'; const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -701,7 +701,7 @@ describe('jwplayerRtdProvider', function() { }, bids: [ bid ] }; - const expectedContentId = 'jw_' + adUnit.fpd.context.data.jwTargeting.mediaID; + const expectedContentId = 'jw_' + adUnit.ortb2Imp.ext.data.jwTargeting.mediaID; const expectedTargeting = { content: { id: expectedContentId @@ -732,8 +732,8 @@ describe('jwplayerRtdProvider', function() { const adUnitEmptyfpd = { code: 'test_ad_unit_empty_fpd', - fpd: { - context: { + ortb2Imp: { + ext: { id: 'sthg' } }, diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 9e20629fe45..babee7e10d7 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1607,7 +1607,29 @@ describe('S2S Adapter', function () { const expected = allowedBidders.map(bidder => ({ bidders: [ bidder ], - config: { fpd: { site: context, user } } + config: { + ortb2: { + site: { + content: { userrating: 4 }, + ext: { + data: { + pageType: 'article', + category: 'tools' + } + } + }, + user: { + yob: '1984', + geo: { country: 'ca' }, + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } })); config.setConfig({ fpd: { context: commonContext, user: commonUser } }); @@ -1615,12 +1637,12 @@ describe('S2S Adapter', function () { adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); - expect(parsedRequestBody.site.ext.data).to.deep.equal(commonContext); - expect(parsedRequestBody.user.ext.data).to.deep.equal(commonUser); + expect(parsedRequestBody.site).to.deep.equal(commonContext); + expect(parsedRequestBody.user).to.deep.equal(commonUser); }); describe('pbAdSlot config', function () { - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context\" is undefined', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1630,30 +1652,32 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1662,16 +1686,18 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { + it('should send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a non-empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } }; @@ -1680,13 +1706,13 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.pbadslot'); - expect(parsedRequestBody.imp[0].ext.context.data.pbadslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.pbadslot'); + expect(parsedRequestBody.imp[0].ext.data.pbadslot).to.equal('/a/b/c'); }); }); describe('GAM ad unit config', function () { - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context\" is undefined', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1696,31 +1722,33 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is undefined', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is empty string', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adServer: { - adSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1730,18 +1758,20 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should send both \"adslot\" and \"name\" from \"imp.ext.context.data.adserver\" if \"fpd.context.adserver.adSlot\" and \"fpd.context.adserver.name\" values are non-empty strings', function () { + it('should send both \"adslot\" and \"name\" from \"imp.ext.data.adserver\" if \"ortb2Imp.ext.data.adserver.adslot\" and \"ortb2Imp.ext.data.adserver.name\" values are non-empty strings', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adserver: { - adSlot: '/a/b/c', - name: 'adserverName1' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '/a/b/c', + name: 'adserverName1' + } } } }; @@ -1751,10 +1781,10 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.adslot'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.name'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.adslot).to.equal('/a/b/c'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.name).to.equal('adserverName1'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.adslot'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.name'); + expect(parsedRequestBody.imp[0].ext.data.adserver.adslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0].ext.data.adserver.name).to.equal('adserverName1'); }); }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 8c25d97dada..36890a2891b 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -827,27 +827,39 @@ describe('the rubicon adapter', function () { }); it('should merge first party data from getConfig with the bid params, if present', () => { - const context = { + const site = { keywords: 'e,f', rating: '4-star', - data: { - page: 'home' + ext: { + data: { + page: 'home' + } } }; const user = { + data: [{ + 'name': 'www.dataprovider1.com', + 'ext': { 'taxonomyname': 'IAB Audience Taxonomy' }, + 'segment': [ + { 'id': '687' }, + { 'id': '123' } + ] + }], gender: 'M', yob: '1984', geo: {country: 'ca'}, keywords: 'd', - data: { - age: 40 + ext: { + data: { + age: 40 + } } }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -861,8 +873,9 @@ describe('the rubicon adapter', function () { 'tg_v.likes': 'sports,video games', 'tg_v.gender': 'M', 'tg_v.age': '40', + 'tg_v.iab': '687,123', 'tg_v.yob': '1984', - 'tg_i.rating': '5-star', + 'tg_i.rating': '4-star,5-star', 'tg_i.page': 'home', 'tg_i.prodtype': 'tech,mobile', }; @@ -1287,12 +1300,12 @@ describe('the rubicon adapter', function () { describe('Prebid AdSlot', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1300,8 +1313,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1310,10 +1323,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '' + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1324,10 +1339,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: 'abc' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abc' + } } } @@ -1339,12 +1356,14 @@ describe('the rubicon adapter', function () { expect(data['tg_i.pbadslot']).to.equal('abc'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } - }; + } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1358,12 +1377,12 @@ describe('the rubicon adapter', function () { describe('GAM ad unit', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1371,8 +1390,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1381,11 +1400,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: '' + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1397,11 +1418,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'abc' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'abc' + } } } } @@ -1414,11 +1437,13 @@ describe('the rubicon adapter', function () { expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'a/b/c' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'a/b/c' + } } } }; @@ -1871,27 +1896,36 @@ describe('the rubicon adapter', function () { it('should include first party data', () => { createVideoBidderRequest(); - const context = { - data: { - page: 'home' + const site = { + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{foo: 'bar'}] }, keywords: 'e,f', - rating: '4-star' + rating: '4-star', + data: [{foo: 'bar'}] }; const user = { - data: { - age: 31 + ext: { + data: { + age: 31 + } }, keywords: 'd', gender: 'M', yob: '1984', - geo: {country: 'ca'} + geo: {country: 'ca'}, + data: [{foo: 'bar'}] }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -1901,19 +1935,19 @@ describe('the rubicon adapter', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const expected = { - site: Object.assign({}, context, context.data, bidderRequest.bids[0].params.inventory), - user: Object.assign({}, user, user.data, bidderRequest.bids[0].params.visitor) + 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 expected.site.data; - delete expected.user.data; - delete expected.site.keywords; - delete expected.user.keywords; + 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.site); - expect(request.data.user.ext.data).to.deep.equal(expected.user); + 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 storedAuctionResponse in video bid request', function () { @@ -1932,29 +1966,33 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); }); - it('should include pbAdSlot in bid request', function () { + it('should include pbadslot in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '1234567890' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '1234567890' + } } - }; + } sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.pbadslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); }); it('should include GAM ad unit in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: '1234567890', - name: 'adServerName1' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '1234567890', + name: 'adServerName1' + } } } }; @@ -1964,8 +2002,8 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.imp[0].ext.context.data.adserver.name).to.equal('adServerName1'); + 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', () => { diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 6af0a855800..1bc77fc9572 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -42,7 +42,7 @@ const ADTYPE_IMG = 'Img'; const ADTYPE_RICHMEDIA = 'Richmedia'; const ADTYPE_VIDEO = 'Video'; -const context = { +const site = { keywords: 'power tools,drills' }; @@ -439,8 +439,8 @@ describe('smaatoBidAdapterTest', () => { it('sends fp data', () => { this.sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index eb410c2525d..30377ec0a5d 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -143,10 +143,10 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, - fpd: { - context: { - pbAdSlot: 'homepage-top-rect', + ortb2Imp: { + ext: { data: { + pbAdSlot: 'homepage-top-rect', adUnitSpecificAttribute: 123 } } @@ -663,11 +663,13 @@ describe('triplelift adapter', function () { const sens = null; const category = ['news', 'weather', 'hurricane']; const pmp_elig = 'true'; - const fpd = { - context: { + const ortb2 = { + site: { pmp_elig: pmp_elig, - data: { - category: category + ext: { + data: { + category: category + } } }, user: { @@ -676,7 +678,7 @@ describe('triplelift adapter', function () { } sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd + ortb2 }; return utils.deepAccess(config, key); }); @@ -688,8 +690,8 @@ 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('pbAdSlot'); 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; }); From 15cf2f0cf9f863c6c88e4890e867e628f4c95ff0 Mon Sep 17 00:00:00 2001 From: Ryan Schweitzer <50628828+r-schweitzer@users.noreply.github.com> Date: Mon, 8 Mar 2021 11:57:29 +0000 Subject: [PATCH 072/198] PBS Bid Adapter: fix s2s alias collision with built-in adapter aliasing (#6379) * fixed overwriting of aliases for s2s * made change * added fix --- modules/prebidServerBidAdapter/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 20cf93caae5..088b5430f46 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -724,7 +724,7 @@ const OPEN_RTB_PROTOCOL = { } if (!utils.isEmpty(aliases)) { - request.ext.prebid.aliases = aliases; + request.ext.prebid.aliases = {...request.ext.prebid.aliases, ...aliases}; } const bidUserIdAsEids = utils.deepAccess(bidRequests, '0.bids.0.userIdAsEids'); From 83e82229398724ac2c38936e5b8afa3f7de66dd4 Mon Sep 17 00:00:00 2001 From: Pierre Turpin Date: Mon, 8 Mar 2021 16:03:08 +0100 Subject: [PATCH 073/198] Clean side-effect when checking that local storage is enabled (#6323) --- src/storageManager.js | 9 +++++-- test/spec/unit/core/storageManager_spec.js | 29 ++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/storageManager.js b/src/storageManager.js index 60e5a7706d0..66a0cf68cbf 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,4 +1,4 @@ -import { hook } from './hook.js'; +import {hook} from './hook.js'; import * as utils from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -110,7 +110,12 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { try { localStorage.setItem('prebid.cookieTest', '1'); return localStorage.getItem('prebid.cookieTest') === '1'; - } catch (error) {} + } catch (error) { + } finally { + try { + localStorage.removeItem('prebid.cookieTest'); + } catch (error) {} + } } return false; } diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index de09df5b196..5bb766217f5 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -46,16 +46,17 @@ describe('storage manager', function() { describe('localstorage forbidden access in 3rd-party context', function() { let errorLogSpy; - const originalLocalStorage = { get: () => window.localStorage }; + let originalLocalStorage; const localStorageMock = { get: () => { throw Error } }; beforeEach(function() { + originalLocalStorage = window.localStorage; Object.defineProperty(window, 'localStorage', localStorageMock); errorLogSpy = sinon.spy(utils, 'logError'); }); afterEach(function() { - Object.defineProperty(window, 'localStorage', originalLocalStorage); + Object.defineProperty(window, 'localStorage', { get: () => originalLocalStorage }); errorLogSpy.restore(); }) @@ -70,4 +71,28 @@ describe('storage manager', function() { sinon.assert.calledThrice(errorLogSpy); }) }) + + describe('localstorage is enabled', function() { + let localStorage; + + beforeEach(function() { + localStorage = window.localStorage; + localStorage.clear(); + }); + + afterEach(function() { + localStorage.clear(); + }) + + it('should remove side-effect after checking', function () { + const storage = getStorageManager(); + + localStorage.setItem('unrelated', 'dummy'); + const val = storage.localStorageIsEnabled(); + + expect(val).to.be.true; + expect(localStorage.length).to.be.eq(1); + expect(localStorage.getItem('unrelated')).to.be.eq('dummy'); + }); + }); }); From 3129a96f57141727c759f82189a348f6c37da19b Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Tue, 9 Mar 2021 05:18:34 -0500 Subject: [PATCH 074/198] updates docs and demo for fpd changes (#6302) Co-authored-by: karimJWP --- .../gpt/jwplayerRtdProvider_example.html | 48 +++++++++---------- modules/jwplayerRtdProvider.md | 12 +++-- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html index 75eb85a2d8c..41c27b70ece 100644 --- a/integrationExamples/gpt/jwplayerRtdProvider_example.html +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -10,32 +10,30 @@ var PREBID_TIMEOUT = 1000; var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - fpd: { - context: { - data: { - jwTargeting: { - // Note: the following Ids are placeholders and should be replaced with your Ids. - playerID: '123', - mediaID: 'abc' + code: 'div-gpt-ad-1460505748561-0', + ortb2Imp: { + ext: { + data: { + jwTargeting: { + // Note: the following Ids are placeholders and should be replaced with your Ids. + playerID: '123', + mediaID: 'abc' + } + } } - }, - } - }, - - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'appnexus', - params: { - placementId: 13144370 - } - }] - + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }] }]; var pbjs = pbjs || {}; diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index ae09277979a..0723e8cbb6c 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -25,14 +25,14 @@ pbjs.setConfig({ } }); ``` -Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `fpd.context.data`: +Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `ortb2Imp.ext.data`: ```javascript const adUnit = { code: '/19968336/prebid_native_example_1', ... - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { // Note: the following Ids are placeholders and should be replaced with your Ids. @@ -52,7 +52,7 @@ pbjs.que.push(function() { }); ``` -**Note**: You may also include `jwTargeting` information in the prebid config's `fpd.context.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. +**Note**: You may also include `jwTargeting` information in the prebid config's `ortb2.site.ext.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. ##Prefetching In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var and set `waitForIt` to `true`: @@ -117,3 +117,7 @@ To view an example: `http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html` **Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. + +#Maintainer info + +Maintained by JW Player. For any questions, comments or feedback please contact Karim Mourra, karim@jwplayer.com From 9b2d3c0872b946f70e19d1160200c69ff83e505f Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Tue, 9 Mar 2021 16:50:53 +0530 Subject: [PATCH 075/198] Changed net revenue to True (#6387) --- modules/pubmaticBidAdapter.js | 2 +- test/spec/modules/pubmaticBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 07e5b62ac26..41ca642c869 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -101,7 +101,7 @@ const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ } ] -const NET_REVENUE = false; +const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', 5: 'PREF', diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 37deb0bca9c..09573fe9f85 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2614,7 +2614,7 @@ describe('PubMatic adapter', function () { } 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].netRevenue).to.equal(true); 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'); @@ -2638,7 +2638,7 @@ describe('PubMatic adapter', function () { } expect(response[1].dealId).to.equal(bidResponses.body.seatbid[1].bid[0].dealid); expect(response[1].currency).to.equal('USD'); - expect(response[1].netRevenue).to.equal(false); + expect(response[1].netRevenue).to.equal(true); expect(response[1].ttl).to.equal(300); expect(response[1].meta.networkId).to.equal(422); expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789'); From a2282be216fae97323bc65a618099b49e1b4807d Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 9 Mar 2021 16:29:02 +0100 Subject: [PATCH 076/198] adagioBidAdapter: add Native support (#6368) --- modules/adagioBidAdapter.js | 116 +++++++++++++- modules/adagioBidAdapter.md | 60 ++++++- test/spec/modules/adagioBidAdapter_spec.js | 173 ++++++++++++++++++++- 3 files changed, 340 insertions(+), 9 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 6f7feec59c9..892411837dd 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -8,16 +8,16 @@ import sha256 from 'crypto-js/sha256.js'; import { getStorageManager } from '../src/storageManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { createEidsArray } from './userId/eids.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import { OUTSTREAM } from '../src/video.js'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.6.0'; +export const VERSION = '2.7.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; -export const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +export const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; export const GVLID = 617; @@ -687,6 +687,112 @@ function _renderer(bid) { }); } +function _parseNativeBidResponse(bid) { + if (!bid.admNative || !Array.isArray(bid.admNative.assets)) { + utils.logError(`${LOG_PREFIX} Invalid native response`); + return; + } + + const native = {} + + function addAssetDataValue(data) { + const map = { + 1: 'sponsoredBy', // sponsored + 2: 'body', // desc + 3: 'rating', + 4: 'likes', + 5: 'downloads', + 6: 'price', + 7: 'salePrice', + 8: 'phone', + 9: 'address', + 10: 'body2', // desc2 + 11: 'displayUrl', + 12: 'cta' + } + if (map.hasOwnProperty(data.type) && typeof data.value === 'string') { + native[map[data.type]] = data.value; + } + } + + // assets + bid.admNative.assets.forEach(asset => { + if (asset.title) { + native.title = asset.title.text + } else if (asset.data) { + addAssetDataValue(asset.data) + } else if (asset.img) { + switch (asset.img.type) { + case 1: + native.icon = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + default: + native.image = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + } + } + }); + + if (bid.admNative.link) { + if (bid.admNative.link.url) { + native.clickUrl = bid.admNative.link.url; + } + if (Array.isArray(bid.admNative.link.clickTrackers)) { + native.clickTrackers = bid.admNative.link.clickTrackers + } + } + + if (Array.isArray(bid.admNative.eventtrackers)) { + native.impressionTrackers = []; + bid.admNative.eventtrackers.forEach(tracker => { + // Only Impression events are supported. Prebid does not support Viewability events yet. + if (tracker.event !== 1) { + return; + } + + // methods: + // 1: image + // 2: js + // note: javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. + switch (tracker.method) { + case 1: + native.impressionTrackers.push(tracker.url); + break; + case 2: + native.javascriptTrackers = ``; + break; + } + }); + } else { + native.impressionTrackers = Array.isArray(bid.admNative.imptrackers) ? bid.admNative.imptrackers : []; + if (bid.admNative.jstracker) { + native.javascriptTrackers = bid.admNative.jstracker; + } + } + + if (bid.admNative.privacy) { + native.privacyLink = bid.admNative.privacy; + } + + if (bid.admNative.ext) { + native.ext = {} + + if (bid.admNative.ext.bvw) { + native.ext.adagio_bvw = bid.admNative.ext.bvw; + } + } + + bid.native = native +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -873,6 +979,10 @@ export const spec = { } } + if (bidObj.mediaType === NATIVE) { + _parseNativeBidResponse(bidObj); + } + bidObj.site = bidReq.params.site; bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index aa79338d79e..46656d88d37 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -38,8 +38,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value // Optional debug mode, used to get a bid response with expected cpm. debug: { enabled: true, @@ -78,8 +78,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value video: { skip: 0 // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. @@ -91,6 +91,58 @@ Connects to Adagio demand source to fetch bids. } } }] + }, + { + code: 'article_native', + mediaTypes: { + native: { + // generic Prebid options + title: { + required: true, + len: 80 + }, + // … + // Custom Adagio data assets + ext: { + adagio_bvw: { + required: false + } + } + } + }, + bids: [{ + bidder: 'adagio', // Required + params: { + organizationId: '1002', // Required - Organization ID provided by Adagio. + site: 'adagio-io', // Required - Site Name provided by Adagio. + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_native', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. + environment: 'mobile', // Recommended. Environment where the page is displayed. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + postBid: false, // Optional. Use it in case of Post-bid integration only. + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value + // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. + native: { + context: 1, + plcmttype: 2 + }, + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 + } + } + }] } ]; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 0a585caaa1a..999773f1a1f 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,5 +1,5 @@ import find from 'core-js-pure/features/array/find.js'; -import { expect } from 'chai'; +import { expect, util } from 'chai'; import { _features, internal as adagio, @@ -13,7 +13,8 @@ import { } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; -import { config } from 'src/config.js'; +import { config } from '../../../src/config.js'; +import { NATIVE } from '../../../src/mediaTypes.js'; const BidRequestBuilder = function BidRequestBuilder(options) { const defaults = { @@ -879,6 +880,174 @@ describe('Adagio bid adapter', () => { expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) }); }); + + describe('Response with native add', () => { + const serverResponseWithNative = utils.deepClone(serverResponse) + serverResponseWithNative.body.bids[0].mediaType = 'native'; + serverResponseWithNative.body.bids[0].admNative = { + ver: '1.2', + link: { + url: 'https://i.am.a.click.url', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ] + }, + privacy: 'http://www.myprivacyurl.url', + ext: { + bvw: 'test' + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://eventrack.local/impression' + }, + { + event: 1, + method: 2, + url: 'https://eventrack.local/impression' + }, + { + event: 2, + method: 1, + url: 'https://eventrack.local/viewable-mrc50' + } + ], + assets: [ + { + required: 1, + title: { + text: 'My title' + } + }, + { + img: { + url: 'https://images.local/image.jpg', + w: 100, + h: 250 + } + }, + { + img: { + type: 1, + url: 'https://images.local/icon.png', + w: 40, + h: 40 + } + }, + { + data: { + type: 1, // sponsored + value: 'Adagio' + } + }, + { + data: { + type: 2, // desc / body + value: 'The super ad text' + } + }, + { + data: { + type: 3, // rating + value: '10 from 10' + } + }, + { + data: { + type: 11, // displayUrl + value: 'https://i.am.a.display.url' + } + } + ] + }; + + const bidRequestNative = utils.deepClone(bidRequest) + bidRequestNative.mediaTypes = { + native: { + sendTargetingKeys: false, + + clickUrl: { + required: true, + }, + title: { + required: true, + }, + body: { + required: true, + }, + sponsoredBy: { + required: false + }, + image: { + required: true + }, + icon: { + required: true + }, + privacyLink: { + required: false + }, + ext: { + adagio_bvw: {} + } + } + }; + + it('Should ignore native parsing due to missing raw admNative property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + delete alternateServerResponse.body.bids[0].admNative + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should ignore native parsing due to invalid raw admNative.assets property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + alternateServerResponse.body.bids[0].admNative.assets = { title: { text: 'test' } }; + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should handle and return a formated Native ad', () => { + const r = spec.interpretResponse(serverResponseWithNative, bidRequestNative); + const expected = { + displayUrl: 'https://i.am.a.display.url', + sponsoredBy: 'Adagio', + body: 'The super ad text', + rating: '10 from 10', + clickUrl: 'https://i.am.a.click.url', + title: 'My title', + impressionTrackers: [ + 'https://eventrack.local/impression' + ], + javascriptTrackers: '', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ], + image: { + url: 'https://images.local/image.jpg', + width: 100, + height: 250 + }, + icon: { + url: 'https://images.local/icon.png', + width: 40, + height: 40 + }, + ext: { + adagio_bvw: 'test' + }, + privacyLink: 'http://www.myprivacyurl.url' + } + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).ok; + expect(r[0].native).to.deep.equal(expected); + }); + }); }); describe('getUserSyncs()', function() { From 7635af1c9b2e143401284fbc511d18e748a6c749 Mon Sep 17 00:00:00 2001 From: rtuschkany <35923908+rtuschkany@users.noreply.github.com> Date: Wed, 10 Mar 2021 13:02:42 +0100 Subject: [PATCH 077/198] EIDS Support Update (#6394) --- modules/connectadBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index d1811a1b7d1..4fa2a56a004 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -72,7 +72,7 @@ export const spec = { // EIDS Support if (validBidRequests[0].userId) { - data.user.ext.eids = createEidsArray(validBidRequests[0].userId); + utils.deepSetValue(data, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); } validBidRequests.map(bid => { From 89d5d410bc4b62c2934f64cec10a6e1948329318 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Wed, 10 Mar 2021 15:03:49 +0300 Subject: [PATCH 078/198] TheMediaGridNM Bid Adapter: fix trouble with alias (#6371) * Added TheMediaGridNM Bid Adapter * Updated required params for TheMediaGridNM Bid Adapter * Update TheMediGridNM Bid Adapter * Fix tests for TheMediaGridNM Bid Adapter * Fixes after review for TheMediaGridNM Bid Adapter * Add support of multi-format in TheMediaGrid Bid Adapter * Update sync url for grid and gridNM Bid Adapters * TheMediaGrid Bid Adapter: added keywords adUnit parameter * Update TheMediaGrid Bid Adapter to support keywords from config * Implement new request format for TheMediaGrid Bid Adapter * Fix jwpseg params for TheMediaGrid Bid Adapter * Update unit tests for The Media Grid Bid Adapter * Fix typo in TheMediaGrid Bid Adapter * Added test for jwTargeting in TheMediaGrid Bid Adapter * The new request format was made by default in TheMediaGrid Bid Adapter * Update userId format in ad request for TheMediaGrid Bid Adapter * Added bidFloor parameter for TheMediaGrid Bid Adapter * Fix for review TheMediaGrid Bid Adapter * Support floorModule in TheMediaGrid Bid Adapter * Fix empty bidfloor for TheMediaGrid Bid Adapter * Some change to restart autotests * Fix userIds format for TheMediaGrid Bid Adapter * Remove digitrust userId from TheMediaGrid Bid Adapter * Protocols was added in video section in ad request for TheMediaGrid Bid Adapter * TheMediaGrid: fix trouble with alias using * TheMediaGridNM: fix trouble with alias --- modules/gridNMBidAdapter.js | 1 - test/spec/modules/gridNMBidAdapter_spec.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index ffd6c1b250c..af1e9f84f43 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -134,7 +134,6 @@ export const spec = { } const bidResponse = { requestId: bid.bidId, - bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index 0dbaac0c526..2aec9713000 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -300,7 +300,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': 11, 'width': 300, 'height': 250, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, @@ -317,7 +316,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, From 20f3cd8972079231f81a02ad4aa24b317e4032dd Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 10 Mar 2021 07:28:35 -0800 Subject: [PATCH 079/198] Grab sourceAgnostic IDs first, then fallback to regular IDs (#6400) --- modules/rubiconAnalyticsAdapter.js | 4 +- .../modules/rubiconAnalyticsAdapter_spec.js | 44 ++++++++++++++++--- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 08ae8bf2dd8..f6724ffcc7a 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -482,8 +482,8 @@ function subscribeToGamSlots() { // these come in as `null` from Gpt, which when stringified does not get removed // so set explicitly to undefined when not a number 'advertiserId', advertiserId => utils.isNumber(advertiserId) ? advertiserId : undefined, - 'creativeId', creativeId => utils.isNumber(creativeId) ? creativeId : undefined, - 'lineItemId', lineItemId => utils.isNumber(lineItemId) ? lineItemId : undefined, + 'creativeId', creativeId => utils.isNumber(event.sourceAgnosticCreativeId) ? event.sourceAgnosticCreativeId : utils.isNumber(creativeId) ? creativeId : undefined, + 'lineItemId', lineItemId => utils.isNumber(event.sourceAgnosticLineItemId) ? event.sourceAgnosticLineItemId : utils.isNumber(lineItemId) ? lineItemId : undefined, 'adSlot', () => event.slot.getAdUnitPath(), 'isSlotEmpty', () => event.isEmpty || undefined ]); diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 136f5ef240a..a9c1eeef4de 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1436,8 +1436,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot0, isEmpty: false, advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333 + sourceAgnosticCreativeId: 2222, + sourceAgnosticLineItemId: 3333 } }; @@ -1448,8 +1448,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot1, isEmpty: false, advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666 + sourceAgnosticCreativeId: 5555, + sourceAgnosticLineItemId: 6666 } }; }); @@ -1515,8 +1515,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot1, isEmpty: false, advertiserId: 0, - creativeId: 0, - lineItemId: 0 + sourceAgnosticCreativeId: 0, + sourceAgnosticLineItemId: 0 } }]); expect(server.requests.length).to.equal(1); @@ -1540,6 +1540,38 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); + it('should pick backup Ids if no sourceAgnostic available first', function () { + performStandardAuction([gptSlotRenderEnded0, { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot1, + isEmpty: false, + advertiserId: 0, + lineItemId: 1234, + creativeId: 5678 + } + }]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 0, + creativeId: 5678, + lineItemId: 1234, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + it('should correctly set adUnit for associated slots', function () { performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); expect(server.requests.length).to.equal(1); From b65ea7371bb4e0d91197c550c13526cb54b2d3a5 Mon Sep 17 00:00:00 2001 From: David Reischer Date: Wed, 10 Mar 2021 16:16:18 +0000 Subject: [PATCH 080/198] Permutive - add AC support for TrustX (#6393) --- .../gpt/permutiveRtdProvider_example.html | 19 ++- modules/permutiveRtdProvider.js | 7 + modules/permutiveRtdProvider.md | 1 + .../spec/modules/permutiveRtdProvider_spec.js | 147 ++++++++++++++++-- 4 files changed, 153 insertions(+), 21 deletions(-) diff --git a/integrationExamples/gpt/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html index 0814dcece5b..a06430bcdfa 100644 --- a/integrationExamples/gpt/permutiveRtdProvider_example.html +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -50,7 +50,7 @@ params: { placementId: 13144370, keywords: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -64,7 +64,7 @@ area: ['home'] }, visitor: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -78,12 +78,21 @@ { settings: {}, targeting: { - inline_kvs: ['1', '2', '3', '4'] + test_kv: ['true'] } } ], ozoneData: {} } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } } ] }, @@ -127,13 +136,13 @@ pbjs.setConfig({ debug: true, realTimeData: { - auctionDelay: 50, // maximum time for RTD modules to respond + auctionDelay: 80, // maximum time for RTD modules to respond dataProviders: [ { name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon', 'ozone'], + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], maxSegs: 500, overwrites: { rubicon: function (bid, data, acEnabled, utils, defaultFn) { diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 8ec215d3cca..db431ed45a7 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -110,6 +110,13 @@ function getDefaultBidderFn (bidder) { deepSetValue(bid, 'params.customData.0.targeting.p_standard', data.ac) } + return bid + }, + trustx: function (bid, data, acEnabled) { + if (acEnabled && data.ac && data.ac.length) { + deepSetValue(bid, 'params.keywords.p_standard', data.ac) + } + return bid } } diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 55bdf6420cf..fe8c34c1b5c 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -33,6 +33,7 @@ The below bidders are currently support by the Permutive RTD module. Please reac | Xandr | `appnexus` | Yes | Yes | | Magnite | `rubicon` | Yes | Yes | | Ozone | `ozone` | No | Yes | +| TrustX | `trustx` | No | Yes | * **First-party segments:** When enabling the respective Activation for a segment in Permutive, this module will automatically attach that segment 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 Permutive. Permutive segments will be sent in the `permutive` key-value. diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index d55bbc58056..cf1f3861eab 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -54,7 +54,7 @@ describe('permutiveRtdProvider', function () { }) } }) - it('sets segment targeting for Rubicon', function () { + it('sets segment targeting for Magnite', function () { const data = transformedTargeting() const adUnits = getAdUnits() const config = getConfig() @@ -93,10 +93,29 @@ describe('permutiveRtdProvider', function () { }) } }) + it('sets segment targeting for TrustX', 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 === 'trustx') { + expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) }) describe('Custom segment targeting', function () { - it('sets custom segment targeting for Rubicon', function () { + it('sets custom segment targeting for Magnite', function () { const data = transformedTargeting() const adUnits = getAdUnits() const config = getConfig() @@ -129,6 +148,81 @@ describe('permutiveRtdProvider', function () { }) }) + describe('Existing key-value targeting', function () { + it('doesn\'t overwrite existing key-values for Xandr', 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 === '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) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + 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) + + function callback () { + 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']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for TrustX', 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 === 'trustx') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + }) + describe('Permutive on page', function () { it('checks if Permutive is on page', function () { expect(isPermutiveOnPage()).to.equal(false) @@ -168,7 +262,7 @@ function getConfig () { name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon', 'ozone'], + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], maxSegs: 500 } } @@ -197,15 +291,20 @@ function getTargetingData () { } function getAdUnits () { + const div_1_sizes = [ + [300, 250], + [300, 600] + ] + const div_2_sizes = [ + [728, 90], + [970, 250] + ] return [ { code: '/19968336/header-bid-tag-0', mediaTypes: { banner: { - sizes: [ - [300, 250], - [300, 600] - ] + sizes: div_1_sizes } }, bids: [ @@ -214,7 +313,7 @@ function getAdUnits () { params: { placementId: 13144370, keywords: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -228,7 +327,7 @@ function getAdUnits () { area: ['home'] }, visitor: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -242,12 +341,21 @@ function getAdUnits () { { settings: {}, targeting: { - inline_kvs: ['1', '2', '3', '4'] + test_kv: ['true'] } } ], ozoneData: {} } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } } ] }, @@ -255,17 +363,17 @@ function getAdUnits () { code: '/19968336/header-bid-tag-1', mediaTypes: { banner: { - sizes: [ - [728, 90], - [970, 250] - ] + sizes: div_2_sizes } }, bids: [ { bidder: 'appnexus', params: { - placementId: 13144370 + placementId: 13144370, + keywords: { + test_kv: ['true'] + } } }, { @@ -273,7 +381,14 @@ function getAdUnits () { params: { publisherId: 'OZONEGMG0001', siteId: '4204204209', - placementId: '0420420500' + placementId: '0420420500', + customData: [ + { + targeting: { + test_kv: ['true'] + } + } + ] } } ] From cdfc9dfeecd48fb9c7cdd116fc7ae976871f11c0 Mon Sep 17 00:00:00 2001 From: evanmsmrtb Date: Wed, 10 Mar 2021 12:56:49 -0600 Subject: [PATCH 081/198] SmartRTB Bid Adapter: add alias and update valid opts (#6365) * Add alias, update valid opts * Update bidder tests --- modules/smartrtbBidAdapter.js | 6 ++---- test/spec/modules/smartrtbBidAdapter_spec.js | 7 ++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/modules/smartrtbBidAdapter.js b/modules/smartrtbBidAdapter.js index 12d5a0ae7da..de303f9e4b2 100644 --- a/modules/smartrtbBidAdapter.js +++ b/modules/smartrtbBidAdapter.js @@ -17,11 +17,9 @@ function getDomain () { export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ 'banner', 'video' ], - aliases: ['smrtb'], + aliases: ['rdigital'], isBidRequestValid: function(bid) { - return (bid.params.pubId !== null && - bid.params.medId !== null && - bid.params.zoneId !== null); + return (bid.params.pubId !== null || bid.params.zoneId !== null); }, buildRequests: function(validBidRequests, bidderRequest) { let stack = (bidderRequest.refererInfo && diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/smartrtbBidAdapter_spec.js index cb5ceee0870..a7f30bdec6e 100644 --- a/test/spec/modules/smartrtbBidAdapter_spec.js +++ b/test/spec/modules/smartrtbBidAdapter_spec.js @@ -69,9 +69,6 @@ describe('SmartRTBBidAdapter', function () { it('should return a bidder code of smartrtb', function () { expect(spec.code).to.equal('smartrtb') }) - it('should alias smrtb', function () { - expect(spec.aliases.length > 0 && spec.aliases[0] === 'smrtb').to.be.true - }) }) describe('isBidRequestValid', function () { @@ -79,8 +76,8 @@ describe('SmartRTBBidAdapter', function () { expect(spec.isBidRequestValid(bannerRequest)).to.be.true }) - it('should return false if any zone id missing', function () { - expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { zoneId: null } }))).to.be.false + it('should return false if any zone id and pub id missing', function () { + expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { pubId: null, zoneId: null } }))).to.be.false }) }) From f1c45cd3a702f9b7b77da8d7be523756173809be Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 10 Mar 2021 13:17:06 -0800 Subject: [PATCH 082/198] Prebid 4.30.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dfd8a60b06d..befc6c79fbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.30.0-pre", + "version": "4.30.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 41b8607a004c111093a8143992b0191f1ff3bd44 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 10 Mar 2021 13:36:31 -0800 Subject: [PATCH 083/198] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index befc6c79fbd..7943e00f3c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.30.0", + "version": "4.31.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f88a55addb04ebdcaa31d43cf121895ea89461e3 Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Thu, 11 Mar 2021 04:00:03 -0500 Subject: [PATCH 084/198] Sonobi Bid Adapter: send eids in bid request. (#6364) * unwrapping id5id uid. Added new eid param for user id modules * set userid to new variable * fixed spelling mistake in unit test Co-authored-by: Scott Menzer * copying userid object so the referenced object does not get updated. * using deepClone instead of spreading the top userId object Co-authored-by: Scott Menzer --- modules/sonobiBidAdapter.js | 16 +++- test/spec/modules/sonobiBidAdapter_spec.js | 85 +++++++++++++++++++++- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index a55992cec22..0e4bfb37829 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,10 +1,9 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, deepClone } from '../src/utils.js'; 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'; - const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); @@ -117,7 +116,18 @@ export const spec = { payload.schain = JSON.stringify(validBidRequests[0].schain) } if (deepAccess(validBidRequests[0], 'userId') && Object.keys(validBidRequests[0].userId).length > 0) { - payload.userid = JSON.stringify(validBidRequests[0].userId); + 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) { + payload.eids = JSON.stringify(eids); } let keywords = validBidRequests[0].params.keywords; // a CSV of keywords diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 52821072a21..d1ac200394c 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -414,15 +414,92 @@ describe('SonobiBidAdapter', function () { expect(JSON.parse(bidRequests.data.schain)).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids as a JSON-encoded set of eids', function () { + bidRequest[0].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + bidRequest[1].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + 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(JSON.parse(bidRequests.data.eids)).to.eql([ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]); + }); + it('should return a properly formatted request with userid as a JSON-encoded set of User ID results', function () { - bidRequest[0].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; - bidRequest[1].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; + 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); 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(JSON.parse(bidRequests.data.userid)).to.eql({'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}); + 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 () { @@ -469,7 +546,7 @@ describe('SonobiBidAdapter', function () { ]; const bidRequests = spec.buildRequests(bRequest, bidderRequests); expect(bidRequests.url).to.equal('https://iad-2-apex.go.sonobi.com/trinity.json'); - }) + }); }); describe('.interpretResponse', function () { From 8e764f666530bf8ea57f1438907adb2951aa6d6f Mon Sep 17 00:00:00 2001 From: thuyhq <61451682+thuyhq@users.noreply.github.com> Date: Fri, 12 Mar 2021 16:48:38 +0700 Subject: [PATCH 085/198] Apacdex Bid Adapter: userId module support, show demo ads in debug mode & other maintenance (#6378) * Upgrade and maintenance apacdexBidAdapter * fix error and add unit test --- modules/apacdexBidAdapter.js | 145 ++++++++++++++++---- test/spec/modules/apacdexBidAdapter_spec.js | 101 +++++++++++++- 2 files changed, 221 insertions(+), 25 deletions(-) diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index 2582e4788c1..62ae3f54125 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,4 +1,5 @@ import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'apacdex'; const CONFIG = { @@ -49,11 +50,34 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { + let siteId; + let schain; + let eids; + let geo; + let test; + var bids = JSON.parse(JSON.stringify(validBidRequests)) bidderConfig = CONFIG[bids[0].bidder]; - const payload = {}; + + test = config.getConfig('debug'); bids.forEach(bidReq => { + siteId = siteId || bidReq.params.siteId; + + if (bidReq.schain) { + schain = schain || bidReq.schain + } + + if (bidReq.userIdAsEids) { + eids = eids || bidReq.userIdAsEids + } + + if (bidReq.params && bidReq.params.geo) { + if (validateGeoObject(bidReq.params.geo)) { + geo = bidReq.params.geo; + } + } + var targetKey = 0; if (bySlotTargetKey[bidReq.adUnitCode] != undefined) { targetKey = bySlotTargetKey[bidReq.adUnitCode]; @@ -73,36 +97,55 @@ export const spec = { bidReq.targetKey = targetKey; }); + const payload = {}; + payload.tmax = bidderRequest.timeout; + if (test) { + payload.test = 1; + } + payload.device = {}; payload.device.ua = navigator.userAgent; - payload.device.height = window.top.innerHeight; - payload.device.width = window.top.innerWidth; + payload.device.height = window.screen.width; + payload.device.width = window.screen.height; payload.device.dnt = _getDoNotTrack(); payload.device.language = navigator.language; + var pageUrl = _extractTopWindowUrlFromBidderRequest(bidderRequest); payload.site = {}; - payload.site.id = bids[0].params.siteId; - payload.site.page = _extractTopWindowUrlFromBidderRequest(bidderRequest); + payload.site.id = siteId; + payload.site.page = pageUrl payload.site.referrer = _extractTopWindowReferrerFromBidderRequest(bidderRequest); - payload.site.hostname = window.top.location.hostname; + payload.site.hostname = getDomain(pageUrl); // Apply GDPR parameters to request. - payload.gdpr = {}; if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr = {}; payload.gdpr.gdprApplies = !!bidderRequest.gdprConsent.gdprApplies; if (bidderRequest.gdprConsent.consentString) { payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; } } - // Apply schain. - if (bids[0].schain) { - payload.schain = bids[0].schain - } + // Apply us_privacy. if (bidderRequest && bidderRequest.uspConsent) { payload.us_privacy = bidderRequest.uspConsent; } + // Apply schain. + if (schain) { + payload.schain = schain + } + + // Apply eids. + if (eids) { + payload.eids = eids + } + + // Apply geo + if (geo) { + payload.geo = geo; + } + payload.bids = bids; return { @@ -115,12 +158,12 @@ export const spec = { }, interpretResponse: function (serverResponse, bidRequest) { const serverBody = serverResponse.body; - const serverBids = serverBody.bids; - // check overall response - if (!serverBody || typeof serverBody !== 'object') { + if (!serverBody || !utils.isPlainObject(serverBody)) { return []; } - if (!serverBids || typeof serverBids !== 'object') { + + const serverBids = serverBody.bids; + if (!serverBids || !utils.isArray(serverBids)) { return []; } @@ -192,15 +235,25 @@ function _getBiggestSize(sizes) { } function _getDoNotTrack() { - if (window.top.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack) { - if (window.top.doNotTrack == '1' || navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') { + try { + if (window.top.doNotTrack && window.top.doNotTrack == '1') { return 1; - } else { - return 0; } - } else { - return 0; - } + } catch (e) { } + + try { + if (navigator.doNotTrack && (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1')) { + return 1; + } + } catch (e) { } + + try { + if (navigator.msDoNotTrack && navigator.msDoNotTrack == '1') { + return 1; + } + } catch (e) { } + + return 0 } /** @@ -210,8 +263,11 @@ function _getDoNotTrack() { * @returns {string} */ function _extractTopWindowUrlFromBidderRequest(bidderRequest) { - if (bidderRequest && utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl')) { - return bidderRequest.refererInfo.canonicalUrl; + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + if (utils.deepAccess(bidderRequest, 'refererInfo.referer')) { + return bidderRequest.refererInfo.referer; } try { @@ -239,4 +295,45 @@ function _extractTopWindowReferrerFromBidderRequest(bidderRequest) { } } +/** + * Extracts the domain from given page url + * + * @param {string} url + * @returns {string} + */ +export function getDomain(pageUrl) { + if (config.getConfig('publisherDomain')) { + var publisherDomain = config.getConfig('publisherDomain'); + return publisherDomain.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; + } + + if (!pageUrl) { + return pageUrl; + } + + return pageUrl.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; +} + +/** + * Validate geo object + * + * @param {Object} geo + * @returns {boolean} + */ +export function validateGeoObject(geo) { + if (!utils.isPlainObject(geo)) { + return false; + } + if (!geo.lat) { + return false; + } + if (!geo.lon) { + return false; + } + if (!geo.accuracy) { + return false; + } + return true; +} + registerBidder(spec); diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index da9a050a8de..c3d0d025c0c 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -1,7 +1,8 @@ import { expect } from 'chai' -import { spec } from 'modules/apacdexBidAdapter.js' +import { spec, validateGeoObject, getDomain } from '../../../modules/apacdexBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' import { userSync } from '../../../src/userSync.js'; +import { config } from 'src/config.js'; describe('ApacdexBidAdapter', function () { const adapter = newBidder(spec) @@ -199,11 +200,34 @@ describe('ApacdexBidAdapter', function () { 'bidder': 'apacdex', 'params': { 'siteId': '1a2b3c4d5e6f1a2b3c4d', + 'geo': {'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60} }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'targetKey': 0, 'bidId': '30b31c1838de1f', + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'p0cCLF9JazY1ZUFjazJRb3NKbEprVTcwZ0IwRUlGalBjOG9laUZNbFJ0ZGpOSnVFbE9VMjBNMzNBTzladGt4cUVGQzBybDY2Y1FqT1dkUkFsMmJIWDRHNjlvNXJjbiUyQlZDd1dOTmt6VlV2TDhRd0F0RTlBcmpyZU5WRHBPU25GQXpyMnlT', + 'atype': 1 + }] + }, { + 'source': 'pubcid.org', + 'uids': [{ + 'id': '2ae366c2-2576-45e5-bd21-72ed10598f17', + 'atype': 1 + }] + }, { + 'source': 'sharedid.org', + 'uids': [{ + 'id': '01EZXQDVAPER4KE1VBS29XKV4Z', + 'atype': 1, + 'ext': { + 'third': '01EZXQDVAPER4KE1VBS29XKV4Z' + } + }] + }], }, { 'bidder': 'apacdex', @@ -300,10 +324,23 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.schain).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) + }); + it('should return a properly formatted request with geo defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.geo).to.deep.equal(bidRequest[0].params.geo) + }); it('should return a properly formatted request with us_privacy included', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); }); + it('should return a properly formatted request with pbjs_debug is true', function () { + config.setConfig({debug: true}); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.test).to.equal(1) + }); }); describe('.interpretResponse', function () { @@ -601,4 +638,66 @@ describe('ApacdexBidAdapter', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, [])).to.have.length(0); }); }); + + describe('validateGeoObject', function () { + it('should return true if the geo object is valid', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(true); + }); + + it('should return false if the geo object is not plain object', () => { + let geoObject = [{ + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }]; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lat attribute', () => { + let geoObject = { + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lon attribute', () => { + let geoObject = { + lat: 123.5624234, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing accuracy attribute', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + }); + + describe('getDomain', function () { + it('should return valid domain from publisherDomain config', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: pageUrl}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return valid domain from pageUrl argument', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return undefined if pageUrl and publisherDomain not config', () => { + let pageUrl; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal(pageUrl); + }); + }); }); From 72820960581e8298fc8580e3098e462105aee671 Mon Sep 17 00:00:00 2001 From: novatiq <79258366+novatiq@users.noreply.github.com> Date: Fri, 12 Mar 2021 16:01:35 +0200 Subject: [PATCH 086/198] Novatiq ID System: add snowflake userId submodule (#6350) * 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 --- modules/.submodules.json | 3 +- modules/novatiqIdSystem.js | 88 +++++++++++++++++++++++ modules/novatiqIdSystem.md | 36 ++++++++++ modules/userId/eids.js | 9 +++ modules/userId/eids.md | 7 ++ modules/userId/userId.md | 4 ++ test/spec/modules/novatiqIdSystem_spec.js | 70 ++++++++++++++++++ 7 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 modules/novatiqIdSystem.js create mode 100644 modules/novatiqIdSystem.md create mode 100644 test/spec/modules/novatiqIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index fc69dd276a3..a7cf1f54426 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -20,7 +20,8 @@ "fabrickIdSystem", "verizonMediaIdSystem", "pubProvidedIdSystem", - "tapadIdSystem" + "tapadIdSystem", + "novatiqIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js new file mode 100644 index 00000000000..fbfa6ca8abc --- /dev/null +++ b/modules/novatiqIdSystem.js @@ -0,0 +1,88 @@ +/** + * This module adds novatiqId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/novatiqIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; + +/** @type {Submodule} */ +export const novatiqIdSubmodule = { + +/** +* used to link submodule with config +* @type {string} +*/ + name: 'novatiq', + + /** +* decode the stored id value for passing to bid requests +* @function +* @returns {novatiq: {snowflake: string}} +*/ + decode(novatiqId, config) { + let responseObj = { + novatiq: { + snowflake: novatiqId + } + }; + return responseObj; + }, + + /** +* performs action to obtain id and return a value in the callback's response argument +* @function +* @param {SubmoduleConfig} config +* @returns {id: string} +*/ + getId(config) { + function snowflakeId(placeholder) { + return placeholder + ? (placeholder ^ Math.random() * 16 >> placeholder / 4).toString(16) + : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11 + 1e3).replace(/[018]/g, snowflakeId); + } + + const configParams = config.params || {}; + const srcId = this.getSrcId(configParams); + utils.logInfo('NOVATIQ Sync request used sourceid param: ' + srcId); + + let partnerhost; + partnerhost = window.location.hostname; + utils.logInfo('NOVATIQ partner hostname: ' + partnerhost); + + const novatiqId = snowflakeId(); + const url = 'https://spadsync.com/sync?sptoken=' + novatiqId + '&sspid=' + srcId + '&ssphost=' + partnerhost; + ajax(url, undefined, undefined, { method: 'GET', withCredentials: false }); + + utils.logInfo('NOVATIQ snowflake: ' + novatiqId); + return { 'id': novatiqId } + }, + + getSrcId(configParams) { + utils.logInfo('NOVATIQ Configured sourceid param: ' + configParams.sourceid); + + function isHex(str) { + var a = parseInt(str, 16); + return (a.toString(16) === str) + } + + let srcId; + if (typeof configParams.sourceid === 'undefined' || configParams.sourceid === null || configParams.sourceid === '') { + srcId = '000'; + utils.logInfo('NOVATIQ sourceid param set to value 000 due to undefined parameter or missing value in config section'); + } else if (configParams.sourceid.length < 3 || configParams.sourceid.length > 3) { + srcId = '001'; + utils.logInfo('NOVATIQ sourceid param set to value 001 due to wrong size in config section 3 chars max e.g. 1ab'); + } else if (isHex(configParams.sourceid) == false) { + srcId = '002'; + utils.logInfo('NOVATIQ sourceid param set to value 002 due to wrong format in config section expecting hex value only'); + } else { + srcId = configParams.sourceid; + } + return srcId + } +}; +submodule('userId', novatiqIdSubmodule); diff --git a/modules/novatiqIdSystem.md b/modules/novatiqIdSystem.md new file mode 100644 index 00000000000..ce561a696e3 --- /dev/null +++ b/modules/novatiqIdSystem.md @@ -0,0 +1,36 @@ +# Novatiq Snowflake ID + +Novatiq proprietary dynamic snowflake ID is a unique, non sequential and single use identifier for marketing activation. Our in network solution matches verification requests to telco network IDs, safely and securely inside telecom infrastructure. Novatiq snowflake ID can be used for identity validation and as a secured 1st party data delivery mechanism. + +## Novatiq Snowflake ID Configuration + +Enable by adding the Novatiq submodule to your Prebid.js package with: + +``` +gulp build --modules=novatiqIdSystem,userId +``` + +Module activation and configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'novatiq', + params: { + sourceid '1a3', // change to the Partner Number you received from Novatiq + } + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | Module identification: `"novatiq"` | `"novatiq"` | +| params | Required | Object | Configuration specifications for the Novatiq module. | | +| params.sourceid | Required | String | This is the Novatiq Partner Number obtained via Novatiq registration. | `1a3` | + +If you have any questions, please reach out to us at prebid@novatiq.com. diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 80750ccaae8..a7e5eaf6061 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -172,6 +172,15 @@ const USER_IDS_CONFIG = { 'tapadId': { source: 'tapad.com', atype: 1 + }, + + // Novatiq Snowflake + 'novatiq': { + getValue: function(data) { + return data.snowflake + }, + source: 'novatiq.com', + atype: 1 } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index b69c4b9bd5e..404066d53e4 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -148,6 +148,13 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 1 }] + }, + { + source: 'novatiq.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] } ] ``` diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 267b3a60cea..a7f98fb39a0 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -182,6 +182,10 @@ pbjs.setConfig({ { name: "criteo", value: { "criteoId": "wK-fkF8zaEIlMkZMbHl3eFo4NEtoNmZaeXJtYkFjZlVuWjBhcjJMaTRYd3pZNSUyQnlKRHNGRXlpdzdjd3pjVzhjcSUyQmY4eTFzN3VSZjV1ZyUyRlA0U2ZiR0UwN2I4bDZRJTNEJTNE" } + }, + { + name: "novatiq", + value: { "snowflake": "81b001ec-8914-488c-a96e-8c220d4ee08895ef" } }], syncDelay: 5000 } diff --git a/test/spec/modules/novatiqIdSystem_spec.js b/test/spec/modules/novatiqIdSystem_spec.js new file mode 100644 index 00000000000..60c82626450 --- /dev/null +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -0,0 +1,70 @@ +import { novatiqIdSubmodule } from 'modules/novatiqIdSystem.js'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('novatiqIdSystem', function () { + describe('getSrcId', function() { + it('getSrcId should set srcId value to 000 due to undefined parameter in config section', function() { + const config = { params: { } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set srcId value to 000 due to missing value in config section', function() { + const config = { params: { sourceid: '' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 000 due to null value in config section', function() { + const config = { params: { sourceid: null } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 001 due to wrong length in config section max 3 chars', function() { + const config = { params: { sourceid: '1234' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('001'); + }); + + it('getSrcId should set value to 002 due to wrong format in config section', function() { + const config = { params: { sourceid: '1xc' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('002'); + }); + }); + + describe('getId', function() { + it('should log message if novatiqId has wrong format', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).to.have.length(40); + }); + + it('should log message if novatiqId not provided', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).should.be.not.empty; + }); + }); + + describe('decode', function() { + it('should log message if novatiqId has wrong format', 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'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).should.be.not.empty; + }); + }); +}) From 4b0779a665d42eae01c8ef6bc02d0fc687da9565 Mon Sep 17 00:00:00 2001 From: lasloche <62240785+lasloche@users.noreply.github.com> Date: Mon, 15 Mar 2021 16:24:53 +0200 Subject: [PATCH 087/198] Rise Bid Adapter: add session_id & is_wrapper params to adapter (#6407) * add new params to rise adapter * add unit tests for isWrapper and sessionId adapter params --- modules/riseBidAdapter.js | 3 ++- test/spec/modules/riseBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index a891e0aa883..e3265ad5d3e 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -211,7 +211,8 @@ function generateParameters(bid, bidderRequest) { bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: utils.getBidIdParameter('auctionId', bid), + session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), + is_wrapper: !!params.isWrapper, publisher_name: domain, site_domain: domain, bidder_version: BIDDER_VERSION diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 176437c4f27..b3257cbda9d 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -75,6 +75,8 @@ describe('riseAdapter', function () { bidderCode: 'rise', } + const customSessionId = '12345678'; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { @@ -83,6 +85,22 @@ describe('riseAdapter', function () { } }); + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + it('sends bid request to test ENDPOINT via GET', function () { const requests = spec.buildRequests(testModeBidRequests, bidderRequest); for (const request of requests) { From da7872851f04e813eb165ea9c5923520421bc8ff Mon Sep 17 00:00:00 2001 From: lasloche <62240785+lasloche@users.noreply.github.com> Date: Mon, 15 Mar 2021 16:25:27 +0200 Subject: [PATCH 088/198] IronSource Bid Adapter: add session_id & is_wrapper params to adapter (#6408) * add new params * add unit tests for isWrapper and sessionId adapter params --- modules/ironsourceBidAdapter.js | 3 ++- test/spec/modules/ironsourceBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/ironsourceBidAdapter.js b/modules/ironsourceBidAdapter.js index ba510e86e7f..5b8531d7a85 100644 --- a/modules/ironsourceBidAdapter.js +++ b/modules/ironsourceBidAdapter.js @@ -211,7 +211,8 @@ function generateParameters(bid, bidderRequest) { bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: utils.getBidIdParameter('auctionId', bid), + session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), + is_wrapper: !!params.isWrapper, publisher_name: domain, site_domain: domain, bidder_version: BIDDER_VERSION diff --git a/test/spec/modules/ironsourceBidAdapter_spec.js b/test/spec/modules/ironsourceBidAdapter_spec.js index 93c3a6fb7b9..cca928ff28b 100644 --- a/test/spec/modules/ironsourceBidAdapter_spec.js +++ b/test/spec/modules/ironsourceBidAdapter_spec.js @@ -75,6 +75,8 @@ describe('ironsourceAdapter', function () { bidderCode: 'ironsource', } + const customSessionId = '12345678'; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { @@ -98,6 +100,22 @@ describe('ironsourceAdapter', function () { } }); + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + it('should send the correct width and height', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { From dc67fe8987886809ee1165a5b145fd7781dcd9d7 Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Mon, 15 Mar 2021 14:29:25 -0400 Subject: [PATCH 089/198] AMX Bid Adapter: add or update general adapter support and code refactoring (#6403) * AMX Bid adapter improvements * fix eslint issues (breaking CI) --- modules/amxBidAdapter.js | 115 +++++++++++------------- test/spec/modules/amxBidAdapter_spec.js | 87 ++++++++++++------ 2 files changed, 114 insertions(+), 88 deletions(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 497c2142b9b..5794b52dce0 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { parseUrl, deepAccess, _each, formatQS, getUniqueIdentifierStr, triggerPixel } from '../src/utils.js'; +import { parseUrl, deepAccess, _each, formatQS, getUniqueIdentifierStr, triggerPixel, isFn, logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -9,7 +9,6 @@ const storage = getStorageManager(737, BIDDER_CODE); const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; const VERSION = 'pba1.2.1'; -const xmlDTDRxp = /^\s*<\?xml[^\?]+\?>/; const VAST_RXP = /^\s*<\??(?:vast|xml)/i; const TRACKING_ENDPOINT = 'https://1x1.a-mo.net/hbx/'; const AMUID_KEY = '__amuidpb'; @@ -45,11 +44,16 @@ function flatMap(input, mapFn) { .reduce((acc, item) => item != null && acc.concat(item), []) } -const generateDTD = (xmlDocument) => - ``; - const isVideoADM = (html) => html != null && VAST_RXP.test(html); -const getMediaType = (bid) => isVideoADM(bid.adm) ? VIDEO : BANNER; + +function getMediaType(bid) { + if (isVideoADM(bid.adm)) { + return VIDEO; + } + + return BANNER; +} + const nullOrType = (value, type) => value == null || (typeof value) === type // eslint-disable-line valid-typeof @@ -103,6 +107,32 @@ const trackEvent = (eventName, data) => eid: getUniqueIdentifierStr(), })}`); +const DEFAULT_MIN_FLOOR = 0; + +function ensureFloor(floorValue) { + return typeof floorValue === 'number' && isFinite(floorValue) && floorValue > 0.0 + ? floorValue : DEFAULT_MIN_FLOOR; +} + +function getFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.floor', DEFAULT_MIN_FLOOR); + } + + try { + const floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + bidRequest: bid + }); + return floor.floor; + } catch (e) { + logError('call to getFloor failed: ', e); + return DEFAULT_MIN_FLOOR; + } +} + function convertRequest(bid) { const size = largestSize(bid.sizes, bid.mediaTypes) || [0, 0]; const isVideoBid = bid.mediaType === VIDEO || VIDEO in bid.mediaTypes @@ -116,16 +146,21 @@ function convertRequest(bid) { bid.sizes, deepAccess(bid, `mediaTypes.${BANNER}.sizes`, []) || [], deepAccess(bid, `mediaTypes.${VIDEO}.sizes`, []) || [], - ] + ]; + + const videoData = deepAccess(bid, `mediaTypes.${VIDEO}`, {}) || {}; const params = { au, av, + vd: videoData, vr: isVideoBid, ms: multiSizes, aw: size[0], ah: size[1], tf: 0, + sc: bid.schain || {}, + f: ensureFloor(getFloor(bid)) }; if (typeof tid === 'string' && tid.length > 0) { @@ -143,52 +178,6 @@ function decorateADM(bid) { return bid.adm + impressions; } -function transformXmlSimple(bid) { - const pixels = [] - _each([bid.nurl].concat(bid.ext != null && bid.ext.himp != null ? bid.ext.himp : []), (pixel) => { - if (pixel != null) { - pixels.push(``) - } - }); - // find the current "Impression" here & slice ours in - const impressionIndex = bid.adm.indexOf(' url != null); - - _each(pixels, (pxl) => { - const imagePixel = doc.createElement('Impression'); - const cdata = doc.createCDATASection(pxl); - imagePixel.appendChild(cdata); - root.appendChild(imagePixel); - }); - - const dtdMatch = xmlDTDRxp.exec(bid.adm); - return (dtdMatch != null ? dtdMatch[0] : generateDTD(doc)) + getOuterHTML(doc.documentElement); -} - function resolveSize(bid, request, bidId) { if (bid.w != null && bid.w > 1 && bid.h != null && bid.h > 1) { return [bid.w, bid.h]; @@ -212,14 +201,16 @@ function values(source) { }); } +const isTrue = (boolValue) => + boolValue === true || boolValue === 1 || boolValue === 'true'; + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid(bid) { return nullOrType(deepAccess(bid, 'params.endpoint', null), 'string') && - nullOrType(deepAccess(bid, 'params.tagId', null), 'string') && - nullOrType(deepAccess(bid, 'params.testMode', null), 'boolean'); + nullOrType(deepAccess(bid, 'params.tagId', null), 'string') }, buildRequests(bidRequests, bidderRequest) { @@ -239,8 +230,9 @@ export const spec = { brc: fbid.bidderRequestsCount || 0, bwc: fbid.bidderWinsCount || 0, trc: fbid.bidRequestsCount || 0, - tm: testMode, + tm: isTrue(testMode), V: '$prebid.version$', + vg: '$$PREBID_GLOBAL$$', i: (testMode && tagId != null) ? tagId : getID(loc), l: {}, f: 0.01, @@ -259,7 +251,8 @@ export const spec = { d: '', m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, - fpd: config.getLegacyFpd(config.getConfig('ortb2')), + fpd2: config.getConfig('ortb2'), + tmax: config.getConfig('bidderTimeout'), eids: values(bidRequests.reduce((all, bid) => { // we only want unique ones in here if (bid == null || bid.userIdAsEids == null) { @@ -306,7 +299,6 @@ export const spec = { }, interpretResponse(serverResponse, request) { - // validate the body/response const response = serverResponse.body; if (response == null || typeof response === 'string') { return []; @@ -320,13 +312,14 @@ export const spec = { return flatMap(response.r[bidID], (siteBid) => siteBid.b.map((bid) => { const mediaType = getMediaType(bid); - // let ad = null; - let ad = mediaType === BANNER ? decorateADM(bid) : decorateVideoADM(bid); + const ad = mediaType === BANNER ? decorateADM(bid) : bid.adm; + if (ad == null) { return null; } const size = resolveSize(bid, request.data, bidID); + const defaultExpiration = mediaType === BANNER ? 240 : 300; return ({ requestId: bidID, @@ -341,7 +334,7 @@ export const spec = { advertiserDomains: bid.adomain, mediaType, }, - ttl: mediaType === VIDEO ? 90 : 70 + ttl: typeof bid.exp === 'number' ? bid.exp : defaultExpiration, }); })).filter((possibleBid) => possibleBid != null); }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 0658fe9f33c..863a8a1d0fc 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -31,20 +31,6 @@ const sampleFPD = { } }; -const legacySampleFPD = { - context: { - keywords: 'sample keywords', - data: { - pageType: 'article' - - } - }, - user: { - gender: 'O', - yob: 1982, - } -}; - const stubConfig = (withStub) => { const stub = sinon.stub(config, 'getConfig').callsFake( (arg) => arg === 'ortb2' ? sampleFPD : null @@ -74,6 +60,15 @@ const sampleBidRequestBase = { endpoint: 'https://httpbin.org/post', }, sizes: [[320, 50]], + getFloor(params) { + if (params.size == null || params.currency == null || params.mediaType == null) { + throw new Error(`getFloor called with incomplete params: ${JSON.stringify(params)}`) + } + return { + floor: 0.5, + currency: 'USD' + } + }, mediaTypes: { [BANNER]: { sizes: [[300, 250]] @@ -85,13 +80,28 @@ const sampleBidRequestBase = { auctionId: utils.getUniqueIdentifierStr(), }; +const schainConfig = { + ver: '1.0', + nodes: [{ + asi: 'greatnetwork.exchange', + sid: '000001', + hp: 1, + rid: 'bid_request_1', + domain: 'publisher.com' + }] +}; + const sampleBidRequestVideo = { ...sampleBidRequestBase, bidId: sampleRequestId + '_video', sizes: [[300, 150]], + schain: schainConfig, mediaTypes: { [VIDEO]: { - sizes: [[360, 250]] + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' } } }; @@ -120,6 +130,7 @@ const sampleServerResponse = { 'h': 600, 'id': '2014691335735134254', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 300 }, @@ -139,6 +150,7 @@ const sampleServerResponse = { 'h': 1, 'id': '7735706981389902829', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 1 }, @@ -160,8 +172,11 @@ describe('AmxBidAdapter', () => { expect(spec.isBidRequestValid({params: { tagId: 'test' }})).to.equal(true) }); - it('testMode is an optional boolean', () => { - expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(false) + it('testMode is an optional truthy value', () => { + expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(true) + expect(spec.isBidRequestValid({params: { testMode: 'true' }})).to.equal(true) + // ignore invalid values (falsy) + expect(spec.isBidRequestValid({params: { testMode: 'non-truthy-invalid-value' }})).to.equal(true) expect(spec.isBidRequestValid({params: { testMode: false }})).to.equal(true) }); @@ -195,6 +210,17 @@ describe('AmxBidAdapter', () => { expect(url).to.equal('https://prebid.a-mo.net/a/c') }); + it('will read the prebid version & global', () => { + const { data: { V: prebidVersion, vg: prebidGlobal } } = spec.buildRequests([{ + ...sampleBidRequestBase, + params: { + testMode: true + } + }], sampleBidderRequest); + expect(prebidVersion).to.equal('$prebid.version$') + expect(prebidGlobal).to.equal('$$PREBID_GLOBAL$$') + }); + it('reads test mode from the first bid request', () => { const { data } = spec.buildRequests([{ ...sampleBidRequestBase, @@ -269,7 +295,7 @@ describe('AmxBidAdapter', () => { it('will forward first-party data', () => { stubConfig(() => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.fpd).to.deep.equal(legacySampleFPD) + expect(data.fpd2).to.deep.equal(sampleFPD) }); }); @@ -315,20 +341,24 @@ describe('AmxBidAdapter', () => { expect(data.m[sampleRequestId]).to.deep.equal({ av: true, au: 'div-gpt-ad-example', + vd: {}, ms: [ [[320, 50]], [[300, 250]], [] ], aw: 300, + sc: {}, ah: 250, tf: 0, + f: 0.5, vr: false }); expect(data.m[sampleRequestId + '_2']).to.deep.equal({ av: true, aw: 300, au: 'div-gpt-ad-example', + sc: {}, ms: [ [[320, 50]], [[300, 250]], @@ -336,7 +366,9 @@ describe('AmxBidAdapter', () => { ], i: 'example', ah: 250, + vd: {}, tf: 0, + f: 0.5, vr: false, }); }); @@ -354,7 +386,15 @@ describe('AmxBidAdapter', () => { av: true, aw: 360, ah: 250, + sc: schainConfig, + vd: { + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' + }, tf: 0, + f: 0.5, vr: true }); }); @@ -401,7 +441,7 @@ describe('AmxBidAdapter', () => { }, width: 300, height: 600, // from the bid itself - ttl: 70, + ttl: 90, ad: sampleDisplayAd( `` + `` @@ -412,20 +452,13 @@ describe('AmxBidAdapter', () => { it('can parse a video ad', () => { const parsed = spec.interpretResponse({ body: sampleServerResponse }, baseRequest) expect(parsed.length).to.equal(2) - - // we should have display, video, display - const xml = parsed[1].vastXml - delete parsed[1].vastXml - - expect(xml).to.have.string(``) - expect(xml).to.have.string(``) - expect(parsed[1]).to.deep.equal({ ...baseBidResponse, meta: { ...baseBidResponse.meta, mediaType: VIDEO, }, + vastXml: sampleVideoAd(''), width: 300, height: 250, ttl: 90, From 670ab08c3e030cf04b94e324719c3ad159ebf309 Mon Sep 17 00:00:00 2001 From: hybrid-ai <58724131+hybrid-ai@users.noreply.github.com> Date: Tue, 16 Mar 2021 07:25:34 +0300 Subject: [PATCH 090/198] VOX Bid adapter, Hybrid Bid adapter: fix global var name to avoid conflicts with astraOne adapter. (#6416) Co-authored-by: Petrov Denis --- modules/hybridBidAdapter.js | 2 +- modules/voxBidAdapter.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index dd55483ef33..e7281086a92 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -164,7 +164,7 @@ function wrapAd(bid, bidData) { parentDocument.style.width = "100%"; } var _content = "${encodeURIComponent(JSON.stringify(bid.inImageContent))}"; - window._ao_ssp.registerInImage(JSON.parse(decodeURIComponent(_content))); + window._hyb_prebid_ssp.registerInImage(JSON.parse(decodeURIComponent(_content))); `; diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index 450f270db31..73df9bb8b9b 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -129,7 +129,7 @@ function wrapInImageBanner(bid, bidData) { var s = document.getElementById("prebidrenderer"); s.onload = function () { var _html = "${encodeURIComponent(JSON.stringify(bid))}"; - window._ao_ssp.registerInImage(JSON.parse(decodeURIComponent(_html))); + window._hyb_prebid_ssp.registerInImage(JSON.parse(decodeURIComponent(_html))); } s.src = "https://st.hybrid.ai/prebidrenderer.js?t=" + Date.now(); if (parent.window.frames[window.name]) { @@ -157,7 +157,7 @@ function wrapBanner(bid, bidData) { var s = document.getElementById("prebidrenderer"); s.onload = function () { var _html = "${encodeURIComponent(JSON.stringify(bid))}"; - window._ao_ssp.registerAds(JSON.parse(decodeURIComponent(_html))); + window._hyb_prebid_ssp.registerAds(JSON.parse(decodeURIComponent(_html))); } s.src = "https://st.hybrid.ai/prebidrenderer.js?t=" + Date.now(); From 20bea36f5fc6cc5b8d5d0a7c82fa852e7e060eee Mon Sep 17 00:00:00 2001 From: Yevhenii Melnyk Date: Tue, 16 Mar 2021 05:31:55 +0100 Subject: [PATCH 091/198] LiveIntent Id System: fix for parsing response twice (#6418) * Don't parse response twice in LiveIntent Id submodule * Update the liveintent module test to have the 204 response --- modules/liveIntentIdSystem.js | 10 +--------- test/spec/modules/liveIntentIdSystem_spec.js | 17 ++++++++++------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index f87d67aae8e..5a955eefa92 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -166,15 +166,7 @@ export const liveIntentIdSubmodule = { const result = function(callback) { liveConnect.resolve( response => { - let responseObj = {}; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - } - callback(responseObj); + callback(response); }, error => { utils.logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 3b4e4b9d9a7..f1de2f3bf93 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -36,7 +36,7 @@ describe('LiveIntentId', function() { resetLiveIntentIdSubmodule(); }); - it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function() { + it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function () { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, @@ -47,12 +47,16 @@ describe('LiveIntentId', function() { submoduleCallback(callBackSpy); let request = server.requests[1]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&gdpr_consent=consentDataString.*/); + const response = { + unifiedId: 'a_unified_id', + segments: [123, 234] + } request.respond( 200, responseHeader, - JSON.stringify({}) + JSON.stringify(response) ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith(response)).to.be.true; }); it('should fire an event when getId', function() { @@ -131,11 +135,10 @@ describe('LiveIntentId', function() { let request = server.requests[1]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899'); request.respond( - 200, - responseHeader, - JSON.stringify({}) + 204, + responseHeader ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith({})).to.be.true; }); it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { From b9cd1a4e6a7c46d1095b8886f1e64c7d021603a3 Mon Sep 17 00:00:00 2001 From: Aleksa Trajkovic Date: Tue, 16 Mar 2021 13:54:50 +0100 Subject: [PATCH 092/198] PBJS Core: use mediaType renderer when backupOnly and no bid.renderer (#6419) * use mediaType renderer when backupOnly and no bid.renderer * check if necessary renderer properties are defined --- src/auction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auction.js b/src/auction.js index 217a50be3d6..7005d56827e 100644 --- a/src/auction.js +++ b/src/auction.js @@ -533,9 +533,9 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { var renderer = null; // the renderer for the mediaType takes precendence - if (mediaTypeRenderer && mediaTypeRenderer.url && !(mediaTypeRenderer.backupOnly === true && mediaTypeRenderer.render)) { + if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) { renderer = mediaTypeRenderer; - } else if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly === true && bid.renderer)) { + } else if (adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render && !(adUnitRenderer.backupOnly === true && bid.renderer)) { renderer = adUnitRenderer; } From 70a41a51ac945b90c47224c294e64fc35742d604 Mon Sep 17 00:00:00 2001 From: susyt Date: Tue, 16 Mar 2021 07:56:52 -0700 Subject: [PATCH 093/198] GumGum Bid Adapter: pass bidfloor currency in bidrequest (#6391) * adds support for zone and pubId params * adds support for iriscat field * sets mediatype depending on product id * Update doc for mediaType needed for video products * makes slot and invideo products avail for pubId * updates gumgum doc * lint * adds missing comma in gumgum doc * adds currency in ad request, adds unit test * readd the previous irisid changes * remove the only in testing --- modules/gumgumBidAdapter.js | 24 +++++++++++----------- test/spec/modules/gumgumBidAdapter_spec.js | 4 ++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 2b0f7e03d22..9a01cd21fa4 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -208,25 +208,24 @@ function _getVidParams (attributes) { * @param {Object} bid * @returns {Number} floor */ -function _getFloor (mediaTypes, bidfloor, bid) { +function _getFloor (mediaTypes, staticBidfloor, bid) { const curMediaType = Object.keys(mediaTypes)[0] || 'banner'; - let floor = bidfloor || 0; + const bidFloor = { floor: 0, currency: 'USD' }; if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', + const { currency, floor } = bid.getFloor({ mediaType: curMediaType, size: '*' }); + floor && (bidFloor.floor = floor); + currency && (bidFloor.currency = currency); - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); + if (staticBidfloor && floor && currency === 'USD') { + bidFloor.floor = Math.max(staticBidfloor, parseFloat(floor)); } } - return floor; + return bidFloor; } /** @@ -250,7 +249,7 @@ function buildRequests (validBidRequests, bidderRequest) { transactionId, userId = {} } = bidRequest; - const bidFloor = _getFloor(mediaTypes, params.bidfloor, bidRequest); + const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); let sizes = [1, 1]; let data = {}; @@ -265,8 +264,9 @@ function buildRequests (validBidRequests, bidderRequest) { data.pv = pageViewId; } - if (bidFloor) { - data.fp = bidFloor; + if (floor) { + data.fp = floor; + data.fpc = currency; } if (params.iriscat && typeof params.iriscat === 'string') { diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 0d37c8b1d25..3f5d32bcef7 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -258,6 +258,10 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.fp).to.equal(bidfloor); }); + it('should return a floor currency', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.fpc).to.equal(floorTestData.currency); + }) }); it('sends bid request to ENDPOINT via GET', function () { From 46ecf2c41400d933ecdf77ba0c1e5513e1982d97 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 16 Mar 2021 21:37:28 -0700 Subject: [PATCH 094/198] Documentation: fixed a typo and sentence structure (#6421) --- modules/geoedgeRtdProvider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md index e4aa046a97d..5414606612c 100644 --- a/modules/geoedgeRtdProvider.md +++ b/modules/geoedgeRtdProvider.md @@ -4,7 +4,7 @@ Module Name: Geoedge Rtd provider Module Type: Rtd Provider Maintainer: guy.books@geoedge.com -The Geoedge Realtime module let pusblishers to block bad ads such as automatic redirects, malware, offensive creatives and landing pages. +The Geoedge Realtime module lets publishers block bad ads such as automatic redirects, malware, offensive creatives and landing pages. To use this module, you'll need to work with [Geoedge](https://www.geoedge.com/publishers-real-time-protection/) to get an account and cutomer key. ## Integration From 8beade60241293e09d745bec932719b59134b2f5 Mon Sep 17 00:00:00 2001 From: Daniel Liebner Date: Wed, 17 Mar 2021 01:03:06 -0400 Subject: [PATCH 095/198] Bid Glass Bid Adapter: pass options in bid request (#6424) * Added bidglass adapter + test * PR Review Updates: - Added formal params to getUserSyncs function definition - getUserSyncs now always returns an array - Improved unit test coverage * PR Review Updates: - Removed unused methods: getUserSyncs, onTimeout, onBidWon, onSetTargeting - Removed getUserSyncs unit test - Removed "dead code" - Removed some unnecessary comments - Fixed usage of parseInt * Bid Glass Bid Adapter: pass options in bid request --- modules/bidglassBidAdapter.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index 6db35f184ca..3162228ce58 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { return window === window.top ? window.location.href : window.parent === window.top ? document.referrer : null; }; let getOrigins = function() { - var ori = ['https://' + window.location.hostname]; + var ori = [window.location.protocol + '//' + window.location.hostname]; if (window.location.ancestorOrigins) { for (var i = 0; i < window.location.ancestorOrigins.length; i++) { @@ -56,7 +56,7 @@ export const spec = { // Derive the parent origin var parts = document.referrer.split('/'); - ori.push('https://' + parts[2]); + ori.push(parts[0] + '//' + parts[2]); if (window.parent !== window.top) { // Additional unknown origins exist @@ -71,11 +71,15 @@ export const spec = { bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); bid.sizes = bid.sizes.filter(size => utils.isArray(size)); - // Stuff to send: [bid id, sizes, adUnitId] + var options = utils.deepClone(bid.params); + delete options.adUnitId; + + // Stuff to send: [bid id, sizes, adUnitId, options] imps.push({ bidId: bid.bidId, sizes: bid.sizes, - adUnitId: utils.getBidIdParameter('adUnitId', bid.params) + adUnitId: utils.getBidIdParameter('adUnitId', bid.params), + options: options }); }); From 990d287e3f158c2dca366c39c2f58d509137d8d1 Mon Sep 17 00:00:00 2001 From: Nick Peceniak Date: Wed, 17 Mar 2021 00:27:41 -0600 Subject: [PATCH 096/198] Spotx Bid Adapter: add publisher support for cache.ignoreBidderCacheKey (#6413) * Support ignoreBidderCacheKey in spotxBidAdapter * Update spotxBidAdapter_spec.js * Update spotxBidAdapter_spec.js Co-authored-by: Nick Peceniak --- modules/spotxBidAdapter.js | 16 ++++++++++--- test/spec/modules/spotxBidAdapter_spec.js | 28 +++++++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index c85a836435e..05e4e0ba1ef 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -215,6 +215,12 @@ export const spec = { } }; + // If the publisher asks to ignore the bidder cache key we need to return the full vast xml + // so that it can be cached on the publishes specified server. + if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { + requestPayload['ext']['wrap_response'] = 0; + } + if (utils.getBidIdParameter('number_of_ads', bid.params)) { requestPayload['ext']['number_of_ads'] = utils.getBidIdParameter('number_of_ads', bid.params); } @@ -336,14 +342,18 @@ export const spec = { ttl: 360, netRevenue: true, channel_id: serverResponseBody.id, - cache_key: spotxBid.ext.cache_key, - vastUrl: 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key, - videoCacheKey: spotxBid.ext.cache_key, mediaType: VIDEO, width: spotxBid.w, height: spotxBid.h }; + if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { + bid.vastXml = spotxBid.adm; + } else { + bid.cache_key = spotxBid.ext.cache_key; + bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key + } + bid.meta = bid.meta || {}; if (spotxBid && spotxBid.adomain && spotxBid.adomain.length > 0) { bid.meta.advertiserDomains = spotxBid.adomain; diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index 927599ac986..873914441aa 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -379,6 +379,32 @@ describe('the spotx adapter', function () { expect(request.data.site.page).to.equal('prebid.js'); }); + + it('should set ext.wrap_response to 0 when cache url is set and ignoreBidderCacheKey is true', function() { + var request; + + var origGetConfig = config.getConfig; + sinon.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'cache') { + return { + url: 'prebidCacheLocation', + ignoreBidderCacheKey: true + }; + } + if (key === 'cache.url') { + return 'prebidCacheLocation'; + } + if (key === 'cache.ignoreBidderCacheKey') { + return true; + } + return origGetConfig.apply(config, arguments); + }); + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.ext.wrap_response).to.equal(0); + config.getConfig.restore(); + }); }); describe('interpretResponse', function() { @@ -469,7 +495,6 @@ describe('the spotx adapter', function () { expect(responses[0].requestId).to.equal(123); expect(responses[0].ttl).to.equal(360); expect(responses[0].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); - expect(responses[0].videoCacheKey).to.equal('cache123'); expect(responses[0].width).to.equal(400); expect(responses[1].cache_key).to.equal('cache124'); expect(responses[1].channel_id).to.equal(12345); @@ -483,7 +508,6 @@ describe('the spotx adapter', function () { expect(responses[1].requestId).to.equal(124); expect(responses[1].ttl).to.equal(360); expect(responses[1].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache124'); - expect(responses[1].videoCacheKey).to.equal('cache124'); expect(responses[1].width).to.equal(200); }); }); From a75f2f48356fb74aedf825971e1b06a619984443 Mon Sep 17 00:00:00 2001 From: BizzClick <73241175+BizzClick@users.noreply.github.com> Date: Wed, 17 Mar 2021 12:57:21 +0200 Subject: [PATCH 097/198] update prebid adapter. Add at, ccpa, gdpr and coppa support (#6405) --- modules/bizzclickBidAdapter.js | 19 ++++++++++++ test/spec/modules/bizzclickBidAdapter_spec.js | 29 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 80d2f6b5395..2af9a7afed2 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -81,10 +81,12 @@ export const spec = { let data = { id: bidRequest.bidId, test: config.getConfig('debug') ? 1 : 0, + at: 1, cur: ['USD'], device: { w: winTop.screen.width, h: winTop.screen.height, + dnt: utils.getDNT() ? 1 : 0, language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', }, site: { @@ -94,9 +96,26 @@ export const spec = { source: { tid: bidRequest.transactionId }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} + }, + user: { + ext: {} + }, tmax: bidRequest.timeout, imp: [impObject], }; + if (bidRequest) { + if (bidRequest.gdprConsent && bidRequest.gdprConsent.gdprApplies) { + utils.deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0); + utils.deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString); + } + + if (bidRequest.uspConsent !== undefined) { + utils.deepSetValue(data, 'regs.ext.us_privacy', bidRequest.uspConsent); + } + } bids.push(data) } return { diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js index 39ad4ae39c9..e0698c9eda8 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/bizzclickBidAdapter.js'; +import {config} from 'src/config.js'; const NATIVE_BID_REQUEST = { code: 'native_example', @@ -53,7 +54,11 @@ const BANNER_BID_REQUEST = { accountId: 'accountId' }, timeout: 1000, - + gdprConsent: { + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + gdprApplies: 1, + }, + uspConsent: 'uspConsent' } const bidRequest = { @@ -172,6 +177,22 @@ const NATIVE_BID_RESPONSE = { }; describe('BizzclickAdapter', function() { + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([BANNER_BID_REQUEST]); + expect(serverRequest.data[0].regs.coppa).to.equal(1); + }); + }); + describe('isBidRequestValid', function() { it('should return true when required params found', function () { expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); @@ -225,6 +246,12 @@ describe('BizzclickAdapter', function() { expect(request.method).to.equal('POST'); }); + it('check consent and ccpa string is set properly', function() { + expect(request.data[0].regs.ext.gdpr).to.equal(1); + expect(request.data[0].user.ext.consent).to.equal(BANNER_BID_REQUEST.gdprConsent.consentString); + expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); + }) + it('Returns valid URL', function () { expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); }); From b98707c541e473db84b8891ec67cdd59bda72233 Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Thu, 18 Mar 2021 00:25:10 +0700 Subject: [PATCH 098/198] Qwarry Bid Adapter: add referer detection (#6412) * qwarry bid adapter * formatting fixes * fix tests for qwarry * qwarry bid adapter * add header for qwarry bid adapter * bid requests fix * fix tests * response fix * fix tests for Qwarry bid adapter * add pos parameter to qwarry bid adapter * qwarryBidAdapter onBidWon hotfix * Change bidder endpoint url for Qwarry adapter * add referer JS detection * use bidderRequest.refererInfo * fix tests Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 2 +- test/spec/modules/qwarryBidAdapter_spec.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index 7cb83520979..5ff48ec53f6 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -27,7 +27,7 @@ export const spec = { return { method: 'POST', url: ENDPOINT, - data: { requestId: bidderRequest.bidderRequestId, bids }, + data: { requestId: bidderRequest.bidderRequestId, bids, referer: bidderRequest.refererInfo.referer }, options: { contentType: 'application/json', customHeaders: { diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 91e3cf4bfdf..bc776f7ebe7 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -70,11 +70,12 @@ describe('qwarryBidAdapter', function () { describe('buildRequests', function () { let bidRequests = [REQUEST] - const bidderRequest = spec.buildRequests(bidRequests, { bidderRequestId: '123' }) + const bidderRequest = spec.buildRequests(bidRequests, { bidderRequestId: '123', refererInfo: { referer: 'http://test.com/path.html' } }) it('sends bid request to ENDPOINT via POST', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') + expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') From 24114d136537ff9f2f162f85adc45ef2e4a41433 Mon Sep 17 00:00:00 2001 From: SmartyAdsSSP <41569976+SmartyAdsSSP@users.noreply.github.com> Date: Wed, 17 Mar 2021 19:35:58 +0200 Subject: [PATCH 099/198] Smartyads Bid Adapter: add coppa field from config (#6402) * update adapter. Add coppa field from config * move stubs and restores for coppa tests --- modules/smartyadsBidAdapter.js | 2 ++ test/spec/modules/smartyadsBidAdapter_spec.js | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index ff60d08e48b..610617155ed 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,5 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; import * as utils from '../src/utils.js'; const BIDDER_CODE = 'smartyads'; @@ -49,6 +50,7 @@ export const spec = { 'secure': 1, 'host': location.host, 'page': location.pathname, + 'coppa': config.getConfig('coppa') === true ? 1 : 0, 'placements': placements }; request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 2780e88255d..8804050134a 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from '../../../modules/smartyadsBidAdapter.js'; +import { config } from '../../../src/config.js'; describe('SmartyadsAdapter', function () { let bid = { @@ -38,9 +39,10 @@ describe('SmartyadsAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); + expect(data.coppa).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'); @@ -57,6 +59,23 @@ describe('SmartyadsAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); + + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([bid]); + expect(serverRequest.data.coppa).to.equal(1); + }); + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { From e0bd8e27d979ad9e7c4b9fe0be28a5f12d5f9a06 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:15:24 -0400 Subject: [PATCH 100/198] PBS Bid Adapter: Fpd2.0 bug fix for first party data issue (#6428) * Bug fix for PBS data from FPD2.0 update: Merging request.site and request.user with site and user object in FPD. --- modules/prebidServerBidAdapter/index.js | 4 ++-- test/spec/modules/prebidServerBidAdapter_spec.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 088b5430f46..d3bbb347720 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -769,10 +769,10 @@ const OPEN_RTB_PROTOCOL = { const commonFpd = getConfig('ortb2') || {}; if (commonFpd.site) { - utils.deepSetValue(request, 'site', commonFpd.site); + utils.mergeDeep(request, {site: commonFpd.site}); } if (commonFpd.user) { - utils.deepSetValue(request, 'user', commonFpd.user); + utils.mergeDeep(request, {user: commonFpd.user}); } addBidderFirstPartyDataToRequest(request); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index babee7e10d7..45d96b96d66 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1631,13 +1631,14 @@ describe('S2S Adapter', function () { } } })); + const commonContextExpected = utils.mergeDeep({'page': 'http://mytestpage.com', 'publisher': {'id': '1'}}, commonContext); config.setConfig({ fpd: { context: commonContext, user: commonUser } }); config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user } } }); adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); - expect(parsedRequestBody.site).to.deep.equal(commonContext); + expect(parsedRequestBody.site).to.deep.equal(commonContextExpected); expect(parsedRequestBody.user).to.deep.equal(commonUser); }); From 04e49740cdd511cd024649d00bb74d6cc681b534 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 17 Mar 2021 15:26:26 -0400 Subject: [PATCH 101/198] Prebid 4.31.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7943e00f3c6..5af93962114 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.31.0-pre", + "version": "4.31.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 76a78287df25c4914ee22e3516cb0f103bf88024 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 17 Mar 2021 15:46:06 -0400 Subject: [PATCH 102/198] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5af93962114..ed13b6ae7da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.31.0", + "version": "4.32.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From c245959f925283eb5e356a2826dead5ca72f2c32 Mon Sep 17 00:00:00 2001 From: Junus Date: Thu, 18 Mar 2021 08:19:43 +0600 Subject: [PATCH 103/198] a4g Bid Adapter: delete adid and use crid if it exists (#6409) * Deleted adid * set crid if it's exist and added unit tests --- modules/a4gBidAdapter.js | 3 +- test/spec/modules/a4gBidAdapter_spec.js | 50 ++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js index b7d8722e9f9..1961dba1f10 100644 --- a/modules/a4gBidAdapter.js +++ b/modules/a4gBidAdapter.js @@ -70,8 +70,7 @@ export const spec = { if (response.cpm > 0) { const bidResponse = { requestId: response.id, - creativeId: response.id, - adId: response.id, + creativeId: response.crid || response.id, cpm: response.cpm, width: response.width, height: response.height, diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js index 3dccbb28426..cb05fa62ab6 100644 --- a/test/spec/modules/a4gBidAdapter_spec.js +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/a4gBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('a4gAdapterTests', function () { describe('bidRequestValidity', function () { @@ -139,15 +140,54 @@ describe('a4gAdapterTests', function () { const bidResponse = { body: [{ - 'id': 'div-gpt-ad-1460505748561-0', + 'id': '51ef8751f9aead', 'ad': 'test ad', 'width': 320, 'height': 250, - 'cpm': 5.2 + 'cpm': 5.2, + 'crid': '111' }], headers: {} }; + it('should get correct bid response for banner ad', function () { + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '111', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set creativeId to default value if not provided', function () { + const bidResponseWithoutCrid = utils.deepClone(bidResponse); + delete bidResponseWithoutCrid.body[0].crid; + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '51ef8751f9aead', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponseWithoutCrid, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }) + it('required keys', function () { const result = spec.interpretResponse(bidResponse, bidRequest); @@ -169,5 +209,11 @@ describe('a4gAdapterTests', function () { expect(requiredKeys.indexOf(key) !== -1).to.equal(true); }); }) + + it('adId should not be equal to requestId', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].requestId).to.not.equal(result[0].adId); + }) }); }); From 595fc0a08b5f97a319b81f8099394623218efe7c Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Thu, 18 Mar 2021 07:47:26 -0500 Subject: [PATCH 104/198] Mediawallah ID System: add openlink userId submodule (#5921) * My first commit * Removed unnecessary await operation * Bug fixes and compliance fixes * Fixing some formatting and naming * Updating code based on automated feedback. * Parking refactoring change for team review * update mwOpenLink module * remove .git 2 folder * Trying to force a change * update the PR comments * applying the changes * update submodules.json and userId.md * fix typo of module names * update module decode function and test code * update test codes * apply the suggestions from Prebid * fix count of modules. Co-authored-by: Eric Brown Co-authored-by: hanna Co-authored-by: hannapanova190119 <71532550+hannapanova190119@users.noreply.github.com> --- modules/.submodules.json | 1 + modules/mwOpenLinkIdSystem.js | 142 +++++++++++++++++++ modules/mwOpenLinkIdSystem.md | 43 ++++++ modules/userId/eids.js | 6 +- modules/userId/eids.md | 7 + modules/userId/userId.md | 7 + test/spec/modules/mwOpenLinkIdSystem_spec.js | 20 +++ test/spec/modules/userId_spec.js | 71 +++++++--- 8 files changed, 280 insertions(+), 17 deletions(-) create mode 100644 modules/mwOpenLinkIdSystem.js create mode 100644 modules/mwOpenLinkIdSystem.md create mode 100644 test/spec/modules/mwOpenLinkIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index a7cf1f54426..0f62627822a 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -20,6 +20,7 @@ "fabrickIdSystem", "verizonMediaIdSystem", "pubProvidedIdSystem", + "mwOpenLinkIdSystem", "tapadIdSystem", "novatiqIdSystem" ], diff --git a/modules/mwOpenLinkIdSystem.js b/modules/mwOpenLinkIdSystem.js new file mode 100644 index 00000000000..b2381836d5d --- /dev/null +++ b/modules/mwOpenLinkIdSystem.js @@ -0,0 +1,142 @@ +/** + * This module adds MediaWallah OpenLink to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/mwOpenLinkIdSystem + * @requires module:modules/userId + */ + +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'; + +const openLinkID = { + name: 'mwol', + cookie_expiration: (86400 * 1000 * 365 * 1) // 1 year +} + +const storage = getStorageManager(); + +function getExpirationDate() { + return (new Date(utils.timestamp() + openLinkID.cookie_expiration)).toGMTString(); +} + +function isValidConfig(configParams) { + if (!configParams) { + utils.logError('User ID - mwOlId submodule requires configParams'); + return false; + } + if (!configParams.accountId) { + utils.logError('User ID - mwOlId submodule requires accountId to be defined'); + return false; + } + if (!configParams.partnerId) { + utils.logError('User ID - mwOlId submodule requires partnerId to be defined'); + return false; + } + return true; +} + +function deserializeMwOlId(mwOlIdStr) { + const mwOlId = {}; + const mwOlIdArr = mwOlIdStr.split(','); + + mwOlIdArr.forEach(function(value) { + const pair = value.split(':'); + // unpack a value of 1 as true + mwOlId[pair[0]] = +pair[1] === 1 ? true : pair[1]; + }); + + return mwOlId; +} + +function serializeMwOlId(mwOlId) { + let components = []; + + if (mwOlId.eid) { + components.push('eid:' + mwOlId.eid); + } + if (mwOlId.ibaOptout) { + components.push('ibaOptout:1'); + } + if (mwOlId.ccpaOptout) { + components.push('ccpaOptout:1'); + } + + return components.join(','); +} + +function readCookie(name) { + if (!name) name = openLinkID.name; + const mwOlIdStr = storage.getCookie(name); + if (mwOlIdStr) { + return deserializeMwOlId(decodeURIComponent(mwOlIdStr)); + } + return null; +} + +function writeCookie(mwOlId) { + if (mwOlId) { + const mwOlIdStr = encodeURIComponent(serializeMwOlId(mwOlId)); + storage.setCookie(openLinkID.name, mwOlIdStr, getExpirationDate(), 'lax'); + } +} + +function register(configParams, olid) { + const { accountId, partnerId, uid } = configParams; + const url = 'https://ol.mediawallahscript.com/?account_id=' + accountId + + '&partner_id=' + partnerId + + '&uid=' + uid + + '&olid=' + olid + + '&cb=' + Math.random() + ; + ajax(url); +} + +function setID(configParams) { + if (!isValidConfig(configParams)) return undefined; + const mwOlId = readCookie(); + const newMwOlId = mwOlId ? utils.deepClone(mwOlId) : {eid: utils.generateUUID()}; + writeCookie(newMwOlId); + register(configParams, newMwOlId.eid); + return { + id: newMwOlId + }; +}; + +/* End MW */ + +export { writeCookie }; + +/** @type {Submodule} */ +export const mwOpenLinkIdSubModule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'mwOpenLinkId', + /** + * decode the stored id value for passing to bid requests + * @function + * @param {MwOlId} mwOlId + * @return {(Object|undefined} + */ + decode(mwOlId) { + const id = mwOlId && utils.isPlainObject(mwOlId) ? mwOlId.eid : undefined; + return id ? { 'mwOpenLinkId': id } : undefined; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} [submoduleParams] + * @returns {id:MwOlId | undefined} + */ + getId(submoduleConfig) { + const submoduleConfigParams = (submoduleConfig && submoduleConfig.params) || {}; + if (!isValidConfig(submoduleConfigParams)) return undefined; + return setID(submoduleConfigParams); + } +}; + +submodule('userId', mwOpenLinkIdSubModule); diff --git a/modules/mwOpenLinkIdSystem.md b/modules/mwOpenLinkIdSystem.md new file mode 100644 index 00000000000..f55913f2983 --- /dev/null +++ b/modules/mwOpenLinkIdSystem.md @@ -0,0 +1,43 @@ +## MediaWallah openLink User ID Submodule + +OpenLink ID User ID Module generates a UUID that can be utilized to improve user matching. This module enables timely synchronization which handles MediaWallah optout. You must have a pre-existing relationship with MediaWallah prior to integrating. + +### Building Prebid with openLink Id Support +Your Prebid build must include the modules for both **userId** and **mwOpenLinkId** submodule. Follow the build instructions for Prebid as +explained in the top level README.md file of the Prebid source tree. + +ex: $ gulp build --modules=userId,mwOpenLinkIdSystem + +##$ MediaWallah openLink ID Example Configuration + +When the module is included, it's automatically enabled and saves an id to both cookie with an expiration time of 1 year. + +### Prebid Params + +Individual params may be set for the MediaWallah openLink User ID Submodule. At least accountId and partnerId must be set in the params. + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'mwOpenLinkId', + params: { + accountId: '1000', + partnerId: '1001', + uid: 'u-123xyz' + } + }] + } +}); +``` + +### Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the MediaWallah OpenLink ID User ID Module integration. + +| Params under usersync.userIds[]| Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `'mwOpenLinkId'` | +| params | Required | Object | Details for mwOLID syncing. | | +| params.accountId | Required | String | The MediaWallah assigned Account Id | `1000` | +| params.partnerId | Required | String | The MediaWallah assign Partner Id | `1001` | +| params.uid | Optional | String | Your unique Id for the user or browser. Used for matching | `u-123xyz` | \ No newline at end of file diff --git a/modules/userId/eids.js b/modules/userId/eids.js index a7e5eaf6061..9b26eff2ebf 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -169,11 +169,15 @@ const USER_IDS_CONFIG = { source: 'neustar.biz', atype: 1 }, + // MediaWallah OpenLink + 'mwOpenLinkId': { + source: 'mediawallahscript.com', + atype: 1 + }, 'tapadId': { source: 'tapad.com', atype: 1 }, - // Novatiq Snowflake 'novatiq': { getValue: function(data) { diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 404066d53e4..bd14ea0b9e7 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -142,6 +142,13 @@ userIdAsEids = [ atype: 1 }] }, + { + source: 'mediawallahscript.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { source: 'tapad.com', uids: [{ diff --git a/modules/userId/userId.md b/modules/userId/userId.md index a7f98fb39a0..828b2993e40 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -77,6 +77,13 @@ pbjs.setConfig({ name: '_criteoId', expires: 1 } + }, { + name: 'mwOpenLinkId', + params: { + accountId: 0000, + partnerId: 0000, + uid: '12345xyz' + } }], syncDelay: 5000, auctionDelay: 1000 diff --git a/test/spec/modules/mwOpenLinkIdSystem_spec.js b/test/spec/modules/mwOpenLinkIdSystem_spec.js new file mode 100644 index 00000000000..fb082b8cd16 --- /dev/null +++ b/test/spec/modules/mwOpenLinkIdSystem_spec.js @@ -0,0 +1,20 @@ +import { writeCookie, mwOpenLinkIdSubModule } from 'modules/mwOpenLinkIdSystem.js'; + +const P_CONFIG_MOCK = { + params: { + accountId: '123', + partnerId: '123' + } +}; + +describe('mwOpenLinkId module', function () { + beforeEach(function() { + writeCookie(''); + }); + + it('getId() should return a MediaWallah openLink Id when the MediaWallah openLink first party cookie exists', function () { + writeCookie({eid: 'XX-YY-ZZ-123'}); + const id = mwOpenLinkIdSubModule.getId(P_CONFIG_MOCK); + expect(id).to.be.deep.equal({id: {eid: 'XX-YY-ZZ-123'}}); + }); +}); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 8a7be41b2b5..95279380232 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -38,6 +38,7 @@ import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {haloIdSubmodule} from 'modules/haloIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; +import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; import {getPrebidInternal} from 'src/utils.js'; @@ -458,7 +459,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -466,14 +467,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -484,7 +485,7 @@ describe('User ID', function () { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -501,15 +502,15 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 14 configurations should result in 14 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + it('config with 15 configurations should result in 15 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -549,17 +550,19 @@ describe('User ID', function () { name: 'zeotapIdPlus' }, { name: 'criteo' + }, { + name: 'mwOpenLinkId' }, { name: 'tapadId', storage: {name: 'tapad_id', type: 'cookie'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 14 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 15 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -574,7 +577,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -589,7 +592,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -1622,7 +1625,27 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId and Criteo have data to pass', function (done) { + it('test hook from mwOpenLinkId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([mwOpenLinkIdSubModule]); + init(config); + config.setConfig(getConfigMock(['mwOpenLinkId', 'mwol', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + }); + }); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId, Criteo and mwOpenLinkId have data to pass', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1637,8 +1660,9 @@ describe('User ID', function () { }), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (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())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1651,6 +1675,7 @@ describe('User ID', function () { ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'])); requestBidsHook(function () { @@ -1691,8 +1716,11 @@ describe('User ID', function () { // 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'); + // also check that mwOpenLink id was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); - expect(bid.userIdAsEids.length).to.equal(11); + expect(bid.userIdAsEids.length).to.equal(12); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1706,11 +1734,12 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId and haloId have their modules added before and after init', function (done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId, haloId and mwOpenLinkId have their modules added before and after init', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1725,6 +1754,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('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())); setSubmoduleRegistry([]); @@ -1744,6 +1774,7 @@ describe('User ID', function () { attachIdSystem(zeotapIdPlusSubmodule); attachIdSystem(haloIdSubmodule); attachIdSystem(criteoIdSubmodule); + attachIdSystem(mwOpenLinkIdSubModule); attachIdSystem(tapadIdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], @@ -1757,6 +1788,7 @@ describe('User ID', function () { ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'])); requestBidsHook(function () { @@ -1800,7 +1832,11 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); - expect(bid.userIdAsEids.length).to.equal(11); + // also check that mwOpenLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + + expect(bid.userIdAsEids.length).to.equal(12); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1814,6 +1850,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -2129,6 +2166,7 @@ describe('User ID', function () { // also check that haloId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.haloId'); expect(bid.userId.haloId).to.equal('testHaloId'); + expect(bid.userIdAsEids.length).to.equal(10); }); }); @@ -2143,6 +2181,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); From a1380fe5d93de9d7af4f92d040c54fa17d34719b Mon Sep 17 00:00:00 2001 From: Skylinar <53079123+Skylinar@users.noreply.github.com> Date: Thu, 18 Mar 2021 17:06:19 +0100 Subject: [PATCH 105/198] Documentation: Adjust desired bitrate examples smartx adapter (#6438) --- modules/smartxBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index b25ce68bb6e..223e51763b9 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -40,7 +40,7 @@ This adapter requires setup and approval from the smartclip team. skipOffset: 0, startOpen: true, endingScreen: true, - desiredBitrate: 1600, + desiredBitrate: 800, }, } }], @@ -75,7 +75,7 @@ This adapter requires setup and approval from the smartclip team. skipOffset: 0, startOpen: true, endingScreen: true, - desiredBitrate: 1600, + desiredBitrate: 800, }, user: { data: [{ From 3eb1216281f25485fc22a5dfa4d61c6a4dfe9e21 Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Thu, 18 Mar 2021 19:14:28 +0100 Subject: [PATCH 106/198] Remove adId (autogenerated by Prebid) (#6441) --- modules/improvedigitalBidAdapter.js | 3 +-- test/spec/modules/improvedigitalBidAdapter_spec.js | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 0432adf2b2d..dc0911ff5da 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -11,7 +11,7 @@ const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; export const spec = { - version: '7.2.0', + version: '7.3.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -126,7 +126,6 @@ export const spec = { } // Common properties - bid.adId = bidObject.id; bid.cpm = parseFloat(bidObject.price); bid.creativeId = bidObject.crid; bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 4d0da695d95..f34a75ef8f3 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -750,7 +750,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBid = [ { 'ad': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', @@ -767,7 +766,6 @@ describe('Improve Digital Adapter Tests', function () { expectedBid[0], { 'ad': '', - 'adId': '1234', 'creativeId': '422033', 'cpm': 1.23, 'currency': 'USD', @@ -783,7 +781,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidNative = [ { mediaType: 'native', - adId: '33e9500b21129f', creativeId: '422031', cpm: 1.45888594164456, currency: 'USD', @@ -832,7 +829,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidInstreamVideo = [ { 'vastXml': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', From 24c4fb8b4b502777f8fc3b3b5f96c0e68aecb8dd Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Fri, 19 Mar 2021 02:00:32 +0100 Subject: [PATCH 107/198] RichAudience Bid Adapter: add render video in banner (#6392) --- modules/richaudienceBidAdapter.js | 62 +++++++----- modules/richaudienceBidAdapter.md | 2 +- .../modules/richaudienceBidAdapter_spec.js | 99 ++++++++++++++++--- 3 files changed, 121 insertions(+), 42 deletions(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 07de3e40594..37a9554e9a4 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -14,20 +14,20 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO], /*** - * Determines whether or not the given bid request is valid - * - * @param {bidRequest} bid The bid params to validate. - * @returns {boolean} True if this is a valid bid, and false otherwise - */ + * Determines whether or not the given bid request is valid + * + * @param {bidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise + */ isBidRequestValid: function (bid) { return !!(bid.params && bid.params.pid && bid.params.supplyType); }, /*** - * Build a server request from the list of valid BidRequests - * @param {validBidRequests} is an array of the valid bids - * @param {bidderRequest} bidder request object - * @returns {ServerRequest} Info describing the request to the server - */ + * Build a server request from the list of valid BidRequests + * @param {validBidRequests} is an array of the valid bids + * @param {bidderRequest} bidder request object + * @returns {ServerRequest} Info describing the request to the server + */ buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bid => { var payload = { @@ -77,11 +77,11 @@ export const spec = { }); }, /*** - * Read the response from the server and build a list of bids - * @param {serverResponse} Response from the server. - * @param {bidRequest} Bid request object - * @returns {bidResponses} Array of bids which were nested inside the server - */ + * Read the response from the server and build a list of bids + * @param {serverResponse} Response from the server. + * @param {bidRequest} Bid request object + * @returns {bidResponses} Array of bids which were nested inside the server + */ interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; // try catch @@ -103,10 +103,16 @@ export const spec = { if (response.media_type === 'video') { bidResponse.vastXml = response.vastXML; try { - if (JSON.parse(bidRequest.data).videoData.format == 'outstream') { - bidResponse.renderer = Renderer.install({ - url: 'https://cdn3.richaudience.com/prebidVideo/player.js' - }); + if (bidResponse.vastXml != null) { + if (JSON.parse(bidRequest.data).videoData.format == 'outstream' || JSON.parse(bidRequest.data).videoData.format == 'banner') { + bidResponse.renderer = Renderer.install({ + id: bidRequest.bidId, + adunitcode: bidRequest.tagId, + loaded: false, + config: response.media_type, + url: 'https://cdn3.richaudience.com/prebidVideo/player.js' + }); + } bidResponse.renderer.setRender(renderer); } } catch (e) { @@ -121,13 +127,13 @@ export const spec = { return bidResponses }, /*** - * User Syncs - * - * @param {syncOptions} Publisher prebid configuration - * @param {serverResponses} Response from the server - * @param {gdprConsent} GPDR consent object - * @returns {Array} - */ + * User Syncs + * + * @param {syncOptions} Publisher prebid configuration + * @param {serverResponses} Response from the server + * @param {gdprConsent} GPDR consent object + * @returns {Array} + */ getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { const syncs = []; @@ -197,6 +203,10 @@ function raiGetVideoInfo(bid) { playerSize: bid.mediaTypes.video.playerSize, mimes: bid.mediaTypes.video.mimes }; + } else { + videoData = { + format: 'banner' + } } return videoData; } diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index fbf59a0208a..f888117b166 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -39,7 +39,7 @@ Please reach out to your account manager for more information. "pid":"ADb1f40rmo", "supplyType":"site", "bidfloor":0.40, - "keywords": "bici=scott;coche=audi;coche=mercedes;" + "keywords": "key1=value1;key2=value2;key3=value3;" } }] } diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 3b9f07b6efc..5deb2463523 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -20,7 +20,7 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.5, pid: 'ADb1f40rmi', supplyType: 'site', - keywords: 'coche=mercedes;coche=audi' + keywords: 'key1=value1;key2=value2' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -75,26 +75,19 @@ describe('Richaudience adapter tests', function () { user: {} }]; - var DEFAULT_PARAMS_VIDEO_OUT_PARAMS = [{ + var DEFAULT_PARAMS_BANNER_OUTSTREAM = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'] + banner: { + sizes: [[300, 250], [600, 300]] } }, bidder: 'richaudience', params: { bidfloor: 0.5, pid: 'ADb1f40rmi', - supplyType: 'site', - player: { - init: 'close', - end: 'close', - skin: 'dark' - } + supplyType: 'site' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -242,7 +235,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('numIframes').and.to.equal(0); expect(typeof requestContent.scr_rsl === 'string') expect(typeof requestContent.cpuc === 'number') - expect(requestContent).to.have.property('kws').and.to.equal('coche=mercedes;coche=audi'); + expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) it('Verify build request to prebid video inestream', function() { @@ -262,8 +255,6 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('640'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('480'); }) it('Verify build request to prebid video outstream', function() { @@ -281,6 +272,7 @@ describe('Richaudience adapter tests', function () { expect(request[0]).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); }) @@ -623,9 +615,19 @@ describe('Richaudience adapter tests', function () { }); const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); expect(bid.mediaType).to.equal('video'); expect(bid.vastXml).to.equal(''); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); }); it('no banner media response outstream', function () { @@ -640,6 +642,35 @@ describe('Richaudience adapter tests', function () { } }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('banner media and response VAST', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_BANNER_OUTSTREAM, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); const bid = bids[0]; expect(bid.mediaType).to.equal('video'); @@ -656,6 +687,16 @@ describe('Richaudience adapter tests', function () { expect(spec.aliases[0]).to.equal('ra'); }); + it('Verifies bidder gvlid', function () { + expect(spec.gvlid).to.equal(108); + }); + + it('Verifies bidder supportedMediaTypes', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('video'); + }); + it('Verifies if bid request is valid', function () { expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); @@ -717,6 +758,34 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.50, } })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + bidfloor: 0.50, + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); }); it('Verifies user syncs iframe', function () { From cd6f1a87b63ba4d0fd4ae42e6a4d3217a2da9d31 Mon Sep 17 00:00:00 2001 From: SKOCHERI <37454420+SKOCHERI@users.noreply.github.com> Date: Fri, 19 Mar 2021 02:58:20 -0700 Subject: [PATCH 108/198] Integration Example: ID import library example (#6434) * ID Import Library example * Fixing review comments Co-authored-by: skocheri --- .../gpt/idImportLibrary_example.html | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 integrationExamples/gpt/idImportLibrary_example.html diff --git a/integrationExamples/gpt/idImportLibrary_example.html b/integrationExamples/gpt/idImportLibrary_example.html new file mode 100644 index 00000000000..da1581d1c89 --- /dev/null +++ b/integrationExamples/gpt/idImportLibrary_example.html @@ -0,0 +1,351 @@ + + + + + + + + + + + + + +Prebid + +

ID Import Library Example

+

Steps before logging in:

+ +
    +
  • Open console +
      +
    • For Mac, Command+Option+J
    • +
    • Windows/Linux, Control+Shift+J
    • +
    +
  • +
  • Search for 'ID-Library' in console
  • +
+ + + + + + + + + From 92c9f67c0bcad765d3058b00453bea417f8ee119 Mon Sep 17 00:00:00 2001 From: Catalin Ciocov Date: Fri, 19 Mar 2021 13:35:08 +0200 Subject: [PATCH 109/198] Mass Module: add module to support MASS protocol (#6332) * Initial implementation for the MASS module * Updated namespace and CDN location * Updated the data object passed to MASS bootloader * Fix linting issues * Added unit tests * Added a README for the MASS module. * Allow MASS be disabled via Prebid configuration * Only check deal ID for matching MASS bids * Updated docs * Update how we test for MASS bids * Thighten the test for MASS bids * Fix linting issues * Change deal ID prefix and add option to specify the bootloader location. Updates to docs. * Updated tests with the new META_MASS deal ID prefix * Update comment in modules/mass.js Co-authored-by: Scott Menzer * Additional information about the module * More specific description of external resource * Identify MASS bids by looking for a 'mass' flag in bid meta or testing deal IDs against a publisher defined pattern * Updated MASS module tests * Bug fixing, added integration example and increased test coverage * Fix integration example and add notice * Updated example page * Updated bootloaderUrl param name to renderUrl and removed its default value. Must be specfied in module config now. * Updated integration example for MASS * Update mass.md Updated disclaimer and synced with docs Co-authored-by: Scott Menzer Co-authored-by: massadmin <58946787+massadmin@users.noreply.github.com> --- integrationExamples/mass/index.html | 110 +++++++++++++++++++++++ modules/mass.js | 129 +++++++++++++++++++++++++++ modules/mass.md | 63 ++++++++++++++ test/spec/modules/mass_spec.js | 130 ++++++++++++++++++++++++++++ 4 files changed, 432 insertions(+) create mode 100644 integrationExamples/mass/index.html create mode 100644 modules/mass.js create mode 100644 modules/mass.md create mode 100644 test/spec/modules/mass_spec.js diff --git a/integrationExamples/mass/index.html b/integrationExamples/mass/index.html new file mode 100644 index 00000000000..80fe4cfb934 --- /dev/null +++ b/integrationExamples/mass/index.html @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + +
+

Note: for this example to work, you need access to a bid simulation tool from your MASS enabled Exchange partner.

+
+ +
+
+ + diff --git a/modules/mass.js b/modules/mass.js new file mode 100644 index 00000000000..14fe556a466 --- /dev/null +++ b/modules/mass.js @@ -0,0 +1,129 @@ +/** + * This module adds MASS support to Prebid.js. + */ + +import { config } from '../src/config.js'; +import { getHook } from '../src/hook.js'; +import find from 'core-js-pure/features/array/find.js'; + +export let listenerAdded = false; +export let massEnabled = false; + +const defaultCfg = { + dealIdPattern: /^MASS/i +}; +let cfg; + +const massBids = {}; + +init(); +config.getConfig('mass', config => init(config.mass)); + +/** + * Module init. + */ +export function init(customCfg) { + cfg = Object.assign({}, defaultCfg, customCfg); + + if (cfg.enabled === false) { + if (massEnabled) { + massEnabled = false; + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + } + } else { + if (!massEnabled) { + getHook('addBidResponse').before(addBidResponseHook); + massEnabled = true; + } + } +} + +/** + * Before hook for 'addBidResponse'. + */ +export function addBidResponseHook(next, adUnitCode, bid) { + if (!isMassBid(bid) || !cfg.renderUrl) { + return next(adUnitCode, bid); + } + + const bidRequest = find(this.bidderRequest.bids, bidRequest => + bidRequest.bidId === bid.requestId + ); + + massBids[bid.requestId] = { + bidRequest, + bid, + adm: bid.ad + }; + + bid.ad = ' + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + + diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 7ddad134451..f3514f66b9b 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -14,11 +14,14 @@ const CENT_TO_DOLLAR_FACTOR = 100; const BANNER_TIME_TO_LIVE = 300; const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NET_REVENUE = true; + const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; +const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; + /** * Transform valid bid request config object to banner impression object that will be sent to ad server. * @@ -33,6 +36,8 @@ function bidToBannerImp(bid) { imp.banner.h = bid.params.size[1]; imp.banner.topframe = utils.inIframe() ? 0 : 1; + _applyFloor(bid, imp, BANNER); + return imp; } @@ -46,7 +51,7 @@ function bidToVideoImp(bid) { const imp = bidToImp(bid); const videoAdUnitRef = utils.deepAccess(bid, 'mediaTypes.video'); const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - const videoAdUnitWhitelist = [ + const videoAdUnitAllowlist = [ 'mimes', 'minduration', 'maxduration', 'protocols', 'protocol', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', @@ -68,12 +73,14 @@ function bidToVideoImp(bid) { } } - for (let adUnitProperty in videoAdUnitRef) { - if (videoAdUnitWhitelist.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { + for (const adUnitProperty in videoAdUnitRef) { + if (videoAdUnitAllowlist.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { imp.video[adUnitProperty] = videoAdUnitRef[adUnitProperty]; } } + _applyFloor(bid, imp, VIDEO); + return imp; } @@ -92,12 +99,73 @@ function bidToImp(bid) { imp.ext.sid = `${bid.params.size[0]}x${bid.params.size[1]}`; } - if (bid.params.hasOwnProperty('bidFloor') && bid.params.hasOwnProperty('bidFloorCur')) { - imp.bidfloor = bid.params.bidFloor; - imp.bidfloorcur = bid.params.bidFloorCur; + return imp; +} + +/** + * Gets priceFloors floors and IX adapter floors, + * Validates and sets the higher one on the impression + * @param {object} bid bid object + * @param {object} imp impression object + * @param {string} mediaType the impression ad type, one of the SUPPORTED_AD_TYPES + */ +function _applyFloor(bid, imp, mediaType) { + let adapterFloor = null; + let moduleFloor = null; + + if (bid.params.bidFloor && bid.params.bidFloorCur) { + adapterFloor = { floor: bid.params.bidFloor, currency: bid.params.bidFloorCur }; } - return imp; + if (utils.isFn(bid.getFloor)) { + let _mediaType = '*'; + let _size = '*'; + + if (mediaType && utils.contains(SUPPORTED_AD_TYPES, mediaType)) { + const { w: width, h: height } = imp[mediaType]; + _mediaType = mediaType; + _size = [width, height]; + } + try { + moduleFloor = bid.getFloor({ + mediaType: _mediaType, + size: _size + }); + } catch (err) { + // continue with no module floors + utils.logWarn('priceFloors module call getFloor failed, error : ', err); + } + } + + if (adapterFloor && moduleFloor) { + if (adapterFloor.currency !== moduleFloor.currency) { + utils.logWarn('The bid floor currency mismatch between IX params and priceFloors module config'); + return; + } + + if (adapterFloor.floor > moduleFloor.floor) { + imp.bidfloor = adapterFloor.floor; + imp.bidfloorcur = adapterFloor.currency; + imp.ext.fl = FLOOR_SOURCE.IX; + } else { + imp.bidfloor = moduleFloor.floor; + imp.bidfloorcur = moduleFloor.currency; + imp.ext.fl = FLOOR_SOURCE.PBJS; + } + return; + } + + if (moduleFloor) { + imp.bidfloor = moduleFloor.floor; + imp.bidfloorcur = moduleFloor.currency; + imp.ext.fl = FLOOR_SOURCE.PBJS; + } else if (adapterFloor) { + imp.bidfloor = adapterFloor.floor; + imp.bidfloorcur = adapterFloor.currency; + imp.ext.fl = FLOOR_SOURCE.IX; + } else { + utils.logInfo('IX Bid Adapter: No floors available, no floors applied'); + } } /** @@ -270,7 +338,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { 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]) { + Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { userEids.push(response.data); } } @@ -498,7 +566,7 @@ function buildIXDiag(validBidRequests) { iu: 0, nu: 0, ou: 0, - allU: 0, + allu: 0, ren: false, version: '$prebid.version$' }; @@ -534,7 +602,7 @@ function buildIXDiag(validBidRequests) { ixdiag.iu++; } - ixdiag.allU++; + ixdiag.allu++; } } @@ -610,16 +678,19 @@ function updateMissingSizes(validBidRequest, missingBannerSizes, imp) { } /** - * + * @param {object} bid ValidBidRequest object, used to adjust floor * @param {object} imp Impression object to be modified * @param {array} newSize The new size to be applied * @return {object} newImp Updated impression object */ -function createMissingBannerImp(imp, newSize) { +function createMissingBannerImp(bid, imp, newSize) { const newImp = utils.deepClone(imp); newImp.ext.sid = `${newSize[0]}x${newSize[1]}`; newImp.banner.w = newSize[0]; newImp.banner.h = newSize[1]; + + _applyFloor(bid, newImp, BANNER); + return newImp; } @@ -658,7 +729,7 @@ export const spec = { } if (!includesSize(bid.sizes, paramsSize) && !((mediaTypeVideoPlayerSize && includesSize(mediaTypeVideoPlayerSize, paramsSize)) || - (mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) { + (mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) { utils.logError('ix bidder params: bid size is not included in ad unit sizes or player size.'); return false; } @@ -730,13 +801,12 @@ export const spec = { if (!videoImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { videoImps[validBidRequest.transactionId].ixImps = []; } - videoImps[validBidRequest.transactionId].ixImps.push(bidToVideoImp(validBidRequest)); } } if (validBidRequest.mediaType === BANNER || - (utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) || - (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { + (utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) || + (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { let imp = bidToBannerImp(validBidRequest); if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { @@ -767,7 +837,7 @@ export const spec = { let origImp = missingBannerSizes[transactionId].impression; for (let i = 0; i < missingSizes.length; i++) { - let newImp = createMissingBannerImp(origImp, missingSizes[i]); + let newImp = createMissingBannerImp(validBidRequest, origImp, missingSizes[i]); bannerImps[transactionId].missingImps.push(newImp); bannerImps[transactionId].missingCount++; } diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 6d7b3a397bd..6b7f35e7fd4 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -26,7 +26,7 @@ describe('IndexexchangeAdapter', function () { } ] }; - var div_many_sizes = [ + const div_many_sizes = [ [300, 250], [600, 410], [336, 280], @@ -94,6 +94,62 @@ describe('IndexexchangeAdapter', function () { [600, 40], [600, 30] ]; + + const ONE_VIDEO = [ + { + bidder: 'ix', + params: { + siteId: '456', + video: { + skippable: false, + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [2] + }, + size: [400, 100] + }, + sizes: [[400, 100]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [[400, 100]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + + const ONE_BANNER = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250] + }, + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', + bidId: '1a2b3c4d', + bidderRequestId: '11a22b33c44d', + auctionId: '1aa2bb3cc4dd', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_BANNER_VALID_BID = [ { bidder: 'ix', @@ -1057,6 +1113,84 @@ describe('IndexexchangeAdapter', function () { expect(impression.ext.sid).to.equal(sidValue); }); + it('video impression has #priceFloors floors', function () { + const bid = utils.deepClone(ONE_VIDEO[0]); + const flr = 5.5 + const floorInfo = {floor: flr, currency: 'USD'}; + bid.getFloor = function () { + return floorInfo; + } + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(flr); + expect(imp1.bidfloorcur).to.equal('USD'); + expect(imp1.ext.fl).to.equal('p'); + }); + + it('banner imp has floors from #priceFloors module', function () { + const floor300x250 = 3.25 + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]) + + const floorInfo = { floor: floor300x250, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + + expect(imp1.bidfloorcur).to.equal('USD'); + expect(imp1.bidfloor).to.equal(floor300x250); + expect(imp1.ext.fl).to.equal('p'); + }); + + it('ix adapter floors chosen over #priceFloors ', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const floorhi = 4.5 + const floorlow = 3.5 + + bid.params.bidFloor = floorhi + bid.params.bidFloorCur = 'USD' + + const floorInfo = { floor: floorlow, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(floorhi); + expect(imp1.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(imp1.ext.fl).to.equal('x'); + }); + + it(' #priceFloors floors chosen over ix adapter floors', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const floorhi = 4.5 + const floorlow = 3.5 + + bid.params.bidFloor = floorlow + bid.params.bidFloorCur = 'USD' + + const floorInfo = { floor: floorhi, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(floorhi); + expect(imp1.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(imp1.ext.fl).to.equal('p'); + }); + it('impression should have bidFloor and bidFloorCur if configured', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; @@ -1066,6 +1200,73 @@ describe('IndexexchangeAdapter', function () { expect(impression.bidfloor).to.equal(bid.params.bidFloor); expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(impression.ext.fl).to.equal('x'); + }); + + it('missing sizes #priceFloors ', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + bid.mediaTypes.banner.sizes.push([500, 400]) + + const floorInfo = { floor: 3.25, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + sinon.spy(bid, 'getFloor'); + + const requestBidFloor = spec.buildRequests([bid])[0]; + // called getFloor with 300 x 250 + expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); + expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); + + // called getFloor with 500 x 400 + expect(bid.getFloor.getCall(1).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(1).args[0].size[0]).to.equal(500); + expect(bid.getFloor.getCall(1).args[0].size[1]).to.equal(400); + + // both will have same floors due to mock getFloor + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(3.25); + expect(imp1.bidfloorcur).to.equal('USD'); + + const imp2 = JSON.parse(requestBidFloor.data.r).imp[1]; + expect(imp2.bidfloor).to.equal(3.25); + expect(imp2.bidfloorcur).to.equal('USD'); + }); + + it('#pricefloors inAdUnit, banner impressions should have floors', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const flr = 4.3 + bid.floors = { + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType', 'size'] + }, + values: { + 'banner|300x250': flr, + 'banner|600x500': 6.5, + 'banner|*': 7.5 + } + }; + const floorInfo = { floor: flr, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + sinon.spy(bid, 'getFloor'); + + const requestBidFloor = spec.buildRequests([bid])[0]; + // called getFloor with 300 x 250 + expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); + expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); + + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(flr); + expect(imp1.bidfloorcur).to.equal('USD'); }); it('payload without mediaType should have correct format and value', function () { @@ -1145,7 +1346,6 @@ describe('IndexexchangeAdapter', function () { const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; const expectedPageUrl = DEFAULT_OPTION.refererInfo.referer + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; - expect(pageUrl).to.equal(expectedPageUrl); }); @@ -1492,7 +1692,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.video.mimes[0]).to.not.equal('video/override'); }); - it('should not add video adunit level properties in imp object if they are not whitelisted', function () { + it('should not add video adunit level properties in imp object if they are not allowlisted', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; bid.mediaTypes.video.random = true; @@ -1502,7 +1702,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.video.random).to.not.exist; }); - it('should add whitelisted adunit level video properties in imp object if they are not configured at params level', function () { + it('should add allowlisted adunit level video properties in imp object if they are not configured at params level', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; delete bid.params.video.protocols; @@ -1571,7 +1771,7 @@ describe('IndexexchangeAdapter', function () { expect(diagObj.ou).to.equal(1); expect(diagObj.ren).to.equal(false); expect(diagObj.mfu).to.equal(1); - expect(diagObj.allU).to.equal(1); + expect(diagObj.allu).to.equal(1); expect(diagObj.version).to.equal('$prebid.version$'); }); }); From f3fe966c84bd52764dae0e01c3d62c6aa00a77de Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 24 Mar 2021 11:21:06 -0400 Subject: [PATCH 121/198] PBS adapter: remove condition on gdpr consentstring which isnt required by the tcf2 spec when gdprapplies is false (#6429) * Update index.js * Update prebidServerBidAdapter_spec.js * Update prebidServerBidAdapter_spec.js * Update prebidServerBidAdapter_spec.js --- modules/prebidServerBidAdapter/index.js | 7 ++----- test/spec/modules/prebidServerBidAdapter_spec.js | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index d3bbb347720..453f9118766 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -188,17 +188,14 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { } if (gdprConsent) { - // only populate gdpr field if we know CMP returned consent information (ie didn't timeout or have an error) - if (typeof gdprConsent.consentString !== 'undefined') { - payload.gdpr = (gdprConsent.gdprApplies) ? 1 : 0; - } + payload.gdpr = (gdprConsent.gdprApplies) ? 1 : 0; // attempt to populate gdpr_consent if we know gdprApplies or it may apply if (gdprConsent.gdprApplies !== false) { payload.gdpr_consent = gdprConsent.consentString; } } - // US Privace (CCPA) support + // US Privacy (CCPA) support if (uspConsent) { payload.us_privacy = uspConsent; } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 45d96b96d66..809e3933eb9 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -639,7 +639,7 @@ describe('S2S Adapter', function () { expect(requestBid.gdpr_consent).is.undefined; }); - it('checks gdpr info gets added to cookie_sync request: consent data unknown', function () { + it('checks gdpr info gets added to cookie_sync request: applies is false', function () { let cookieSyncConfig = utils.deepClone(CONFIG); cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; @@ -649,7 +649,7 @@ describe('S2S Adapter', function () { let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = { consentString: undefined, - gdprApplies: undefined + gdprApplies: false }; const s2sBidRequest = utils.deepClone(REQUEST); @@ -658,7 +658,7 @@ describe('S2S Adapter', function () { adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.gdpr).is.undefined; + expect(requestBid.gdpr).is.equal(0); expect(requestBid.gdpr_consent).is.undefined; }); }); From 9faeb687b1dd1088f95defb98e58df100d064f7b Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Wed, 24 Mar 2021 16:27:17 +0100 Subject: [PATCH 122/198] if the stored value was an object, then it could end up with a space before the {, causing the stored value not to be json parsed simply because of a space in the first character (#6467) --- modules/userId/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 3253be42a76..be9883dae9c 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -247,7 +247,7 @@ function getStoredValue(storage, key = undefined) { } } // support storing a string or a stringified object - if (typeof storedValue === 'string' && storedValue.charAt(0) === '{') { + if (typeof storedValue === 'string' && storedValue.trim().charAt(0) === '{') { storedValue = JSON.parse(storedValue); } } catch (e) { From fa5c978fbe496717cbedd9582bfe6671e1f39f9d Mon Sep 17 00:00:00 2001 From: Bernhard Valenti Date: Wed, 24 Mar 2021 12:29:55 -0400 Subject: [PATCH 123/198] Kargo Bid Adapter: add gdpr support (#6447) --- modules/kargoBidAdapter.js | 38 ++++++++++--- test/spec/modules/kargoBidAdapter_spec.js | 65 +++++++++++++++++++---- 2 files changed, 86 insertions(+), 17 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 03767efc135..610f4558139 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -3,17 +3,19 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); const BIDDER_CODE = 'kargo'; const HOST = 'https://krk.kargo.com'; -const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}'; +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 SYNC_COUNT = 5; +const GVLID = 972; +const storage = getStorageManager(GVLID, BIDDER_CODE); let sessionId, lastPageUrl, requestCounter; export const spec = { + gvlid: GVLID, code: BIDDER_CODE, isBidRequestValid: function(bid) { if (!bid || !bid.params) { @@ -48,7 +50,7 @@ export const spec = { bidIDs: bidIds, bidSizes: bidSizes, prebidRawBidRequests: validBidRequests - }, spec._getAllMetadata(tdid, bidderRequest.uspConsent)); + }, spec._getAllMetadata(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent)); const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); return Object.assign({}, bidderRequest, { method: 'GET', @@ -85,15 +87,25 @@ export const spec = { } return bidResponses; }, - getUserSyncs: function(syncOptions) { + 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 (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) + url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed) + .replace('{INDEX}', i) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') }); } } @@ -183,7 +195,7 @@ export const spec = { } }, - _getUserIds(tdid, usp) { + _getUserIds(tdid, usp, gdpr) { const crb = spec._getCrb(); const userIds = { kargoID: crb.userId, @@ -192,6 +204,16 @@ export const spec = { optOut: crb.optOut, usp: usp }; + + try { + if (gdpr) { + userIds['gdpr'] = { + consent: gdpr.consentString || '', + applies: !!gdpr.gdprApplies, + } + } + } catch (e) { + } if (tdid) { userIds.tdID = tdid; } @@ -203,9 +225,9 @@ export const spec = { return crb.clientId; }, - _getAllMetadata(tdid, usp) { + _getAllMetadata(tdid, usp, gdpr) { return { - userIDs: spec._getUserIds(tdid, usp), + userIDs: spec._getUserIds(tdid, usp, gdpr), krux: spec._getKrux(), pageURL: window.location.href, rawCRB: spec._readCookie('krg_crb'), diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 9dbcca8e331..43968bbef5a 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -134,6 +134,21 @@ describe('kargo adapter tests', function () { noAdServerCurrency = true; } + function generateGDPR(applies, haveConsent) { + var data = { + consentString: 'gdprconsentstring', + gdprApplies: applies, + }; + return data; + } + + function generateGDPRExpect(applies, haveConsent) { + return { + consent: 'gdprconsentstring', + applies: applies, + }; + } + function initializeKruxUser() { setLocalStorageItem('kxkar_user', 'rsgr9pnij'); } @@ -221,7 +236,7 @@ describe('kargo adapter tests', function () { return spec._getSessionId(); } - function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie) { + function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { var base = { timeout: 200, requestCount: requestCount++, @@ -299,6 +314,10 @@ describe('kargo adapter tests', function () { rawCRBLocalStorage: expectedRawCRB }; + if (expectedGDPR) { + base.userIDs['gdpr'] = expectedGDPR; + } + if (excludeUserIds === true) { base.userIDs = { crbIDs: {}, @@ -317,12 +336,16 @@ describe('kargo adapter tests', function () { return base; } - function testBuildRequests(excludeTdid, expected) { + function testBuildRequests(excludeTdid, expected, gdpr) { var clonedBids = JSON.parse(JSON.stringify(bids)); if (excludeTdid) { delete clonedBids[0].userId.tdid; } - var request = spec.buildRequests(clonedBids, {timeout: 200, uspConsent: '1---', foo: 'bar'}); + var payload = { timeout: 200, uspConsent: '1---', foo: 'bar' }; + if (gdpr) { + payload['gdprConsent'] = gdpr + } + var request = spec.buildRequests(clonedBids, payload); expected.sessionId = getSessionId(); sessionIds.push(expected.sessionId); var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); @@ -431,6 +454,15 @@ describe('kargo adapter tests', function () { initializeKrgCrb(); testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); }); + + it('sends gdpr consent', function () { + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgCrb(); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); + }); }); describe('response handler', function() { @@ -558,8 +590,8 @@ describe('kargo adapter tests', function () { }); }); - function getUserSyncsWhenAllowed() { - return spec.getUserSyncs({iframeEnabled: true}); + function getUserSyncsWhenAllowed(gdprConsent, usPrivacy) { + return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy); } function getUserSyncsWhenForbidden() { @@ -574,17 +606,17 @@ describe('kargo adapter tests', function () { shouldSimulateOutdatedBrowser = true; } - function getSyncUrl(index) { + function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy) { return { type: 'iframe', - url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}` + 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}` }; } - function getSyncUrls() { + function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy) { var syncs = []; for (var i = 0; i < 5; i++) { - syncs[i] = getSyncUrl(i); + syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || ''); } return syncs; } @@ -606,6 +638,21 @@ describe('kargo adapter tests', function () { safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); }); + it('no user syncs when there is no us privacy consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YYY')).to.be.an('array').that.is.empty); + }); + + it('pass through us privacy consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YNY')).to.deep.equal(getSyncUrls(0, '', '1YNY'))); + }); + + it('pass through gdpr consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed({ gdprApplies: true, consentString: 'consentstring' })).to.deep.equal(getSyncUrls(1, 'consentstring', ''))); + }); + it('no user syncs when there is outdated browser', function() { turnOnClientId(); simulateOutdatedBrowser(); From 1dc7c05497514410f7abed5636f0d3bf43cffa5c Mon Sep 17 00:00:00 2001 From: Daniel Liebner Date: Wed, 24 Mar 2021 12:35:53 -0400 Subject: [PATCH 124/198] Bid Glass Adapter: Merge externally set targeting params (#6426) * Added bidglass adapter + test * PR Review Updates: - Added formal params to getUserSyncs function definition - getUserSyncs now always returns an array - Improved unit test coverage * PR Review Updates: - Removed unused methods: getUserSyncs, onTimeout, onBidWon, onSetTargeting - Removed getUserSyncs unit test - Removed "dead code" - Removed some unnecessary comments - Fixed usage of parseInt * Bid Glass Bid Adapter: pass options in bid request * Merge externally set targeting params * Updates to address gulp errors * Get `bidglass` reference from window --- modules/bidglassBidAdapter.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index 3162228ce58..44f5cdf4384 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -67,18 +67,29 @@ export const spec = { return ori; }; + let bidglass = window['bidglass']; + utils._each(validBidRequests, function(bid) { bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); bid.sizes = bid.sizes.filter(size => utils.isArray(size)); + var adUnitId = utils.getBidIdParameter('adUnitId', bid.params); var options = utils.deepClone(bid.params); + delete options.adUnitId; + // Merge externally set targeting params + if (typeof bidglass === 'object' && bidglass.getTargeting) { + let targeting = bidglass.getTargeting(adUnitId, options.targeting); + + if (targeting && Object.keys(targeting).length > 0) options.targeting = targeting; + } + // Stuff to send: [bid id, sizes, adUnitId, options] imps.push({ bidId: bid.bidId, sizes: bid.sizes, - adUnitId: utils.getBidIdParameter('adUnitId', bid.params), + adUnitId: adUnitId, options: options }); }); From f03e95a736a7289d8d11d79d9d9c91212136ec49 Mon Sep 17 00:00:00 2001 From: Amit Aisikowitz <7425067+pixelgroup-israel@users.noreply.github.com> Date: Wed, 24 Mar 2021 19:21:23 +0200 Subject: [PATCH 125/198] Optimon Analytics Adapter: add new analytics adapter (#6333) * Publish the Optimon platform's analytics adapter for prebid Added js and md files for the analytics adapter. * Fix wrong content in MD file * Fix wrong content in MD file * Created unit testing to Optimon Analytics Adapter * Created unit testing to Optimon Analytics Adapter * Created unit testing to Optimon Analytics Adapter * Created unit testing to Optimon Analytics Adapter * Fixes ESlint styling * Removing DEF const * Created unit testing to Optimon Analytics Adapter * Created unit testing to Optimon Analytics Adapter --- modules/optimonAnalyticsAdapter.js | 25 ++++++++++++ modules/optimonAnalyticsAdapter.md | 13 ++++++ .../modules/optimonAnalyticsAdapter_spec.js | 40 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 modules/optimonAnalyticsAdapter.js create mode 100644 modules/optimonAnalyticsAdapter.md create mode 100644 test/spec/modules/optimonAnalyticsAdapter_spec.js diff --git a/modules/optimonAnalyticsAdapter.js b/modules/optimonAnalyticsAdapter.js new file mode 100644 index 00000000000..34b2778afc9 --- /dev/null +++ b/modules/optimonAnalyticsAdapter.js @@ -0,0 +1,25 @@ +/** +* +********************************************************* +* +* Optimon.io Prebid Analytics Adapter +* +********************************************************* +* +*/ + +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; + +const optimonAnalyticsAdapter = adapter({ + global: 'OptimonAnalyticsAdapter', + handler: 'on', + analyticsType: 'bundle' +}); + +adapterManager.registerAnalyticsAdapter({ + adapter: optimonAnalyticsAdapter, + code: 'optimon', +}); + +export default optimonAnalyticsAdapter; diff --git a/modules/optimonAnalyticsAdapter.md b/modules/optimonAnalyticsAdapter.md new file mode 100644 index 00000000000..4e2c00dfcab --- /dev/null +++ b/modules/optimonAnalyticsAdapter.md @@ -0,0 +1,13 @@ +# Overview + +Module Name: Optimon.io Prebid Analytics Adapter +Module Type: Analytics Adapter +Maintainer: hello@optimon.io + +# Description + +Start analyzing your Prebid performance by visiting our website [Optimon.io](https://optimon.io/?utm_source=prebid-org&utm_medium=analytics-adapter) or contact us directly by email: [hello@optimon.io](mailto:hello@optimon.io) to get started. + +# Platform Details + +[Optimon.io](https://optimon.io/?utm_source=prebid-org&utm_medium=analytics-adapter) is a Robust Alerting & Reporting Platform for Prebid and GAM that helps publishers make the right decisions by collecting data from your Google Ad Manager, Prebid, and other SSPs and providing smart insights and suggestions to optimize and maximize their overall yield and save manual work. diff --git a/test/spec/modules/optimonAnalyticsAdapter_spec.js b/test/spec/modules/optimonAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b5b76ce3fde --- /dev/null +++ b/test/spec/modules/optimonAnalyticsAdapter_spec.js @@ -0,0 +1,40 @@ +import * as utils from 'src/utils.js'; +import { expect } from 'chai'; +import optimonAnalyticsAdapter from '../../../modules/optimonAnalyticsAdapter.js'; +import adapterManager from 'src/adapterManager'; +import events from 'src/events'; +import constants from 'src/constants.json' + +const AD_UNIT_CODE = 'demo-adunit-1'; +const PUBLISHER_CONFIG = { + pubId: 'optimon_test', + pubAdxAccount: 123456789, + pubTimezone: 'Asia/Jerusalem' +}; + +describe('Optimon Analytics Adapter', () => { + const optmn_currentWindow = utils.getWindowSelf(); + let optmn_queue = []; + + beforeEach(() => { + optmn_currentWindow.OptimonAnalyticsAdapter = (...optmn_args) => optmn_queue.push(optmn_args); + adapterManager.enableAnalytics({ + provider: 'optimon' + }); + optmn_queue = [] + }); + + afterEach(() => { + optimonAnalyticsAdapter.disableAnalytics(); + }); + + it('should forward all events to the queue', () => { + const optmn_arguments = [AD_UNIT_CODE, PUBLISHER_CONFIG]; + + events.emit(constants.EVENTS.AUCTION_END, optmn_arguments) + events.emit(constants.EVENTS.BID_TIMEOUT, optmn_arguments) + events.emit(constants.EVENTS.BID_WON, optmn_arguments) + + expect(optmn_queue.length).to.eql(3); + }); +}); From 95c56254811d54edb4e52a0d130b6ae4f1190534 Mon Sep 17 00:00:00 2001 From: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Date: Wed, 24 Mar 2021 16:38:29 -0400 Subject: [PATCH 126/198] 4.32.0 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed13b6ae7da..82aa5bf6e0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.32.0-pre", + "version": "4.32.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 48cd16a9bed3a3872a7f481255d2083e4bc696b9 Mon Sep 17 00:00:00 2001 From: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Date: Wed, 24 Mar 2021 17:30:05 -0400 Subject: [PATCH 127/198] 4.33.0-pre --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82aa5bf6e0b..aca216767cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.32.0", + "version": "4.33.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 94faa28f2160d2fa8e8c1f3771e858c3d5c54240 Mon Sep 17 00:00:00 2001 From: SKOCHERI <37454420+SKOCHERI@users.noreply.github.com> Date: Thu, 25 Mar 2021 01:11:21 -0700 Subject: [PATCH 128/198] UID 2.0 Userid submodule (#6443) * UID 2.0 User id submodule * UID 2.0 User id submodule * UID 2.0 User id submodule * UID 2.0 User id submodule * Resolving merge conflicts and review comments * Updating documentation * Renaming module * Fixing review comments * Fixing review comments * Fixing review comments * Fixing review comments * Fixing review comments * Updating source uid2.com to uidapi.com Co-authored-by: skocheri --- integrationExamples/gpt/userId_example.html | 3 + modules/uid2IdSystem.js | 97 +++++++++++++++++++++ modules/uid2IdSystem.md | 24 +++++ modules/userId/eids.js | 7 ++ modules/userId/eids.md | 9 +- modules/userId/userId.md | 5 +- test/spec/modules/eids_spec.js | 14 +++ test/spec/modules/userId_spec.js | 93 +++++++++++++++----- 8 files changed, 228 insertions(+), 24 deletions(-) create mode 100644 modules/uid2IdSystem.js create mode 100644 modules/uid2IdSystem.md diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 71299a4a6e1..fae5ca2b539 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -277,6 +277,9 @@ { name: "criteo" }, + { + name: "uid2" + } ], syncDelay: 5000, auctionDelay: 1000 diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js new file mode 100644 index 00000000000..053b57cb76d --- /dev/null +++ b/modules/uid2IdSystem.js @@ -0,0 +1,97 @@ +/** + * This module adds uid2 ID support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/uid2IdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import {submodule} from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'uid2'; +const GVLID = 887; +const LOG_PRE_FIX = 'UID2: '; +const ADVERTISING_COOKIE = '__uid2_advertising_token'; + +function readCookie() { + return storage.cookiesAreEnabled() ? storage.getCookie(ADVERTISING_COOKIE) : null; +} + +function readFromLocalStorage() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(ADVERTISING_COOKIE) : null; +} + +function getStorage() { + return getStorageManager(GVLID, MODULE_NAME); +} + +const storage = getStorage(); + +const logInfo = createLogInfo(LOG_PRE_FIX); + +function createLogInfo(prefix) { + return function (...strings) { + utils.logInfo(prefix + ' ', ...strings); + } +} + +/** + * Encode the id + * @param value + * @returns {string|*} + */ +function encodeId(value) { + const result = {}; + if (value) { + const bidIds = { + id: value + } + result.uid2 = bidIds; + logInfo('Decoded value ' + JSON.stringify(result)); + return result; + } + return undefined; +} + +/** @type {Submodule} */ +export const uid2IdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * Vendor id of Prebid + * @type {Number} + */ + gvlid: GVLID, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{uid2:{ id: string }} or undefined if value doesn't exists + */ + decode(value) { + return (value) ? encodeId(value) : undefined; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData|undefined} consentData + * @returns {uid2Id} + */ + getId(config, consentData) { + logInfo('Creating UID 2.0'); + let value = readCookie() || readFromLocalStorage(); + logInfo('The advertising token: ' + value); + return {id: value} + }, + +}; + +// Register submodule for userId +submodule('userId', uid2IdSubmodule); diff --git a/modules/uid2IdSystem.md b/modules/uid2IdSystem.md new file mode 100644 index 00000000000..59149e562ac --- /dev/null +++ b/modules/uid2IdSystem.md @@ -0,0 +1,24 @@ +## UID 2.0 User ID Submodule + +UID 2.0 ID Module. + +### Prebid Params + +Individual params may be set for the UID 2.0 Submodule. At least one identifier must be set in the params. + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'uid2' + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the UID 2.0 User ID Module integration. + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the UID20 module - `"uid2"` | `"uid2"` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the UID 2.O ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"uid2": { "id": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}}` | diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 9b26eff2ebf..31879286675 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -185,6 +185,13 @@ const USER_IDS_CONFIG = { }, source: 'novatiq.com', atype: 1 + }, + 'uid2': { + source: 'uidapi.com', + atype: 3, + getValue: function(data) { + return data.id; + } } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index bd14ea0b9e7..53d9196e255 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -162,6 +162,13 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 1 }] - } + }, + { + source: 'uidapi.com', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] + } ] ``` diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 828b2993e40..4038e9b00e4 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -84,6 +84,9 @@ pbjs.setConfig({ partnerId: 0000, uid: '12345xyz' } + }, { + name: 'uid2' + } }], syncDelay: 5000, auctionDelay: 1000 @@ -161,7 +164,7 @@ pbjs.setConfig({ type: 'html5', name: '_criteoId', expires: 1 - } + } }], syncDelay: 5000 } diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 659e5257e90..bd839124b6d 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -276,6 +276,20 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + it('uid2', function() { + const userId = { + uid2: {'id': 'Sample_AD_Token'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3 + }] + }); + }); it('pubProvidedId', function() { const userId = { pubProvidedId: [{ diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 95279380232..cfc3529496a 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -41,6 +41,7 @@ import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; import {getPrebidInternal} from 'src/utils.js'; +import {uid2IdSubmodule} from 'modules/uid2IdSystem.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -459,7 +460,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -467,14 +468,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig({ userSync: { @@ -485,7 +486,7 @@ describe('User ID', function () { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig({ userSync: { @@ -502,15 +503,15 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 15 configurations should result in 15 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + it('config with 15 configurations should result in 16 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig({ userSync: { @@ -555,14 +556,16 @@ describe('User ID', function () { }, { name: 'tapadId', storage: {name: 'tapad_id', type: 'cookie'} + }, { + name: 'uid2' }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 15 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 16 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig({ userSync: { @@ -577,7 +580,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig({ userSync: { @@ -592,7 +595,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig({ userSync: { @@ -1645,7 +1648,7 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId, Criteo and mwOpenLinkId have data to pass', function (done) { + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId, Criteo, UID 2.0 and mwOpenLinkId have data to pass', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1661,8 +1664,9 @@ describe('User ID', function () { coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (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())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1676,7 +1680,8 @@ describe('User ID', function () { ['haloId', 'haloId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], ['mwOpenLinkId', 'mwol', 'cookie'], - ['tapadId', 'tapad_id', 'cookie'])); + ['tapadId', 'tapad_id', 'cookie'], + ['uid2', 'uid2id', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1719,8 +1724,10 @@ describe('User ID', function () { // also check that mwOpenLink id was copied to bid expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); - - expect(bid.userIdAsEids.length).to.equal(12); + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); + expect(bid.userIdAsEids.length).to.equal(13); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1735,11 +1742,12 @@ describe('User ID', function () { coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId, haloId and mwOpenLinkId have their modules added before and after init', function (done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId, haloId, UID 2.0 and mwOpenLinkId have their modules added before and after init', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1755,6 +1763,7 @@ describe('User ID', function () { coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (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())); setSubmoduleRegistry([]); @@ -1776,6 +1785,7 @@ describe('User ID', function () { attachIdSystem(criteoIdSubmodule); attachIdSystem(mwOpenLinkIdSubModule); attachIdSystem(tapadIdSubmodule); + attachIdSystem(uid2IdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1789,7 +1799,8 @@ describe('User ID', function () { ['haloId', 'haloId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], ['mwOpenLinkId', 'mwol', 'cookie'], - ['tapadId', 'tapad_id', 'cookie'])); + ['tapadId', 'tapad_id', 'cookie'], + ['uid2', 'uid2id', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1834,9 +1845,12 @@ describe('User ID', function () { // also check that mwOpenLink id data was copied to bid expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); - expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123') + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); ; - expect(bid.userIdAsEids.length).to.equal(12); + expect(bid.userIdAsEids.length).to.equal(13); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1851,6 +1865,7 @@ describe('User ID', function () { coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -2065,6 +2080,34 @@ describe('User ID', function () { }, {adUnits}); }); + it('test hook from UID2 cookie', function (done) { + coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([uid2IdSubmodule]); + init(config); + config.setConfig(getConfigMock(['uid2', 'uid2id', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.uid2'); + expect(bid.userId.uid2).to.have.deep.nested.property('id'); + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3, + }] + }); + }); + }); + coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); it('should add new id system ', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); @@ -2080,8 +2123,9 @@ describe('User ID', function () { coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('__uid2_advertising_token', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, uid2IdSubmodule]); init(config); config.setConfig({ @@ -2109,6 +2153,8 @@ describe('User ID', function () { name: 'haloId', storage: {name: 'haloId', type: 'cookie'} }, { name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} + }, { + name: 'uid2' }] } }); @@ -2166,8 +2212,11 @@ describe('User ID', function () { // also check that haloId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.haloId'); expect(bid.userId.haloId).to.equal('testHaloId'); + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); - expect(bid.userIdAsEids.length).to.equal(10); + expect(bid.userIdAsEids.length).to.equal(11); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); From 78b3a8b099c4d05459d777d9b6cf6a37254ccdf5 Mon Sep 17 00:00:00 2001 From: h12media <65672347+h12media@users.noreply.github.com> Date: Thu, 25 Mar 2021 14:00:58 +0300 Subject: [PATCH 129/198] H12media Bid Adapter: added new optional params, fixes, and various integration support (#6436) * Change module H12 Media * Change module H12 Media * Change module H12 Media * Change module H12 Media * Update module H12 Media * Update module H12 Media * Update module H12 Media * Update module H12 Media * Update module H12 Media * Update module H12 Media --- modules/h12mediaBidAdapter.js | 208 +++++++++++-------- test/spec/modules/h12mediaBidAdapter_spec.js | 135 ++++++++---- 2 files changed, 218 insertions(+), 125 deletions(-) diff --git a/modules/h12mediaBidAdapter.js b/modules/h12mediaBidAdapter.js index 0d2c22a3f68..7b736780226 100644 --- a/modules/h12mediaBidAdapter.js +++ b/modules/h12mediaBidAdapter.js @@ -1,6 +1,5 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import find from 'core-js-pure/features/array/find.js'; const BIDDER_CODE = 'h12media'; const DEFAULT_URL = 'https://bidder.h12-media.com/prebid/'; const DEFAULT_CURRENCY = 'USD'; @@ -16,21 +15,31 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { - const requestUrl = validBidRequests[0].params.endpointdom || DEFAULT_URL; - const isiframe = !((window.self === window.top) || window.frameElement); + const isiframe = utils.inIframe(); const screenSize = getClientDimensions(); const docSize = getDocumentDimensions(); - const bidrequests = validBidRequests.map((bidRequest) => { + return validBidRequests.map((bidRequest) => { const bidderParams = bidRequest.params; - const adUnitElement = document.getElementById(bidRequest.adUnitCode); + const requestUrl = bidderParams.endpointdom || DEFAULT_URL; + let pubsubid = bidderParams.pubsubid || ''; + if (pubsubid && pubsubid.length > 32) { + utils.logError('Bidder param \'pubsubid\' should be not more than 32 chars.'); + pubsubid = ''; + } + const pubcontainerid = bidderParams.pubcontainerid; + const adUnitElement = document.getElementById(pubcontainerid || bidRequest.adUnitCode); const ishidden = !isVisible(adUnitElement); - const coords = { + const framePos = getFramePos(); + const coords = isiframe ? { + x: framePos[0], + y: framePos[1], + } : { x: adUnitElement && adUnitElement.getBoundingClientRect().x, y: adUnitElement && adUnitElement.getBoundingClientRect().y, }; - return { + const bidrequest = { bidId: bidRequest.bidId, transactionId: bidRequest.transactionId, adunitId: bidRequest.adUnitCode, @@ -40,33 +49,46 @@ export const spec = { adunitSize: bidRequest.mediaTypes.banner.sizes || [], coords, ishidden, + pubsubid, + pubcontainerid, }; - }); - return { - method: 'POST', - url: requestUrl, - options: {withCredentials: false}, - data: { - gdpr: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false, - gdpr_cs: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? bidderRequest.gdprConsent.consentString : '', - topLevelUrl: window.top.location.href, - refererUrl: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer : '', - isiframe, - version: '$prebid.version$', - visitorInfo: { - localTime: getLocalDateFormatted(), - dayOfWeek: new Date().getDay(), - screenWidth: screenSize[0], - screenHeight: screenSize[1], - docWidth: docSize[0], - docHeight: docSize[1], - scrollbarx: window.scrollX, - scrollbary: window.scrollY, + let windowTop; + try { + windowTop = window.top; + } catch (e) { + utils.logMessage(e); + windowTop = window; + } + + return { + method: 'POST', + url: requestUrl, + options: {withCredentials: true}, + data: { + gdpr: !!utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies', false), + gdpr_cs: utils.deepAccess(bidderRequest, 'gdprConsent.consentString', ''), + usp: !!utils.deepAccess(bidderRequest, 'uspConsent', false), + usp_cs: utils.deepAccess(bidderRequest, 'uspConsent', ''), + topLevelUrl: utils.deepAccess(bidderRequest, 'refererInfo.referer', ''), + refererUrl: windowTop.document.referrer, + isiframe, + version: '$prebid.version$', + ExtUserIDs: bidRequest.userId, + visitorInfo: { + localTime: getLocalDateFormatted(), + dayOfWeek: new Date().getDay(), + screenWidth: screenSize[0], + screenHeight: screenSize[1], + docWidth: docSize[0], + docHeight: docSize[1], + scrollbarx: windowTop.scrollX, + scrollbary: windowTop.scrollY, + }, + bidrequest, }, - bidrequests, - }, - }; + }; + }); }, interpretResponse: function(serverResponse, bidRequests) { @@ -74,29 +96,28 @@ export const spec = { try { const serverBody = serverResponse.body; if (serverBody) { - if (serverBody.bids) { - serverBody.bids.forEach(bidBody => { - const bidRequest = find(bidRequests.data.bidrequests, bid => bid.bidId === bidBody.bidId); - const bidResponse = { - currency: serverBody.currency || DEFAULT_CURRENCY, - netRevenue: serverBody.netRevenue || DEFAULT_NET_REVENUE, - ttl: serverBody.ttl || DEFAULT_TTL, - requestId: bidBody.bidId, - cpm: bidBody.cpm, - width: bidBody.width, - height: bidBody.height, - creativeId: bidBody.creativeId, - ad: bidBody.ad, - meta: bidBody.meta, - mediaType: 'banner', - }; - if (bidRequest) { - bidResponse.pubid = bidRequest.pubid; - bidResponse.placementid = bidRequest.placementid; - bidResponse.size = bidRequest.size; - } - bidResponses.push(bidResponse); - }); + if (serverBody.bid) { + const bidBody = serverBody.bid; + const bidRequest = bidRequests.data.bidrequest; + const bidResponse = { + currency: serverBody.currency || DEFAULT_CURRENCY, + netRevenue: serverBody.netRevenue || DEFAULT_NET_REVENUE, + ttl: serverBody.ttl || DEFAULT_TTL, + requestId: bidBody.bidId, + cpm: bidBody.cpm, + width: bidBody.width, + height: bidBody.height, + creativeId: bidBody.creativeId, + ad: bidBody.ad, + meta: bidBody.meta, + mediaType: 'banner', + }; + if (bidRequest) { + bidResponse.pubid = bidRequest.pubid; + bidResponse.placementid = bidRequest.placementid; + bidResponse.size = bidRequest.size; + } + bidResponses.push(bidResponse); } } return bidResponses; @@ -105,47 +126,50 @@ export const spec = { } }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { - const serverBody = serverResponses[0].body; + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, usPrivacy) { const syncs = []; + const uspApplies = !!utils.deepAccess(usPrivacy, 'uspConsent', false); + const uspString = utils.deepAccess(usPrivacy, 'uspConsent', ''); gdprConsent = gdprConsent || { gdprApplies: false, consentString: '', }; - if (serverBody) { - if (serverBody.bids) { - serverBody.bids.forEach(bidBody => { - const userSyncUrls = bidBody.usersync || []; - const userSyncUrlProcess = url => { - return url - .replace('{gdpr}', gdprConsent.gdprApplies) - .replace('{gdpr_cs}', gdprConsent.consentString); - } + const userSyncUrlProcess = url => { + return url + .replace('{gdpr}', gdprConsent.gdprApplies) + .replace('{gdpr_cs}', gdprConsent.consentString) + .replace('{usp}', uspApplies) + .replace('{usp_cs}', uspString); + } - userSyncUrls.forEach(sync => { - if (syncOptions.iframeEnabled && sync.type === 'iframe' && sync.url) { - syncs.push({ - type: 'iframe', - url: userSyncUrlProcess(sync.url), - }); - } - if (syncOptions.pixelEnabled && sync.type === 'image' && sync.url) { - syncs.push({ - type: 'image', - url: userSyncUrlProcess(sync.url), - }); - } + serverResponses.forEach(serverResponse => { + const userSyncUrls = serverResponse.body.usersync || []; + userSyncUrls.forEach(sync => { + if (syncOptions.iframeEnabled && sync.type === 'iframe' && sync.url) { + syncs.push({ + type: 'iframe', + url: userSyncUrlProcess(sync.url), }); - }); - } - } + } + if (syncOptions.pixelEnabled && sync.type === 'image' && sync.url) { + syncs.push({ + type: 'image', + url: userSyncUrlProcess(sync.url), + }); + } + }) + }); return syncs; }, } function getContext(elem) { - return elem && window.document.body.contains(elem) ? window : (window.top.document.body.contains(elem) ? top : undefined); + try { + return elem && window.document.body.contains(elem) ? window : (window.top.document.body.contains(elem) ? top : undefined); + } catch (e) { + return undefined; + } } function isDefined(val) { @@ -206,4 +230,24 @@ function getLocalDateFormatted() { return `${d.getFullYear()}-${two(d.getMonth() + 1)}-${two(d.getDate())} ${two(d.getHours())}:${two(d.getMinutes())}:${two(d.getSeconds())}`; } +function getFramePos() { + let t = window; + let m = 0; + let frmLeft = 0; + let frmTop = 0; + do { + m = m + 1; + try { + if (m > 1) { + t = t.parent + } + frmLeft = frmLeft + t.frameElement.getBoundingClientRect().left; + frmTop = frmTop + t.frameElement.getBoundingClientRect().top; + } catch (o) { /* keep looping */ + } + } while ((m < 100) && (t.parent !== t.self)) + + return [frmLeft, frmTop]; +} + registerBidder(spec); diff --git a/test/spec/modules/h12mediaBidAdapter_spec.js b/test/spec/modules/h12mediaBidAdapter_spec.js index 08a83ce981f..9861069f260 100644 --- a/test/spec/modules/h12mediaBidAdapter_spec.js +++ b/test/spec/modules/h12mediaBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/h12mediaBidAdapter'; import {newBidder} from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; describe('H12 Media Adapter', function () { const DEFAULT_CURRENCY = 'USD'; @@ -21,6 +22,7 @@ describe('H12 Media Adapter', function () { auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f313', params: { pubid: 123321, + pubsubid: 'pubsubtestid', }, }; @@ -72,34 +74,34 @@ describe('H12 Media Adapter', function () { currency: 'EUR', netRevenue: true, ttl: 500, - bids: [{ + bid: { bidId: validBid.bidId, cpm: 0.33, width: 300, height: 600, creativeId: '335566', ad: '
my ad
', - usersync: [ - {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'image'}, - {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'iframe'} - ], meta: { advertiserId: '54321', advertiserName: 'My advertiser', advertiserDomains: ['test.com'] } - }] + }, + usersync: [ + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'image'}, + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'iframe'} + ], }; const serverResponse2 = { - bids: [{ + bid: { bidId: validBid2.bidId, cpm: 0.33, width: 300, height: 600, creativeId: '335566', ad: '
my ad 2
', - }] + } }; function removeElement(id) { @@ -152,6 +154,10 @@ describe('H12 Media Adapter', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); + sandbox.stub(frameElement, 'getBoundingClientRect').returns({ + left: 10, + top: 10, + }); }); afterEach(function () { @@ -186,36 +192,62 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({adunitSize: validBid.mediaTypes.banner.sizes}); + expect(requestsData).to.include({adunitSize: validBid.mediaTypes.banner.sizes}); }); it('should return empty bid size', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData2 = requests[1].data.bidrequest; + + expect(requestsData2).to.deep.include({adunitSize: []}); + }); + + it('should return pubsubid from params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const requestsData = requests[0].data.bidrequest; + const requestsData2 = requests[1].data.bidrequest; + + expect(requestsData).to.include({pubsubid: 'pubsubtestid'}); + expect(requestsData2).to.include({pubsubid: ''}); + }); - expect(requestsData.bidrequests[1]).to.deep.include({adunitSize: []}); + it('should return empty for incorrect pubsubid from params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const bidWithPub = {...validBid}; + bidWithPub.params.pubsubid = 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'; // More than 32 chars + const requests = spec.buildRequests([bidWithPub], bidderRequest); + const requestsData = requests[0].data.bidrequest; + + expect(requestsData).to.include({pubsubid: ''}); }); it('should return bid size from params', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; + const requestsData2 = requests[1].data.bidrequest; - expect(requestsData.bidrequests[1]).to.include({size: validBid2.params.size}); + expect(requestsData).to.include({size: ''}); + expect(requestsData2).to.include({size: validBid2.params.size}); }); it('should return GDPR info', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; expect(requestsData).to.include({gdpr: true, gdpr_cs: bidderRequest.gdprConsent.consentString}); + expect(requestsData2).to.include({gdpr: true, gdpr_cs: bidderRequest.gdprConsent.consentString}); }); it('should not have error on empty GDPR', function () { @@ -223,9 +255,23 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid2.adUnitCode); const bidderRequestWithoutGDRP = {...bidderRequest, gdprConsent: null}; const requests = spec.buildRequests([validBid, validBid2], bidderRequestWithoutGDRP); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; expect(requestsData).to.include({gdpr: false}); + expect(requestsData2).to.include({gdpr: false}); + }); + + it('should not have error on empty USP', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const bidderRequestWithoutUSP = {...bidderRequest, uspConsent: null}; + const requests = spec.buildRequests([validBid, validBid2], bidderRequestWithoutUSP); + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; + + expect(requestsData).to.include({usp: false}); + expect(requestsData2).to.include({usp: false}); }); it('should create single POST', function () { @@ -233,7 +279,8 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - expect(requests.method).to.equal('POST'); + expect(requests[0].method).to.equal('POST'); + expect(requests[1].method).to.equal('POST'); }); }); @@ -241,42 +288,44 @@ describe('H12 Media Adapter', function () { it('should return coords', function () { createElementVisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.deep.include({coords: {x: 10, y: 10}}); + expect(requestsData).to.deep.include({coords: {x: 10, y: 10}}); }); - it('should define not iframe', function () { + it('should define iframe', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; - expect(requestsData).to.include({isiframe: false}); + expect(requestsData).to.include({isiframe: true}); + expect(requestsData2).to.include({isiframe: true}); }); it('should define visible element', function () { createElementVisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: false}); + expect(requestsData).to.include({ishidden: false}); }); it('should define invisible element', function () { createElementInvisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + expect(requestsData).to.include({ishidden: true}); }); it('should define hidden element', function () { createElementHidden(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + expect(requestsData).to.include({ishidden: true}); }); }); @@ -290,27 +339,27 @@ describe('H12 Media Adapter', function () { it('should return no bids if the response is empty', function () { const bidResponse = spec.interpretResponse({ body: [] }, { validBid }); - expect(bidResponse.length).to.equal(0); + expect(bidResponse).to.be.empty; }); it('should return valid bid responses', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const request = spec.buildRequests([validBid, validBid2], bidderRequest); - const bidResponse = spec.interpretResponse({body: serverResponse}, request); + const bidResponse = spec.interpretResponse({body: serverResponse}, request[0]); expect(bidResponse[0]).to.deep.include({ requestId: validBid.bidId, - ad: serverResponse.bids[0].ad, + ad: serverResponse.bid.ad, mediaType: 'banner', - creativeId: serverResponse.bids[0].creativeId, - cpm: serverResponse.bids[0].cpm, - width: serverResponse.bids[0].width, - height: serverResponse.bids[0].height, + creativeId: serverResponse.bid.creativeId, + cpm: serverResponse.bid.cpm, + width: serverResponse.bid.width, + height: serverResponse.bid.height, currency: 'EUR', netRevenue: true, ttl: 500, - meta: serverResponse.bids[0].meta, + meta: serverResponse.bid.meta, pubid: validBid.params.pubid }); }); @@ -319,17 +368,17 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const request = spec.buildRequests([validBid, validBid2], bidderRequest); - const bidResponse = spec.interpretResponse({body: serverResponse2}, request); + const bidResponse = spec.interpretResponse({body: serverResponse2}, request[0]); expect(bidResponse[0]).to.deep.include({ requestId: validBid2.bidId, - ad: serverResponse2.bids[0].ad, + ad: serverResponse2.bid.ad, mediaType: 'banner', - creativeId: serverResponse2.bids[0].creativeId, - cpm: serverResponse2.bids[0].cpm, - width: serverResponse2.bids[0].width, - height: serverResponse2.bids[0].height, - meta: serverResponse2.bids[0].meta, + creativeId: serverResponse2.bid.creativeId, + cpm: serverResponse2.bid.cpm, + width: serverResponse2.bid.width, + height: serverResponse2.bid.height, + meta: serverResponse2.bid.meta, pubid: validBid2.params.pubid, currency: DEFAULT_CURRENCY, netRevenue: DEFAULT_NET_REVENUE, From 76c86e8e21e93e091d1764dbf5b30306806d275b Mon Sep 17 00:00:00 2001 From: guiann Date: Thu, 25 Mar 2021 14:50:44 +0100 Subject: [PATCH 130/198] AdYouLike Bid Adapter: add video capabilities (#6398) * read and send getFloor value * improve robustness on OnEvent fields * add video mediatype to adyoulike adapter * improve robustness on OnEvent fields * handle instream cases * minor format fix * move the atob call to apply it on every Vast response * update adyoulike md file with video type * update adyoulike bidder doc * fix merge error on 'bid' var name * update adyoulike bidder doc --- modules/adyoulikeBidAdapter.js | 73 +++++++++++++++++++++++++++------- modules/adyoulikeBidAdapter.md | 24 ++++++----- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index e46a9b6bed2..385ada65538 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -2,11 +2,12 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import find from 'core-js-pure/features/array/find.js'; -import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; const VERSION = '1.0'; const BIDDER_CODE = 'adyoulike'; const DEFAULT_DC = 'hb-api'; +const CURRENCY = 'USD'; const NATIVE_IMAGE = { image: { @@ -34,7 +35,7 @@ const NATIVE_IMAGE = { export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, NATIVE], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], aliases: ['ayl'], // short code /** * Determines whether or not the given bid request is valid. @@ -59,21 +60,28 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { const payload = { Version: VERSION, - Bids: bidRequests.reduce((accumulator, bid) => { - let sizesArray = getSizeArray(bid); + Bids: bidRequests.reduce((accumulator, bidReq) => { + let mediatype = getMediatype(bidReq); + let sizesArray = getSizeArray(bidReq); let size = getSize(sizesArray); - accumulator[bid.bidId] = {}; - accumulator[bid.bidId].PlacementID = bid.params.placement; - accumulator[bid.bidId].TransactionID = bid.transactionId; - accumulator[bid.bidId].Width = size.width; - accumulator[bid.bidId].Height = size.height; - accumulator[bid.bidId].AvailableSizes = sizesArray.join(','); - if (bid.mediaTypes && bid.mediaTypes.native) { - let nativeReq = bid.mediaTypes.native; + accumulator[bidReq.bidId] = {}; + accumulator[bidReq.bidId].PlacementID = bidReq.params.placement; + accumulator[bidReq.bidId].TransactionID = bidReq.transactionId; + accumulator[bidReq.bidId].Width = size.width; + accumulator[bidReq.bidId].Height = size.height; + accumulator[bidReq.bidId].AvailableSizes = sizesArray.join(','); + if (typeof bidReq.getFloor === 'function') { + accumulator[bidReq.bidId].Pricing = getFloor(bidReq, size, mediatype); + } + if (mediatype === NATIVE) { + let nativeReq = bidReq.mediaTypes.native; if (nativeReq.type === 'image') { nativeReq = Object.assign({}, NATIVE_IMAGE, nativeReq); } - accumulator[bid.bidId].Native = nativeReq; + accumulator[bidReq.bidId].Native = nativeReq; + } + if (mediatype === VIDEO) { + accumulator[bidReq.bidId].Video = bidReq.mediaTypes.video; } return accumulator; }, {}), @@ -156,6 +164,31 @@ function getCanonicalUrl() { return ''; } +/* Get mediatype from bidRequest */ +function getMediatype(bidRequest) { + var type = BANNER; + + if (utils.deepAccess(bidRequest, 'mediaTypes.native')) { + type = NATIVE; + } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + type = VIDEO; + } + + return type; +} +/* Get Floor price information */ +function getFloor(bidRequest, size, mediaType) { + const bidFloors = bidRequest.getFloor({ + currency: CURRENCY, + mediaType, + size: [ size.width, size.height ] + }); + + if (!isNaN(bidFloors.floor) && (bidFloors.currency === CURRENCY)) { + return bidFloors.floor; + } +} + /* Get information on page refresh */ function getPageRefreshed() { try { @@ -287,6 +320,14 @@ function getTrackers(eventsArray, jsTrackers) { return result; } +function getVideoAd(response) { + var adJson = {}; + if (typeof response.Ad === 'string') { + adJson = JSON.parse(response.Ad.match(/\/\*PREBID\*\/(.*)\/\*PREBID\*\//)[1]); + return utils.deepAccess(adJson, 'Content.MainVideo.Vast'); + } +} + function getNativeAssets(response, nativeConfig) { const native = {}; @@ -402,12 +443,16 @@ function createBid(response, bidRequests) { creativeId: response.CreativeID, cpm: response.Price, netRevenue: true, - currency: 'USD' + currency: CURRENCY }; if (request && request.Native) { bid.native = getNativeAssets(response, request.Native); bid.mediaType = 'native'; + } else if (request && request.Video) { + const vast64 = response.Vast || getVideoAd(response); + bid.vastXml = vast64 ? window.atob(vast64) : ''; + bid.mediaType = 'video'; } else { bid.width = response.Width; bid.height = response.Height; diff --git a/modules/adyoulikeBidAdapter.md b/modules/adyoulikeBidAdapter.md index d0e7fa8883b..edb47d25637 100644 --- a/modules/adyoulikeBidAdapter.md +++ b/modules/adyoulikeBidAdapter.md @@ -7,16 +7,20 @@ Maintainer: prebid@adyoulike.com # Description Module that connects to Adyoulike demand sources. -Banner formats are supported. +Banner, Native and Video ad formats are supported. # Test Parameters ``` - var adUnits = { + var adUnits = { "code": "test-div", "mediaTypes": { "banner": { "sizes": ["300x250"] }, + "video": { + context: "instream", + playerSize: [[640, 480]] + }, "native": { "image": { "required": true, @@ -48,12 +52,12 @@ Banner formats are supported. "sizes": [] } } - bids: [{ - bidder: "adyoulike", - params: { - placement: 194 f787b85c829fb8822cdaf1ae64435, - DC: "fra01", // Optional for set the data center name - } - }] - }; + }, + bids: [{ + bidder: "adyoulike", + params: { + placement: "e622af275681965d3095808561a1e510" + } + }] + }; ``` From b96f38003bf516f791ad0d90c21307b77bb4240a Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Thu, 25 Mar 2021 08:32:46 -0700 Subject: [PATCH 131/198] remove deprecated creative rendering example --- integrationExamples/gpt/creative_rendering.html | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/integrationExamples/gpt/creative_rendering.html b/integrationExamples/gpt/creative_rendering.html index aef8b7f1654..04d4736c631 100644 --- a/integrationExamples/gpt/creative_rendering.html +++ b/integrationExamples/gpt/creative_rendering.html @@ -1,9 +1,4 @@ - - - - + + + + + + + +

Prebid Display/Video Merged Auction with Adloox Integration

+ +

div-1

+
+ +
+ +

div-2

+
+ +
+ +

video-1

+
+ + + + diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js new file mode 100644 index 00000000000..3e92ae34004 --- /dev/null +++ b/modules/adlooxAnalyticsAdapter.js @@ -0,0 +1,288 @@ +/** + * This module provides [Adloox]{@link https://www.adloox.com/} Analytics + * The module will inject Adloox's verification JS tag alongside slot at bidWin + * @module modules/adlooxAnalyticsAdapter + */ + +import adapterManager from '../src/adapterManager.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { AUCTION_COMPLETED } from '../src/auction.js'; +import { EVENTS } from '../src/constants.json'; +import find from 'core-js-pure/features/array/find.js'; +import * as utils from '../src/utils.js'; + +const MODULE = 'adlooxAnalyticsAdapter'; + +const URL_JS = 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js'; + +const ADLOOX_VENDOR_ID = 93; + +const ADLOOX_MEDIATYPE = { + DISPLAY: 2, + VIDEO: 6 +}; + +const MACRO = {}; +MACRO['client'] = function(b, c) { + return c.client; +}; +MACRO['clientid'] = function(b, c) { + return c.clientid; +}; +MACRO['tagid'] = function(b, c) { + return c.tagid; +}; +MACRO['platformid'] = function(b, c) { + return c.platformid; +}; +MACRO['targetelt'] = function(b, c) { + return c.toselector(b); +}; +MACRO['creatype'] = function(b, c) { + return b.mediaType == 'video' ? ADLOOX_MEDIATYPE.VIDEO : ADLOOX_MEDIATYPE.DISPLAY; +}; +MACRO['pbAdSlot'] = function(b, c) { + const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); + return utils.deepAccess(adUnit, 'fpd.context.pbAdSlot') || utils.getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; +}; + +const PARAMS_DEFAULT = { + 'id1': function(b) { return b.adUnitCode }, + 'id2': '%%pbAdSlot%%', + 'id3': function(b) { return b.bidder }, + 'id4': function(b) { return b.adId }, + 'id5': function(b) { return b.dealId }, + 'id6': function(b) { return b.creativeId }, + 'id7': function(b) { return b.size }, + 'id11': '$ADLOOX_WEBSITE' +}; + +const NOOP = function() {}; + +let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { + track({ eventType, args }) { + if (!analyticsAdapter[`handle_${eventType}`]) return; + + utils.logInfo(MODULE, 'track', eventType, args); + + analyticsAdapter[`handle_${eventType}`](args); + } +}); + +analyticsAdapter.context = null; + +analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; +analyticsAdapter.enableAnalytics = function(config) { + analyticsAdapter.context = null; + + utils.logInfo(MODULE, 'config', config); + + if (!utils.isPlainObject(config.options)) { + utils.logError(MODULE, 'missing options'); + return; + } + if (!(config.options.js === undefined || utils.isStr(config.options.js))) { + utils.logError(MODULE, 'invalid js options value'); + return; + } + if (!(config.options.toselector === undefined || utils.isFn(config.options.toselector))) { + utils.logError(MODULE, 'invalid toselector options value'); + return; + } + if (!utils.isStr(config.options.client)) { + utils.logError(MODULE, 'invalid client options value'); + return; + } + if (!utils.isNumber(config.options.clientid)) { + utils.logError(MODULE, 'invalid clientid options value'); + return; + } + if (!utils.isNumber(config.options.tagid)) { + utils.logError(MODULE, 'invalid tagid options value'); + return; + } + if (!utils.isNumber(config.options.platformid)) { + utils.logError(MODULE, 'invalid platformid options value'); + return; + } + if (!(config.options.params === undefined || utils.isPlainObject(config.options.params))) { + utils.logError(MODULE, 'invalid params options value'); + return; + } + + analyticsAdapter.context = { + js: config.options.js || URL_JS, + toselector: config.options.toselector || function(bid) { + let code = utils.getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; + // https://mathiasbynens.be/notes/css-escapes + code = code.replace(/^\d/, '\\3$& '); + return `#${code}` + }, + client: config.options.client, + clientid: config.options.clientid, + tagid: config.options.tagid, + platformid: config.options.platformid, + params: [] + }; + + config.options.params = utils.mergeDeep({}, PARAMS_DEFAULT, config.options.params || {}); + Object + .keys(config.options.params) + .forEach(k => { + if (!Array.isArray(config.options.params[k])) { + config.options.params[k] = [ config.options.params[k] ]; + } + config.options.params[k].forEach(v => analyticsAdapter.context.params.push([ k, v ])); + }); + + Object.keys(COMMAND_QUEUE).forEach(commandProcess); + + analyticsAdapter.originEnableAnalytics(config); +} + +analyticsAdapter.originDisableAnalytics = analyticsAdapter.disableAnalytics; +analyticsAdapter.disableAnalytics = function() { + analyticsAdapter.context = null; + + analyticsAdapter.originDisableAnalytics(); +} + +analyticsAdapter.url = function(url, args, bid) { + // utils.formatQS outputs PHP encoded querystrings... (╯°□°)╯ ┻━┻ + function a2qs(a) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent + function fixedEncodeURIComponent(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16); + }); + } + + const args = []; + let n = a.length; + while (n-- > 0) { + if (!(a[n][1] === undefined || a[n][1] === null || a[n][1] === false)) { + args.unshift(fixedEncodeURIComponent(a[n][0]) + (a[n][1] !== true ? ('=' + fixedEncodeURIComponent(a[n][1])) : '')); + } + } + + return args.join('&'); + } + + const macros = (str) => { + return str.replace(/%%([a-z]+)%%/gi, (match, p1) => MACRO[p1] ? MACRO[p1](bid, analyticsAdapter.context) : match); + }; + + url = macros(url); + args = args || []; + + let n = args.length; + while (n-- > 0) { + if (utils.isFn(args[n][1])) { + try { + args[n][1] = args[n][1](bid); + } catch (_) { + utils.logError(MODULE, 'macro', args[n][0], _.message); + args[n][1] = `ERROR: ${_.message}`; + } + } + if (utils.isStr(args[n][1])) { + args[n][1] = macros(args[n][1]); + } + } + + return url + a2qs(args); +} + +analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { + if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; + analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; + + utils.logMessage(MODULE, 'preloading verification JS'); + + const uri = utils.parseUrl(analyticsAdapter.url(`${analyticsAdapter.context.js}#`)); + + const link = document.createElement('link'); + link.setAttribute('href', `${uri.protocol}://${uri.host}${uri.pathname}`); + link.setAttribute('rel', 'preload'); + link.setAttribute('as', 'script'); + utils.insertElement(link); +} + +analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { + const sl = analyticsAdapter.context.toselector(bid); + let el; + try { + el = document.querySelector(sl); + } catch (_) { } + if (!el) { + utils.logWarn(MODULE, `unable to find ad unit code '${bid.adUnitCode}' slot using selector '${sl}' (use options.toselector to change), ignoring`); + return; + } + + utils.logMessage(MODULE, `measuring '${bid.mediaType}' unit at '${bid.adUnitCode}'`); + + const params = analyticsAdapter.context.params.concat([ + [ 'tagid', '%%tagid%%' ], + [ 'platform', '%%platformid%%' ], + [ 'fwtype', 4 ], + [ 'targetelt', '%%targetelt%%' ], + [ 'creatype', '%%creatype%%' ] + ]); + + loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), 'adloox'); +} + +adapterManager.registerAnalyticsAdapter({ + adapter: analyticsAdapter, + code: 'adloox', + gvlid: ADLOOX_VENDOR_ID +}); + +export default analyticsAdapter; + +// src/events.js does not support custom events or handle races... (╯°□°)╯ ┻━┻ +const COMMAND_QUEUE = {}; +export const COMMAND = { + CONFIG: 'config', + URL: 'url', + TRACK: 'track' +}; +export function command(cmd, data, callback0) { + const cid = utils.getUniqueIdentifierStr(); + const callback = function() { + delete COMMAND_QUEUE[cid]; + if (callback0) callback0.apply(null, arguments); + }; + COMMAND_QUEUE[cid] = { cmd, data, callback }; + if (analyticsAdapter.context) commandProcess(cid); +} +function commandProcess(cid) { + const { cmd, data, callback } = COMMAND_QUEUE[cid]; + + utils.logInfo(MODULE, 'command', cmd, data); + + switch (cmd) { + case COMMAND.CONFIG: + const response = { + client: analyticsAdapter.context.client, + clientid: analyticsAdapter.context.clientid, + tagid: analyticsAdapter.context.tagid, + platformid: analyticsAdapter.context.platformid + }; + callback(response); + break; + case COMMAND.URL: + if (data.ids) data.args = data.args.concat(analyticsAdapter.context.params.filter(p => /^id([1-9]|10)$/.test(p[0]))); // not >10 + callback(analyticsAdapter.url(data.url, data.args, data.bid)); + break; + case COMMAND.TRACK: + analyticsAdapter.track(data); + callback(); // drain queue + break; + default: + utils.logWarn(MODULE, 'command unknown', cmd); + // do not callback as arguments are unknown and to aid debugging + } +} diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md new file mode 100644 index 00000000000..0ca67f937f6 --- /dev/null +++ b/modules/adlooxAnalyticsAdapter.md @@ -0,0 +1,146 @@ +# Overview + + Module Name: Adloox Analytics Adapter + Module Type: Analytics Adapter + Maintainer: technique@adloox.com + +# Description + +Analytics adapter for adloox.com. Contact adops@adloox.com for information. + +This module can be used to track: + + * Display + * Native + * Video (see below for further instructions) + +The adapter adds an HTML `