From af0e9d7e80169a03613df5edde473e0478ba0477 Mon Sep 17 00:00:00 2001 From: pm-shashank-jain Date: Thu, 13 Feb 2020 18:57:40 +0530 Subject: [PATCH 001/190] support for video in hybrid profiles --- modules/pubmaticServerBidAdapter.js | 148 +++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 14 deletions(-) diff --git a/modules/pubmaticServerBidAdapter.js b/modules/pubmaticServerBidAdapter.js index 043138a2908..d02781ecd46 100644 --- a/modules/pubmaticServerBidAdapter.js +++ b/modules/pubmaticServerBidAdapter.js @@ -3,7 +3,9 @@ import * as ajax from '../src/ajax'; import {userSync} from '../src/userSync'; import { config } from '../src/config'; import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, VIDEO } from '../src/mediaTypes'; const constants = require('../src/constants.json'); +const LOG_WARN_PREFIX = 'PubMatic: '; const BIDDER_CODE = 'pubmaticServer'; const ENDPOINT = 'https://ow.pubmatic.com/openrtb/2.5/'; @@ -15,6 +17,29 @@ const IFRAME = 'iframe'; const IMAGE = 'image'; const REDIRECT = 'redirect'; const DEFAULT_VERSION_ID = '0'; +const DATA_TYPES = { + 'NUMBER': 'number', + 'STRING': 'string', + 'BOOLEAN': 'boolean', + 'ARRAY': 'array', + 'OBJECT': 'object' +}; +const VIDEO_CUSTOM_PARAMS = { + 'mimes': DATA_TYPES.ARRAY, + 'minduration': DATA_TYPES.NUMBER, + 'maxduration': DATA_TYPES.NUMBER, + 'startdelay': DATA_TYPES.NUMBER, + 'playbackmethod': DATA_TYPES.ARRAY, + 'api': DATA_TYPES.ARRAY, + 'protocols': DATA_TYPES.ARRAY, + 'w': DATA_TYPES.NUMBER, + 'h': DATA_TYPES.NUMBER, + 'battr': DATA_TYPES.ARRAY, + 'linearity': DATA_TYPES.NUMBER, + 'placement': DATA_TYPES.NUMBER, + 'minbitrate': DATA_TYPES.NUMBER, + 'maxbitrate': DATA_TYPES.NUMBER +} const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url @@ -120,12 +145,54 @@ function _createOrtbTemplate(conf) { } function _createImpressionObject(bid, conf) { - return { + var mediaTypes = ''; + + var impObj = { id: bid.bidId, tagid: bid.params.adUnitId, bidfloor: _parseSlotParam('kadfloor', bid.params.kadfloor), secure: 1, - banner: { + ext: { + wrapper: { + div: bid.params.divId + }, + bidder: { + pubmatic: { + pmZoneId: _parseSlotParam('pmzoneid', bid.params.pmzoneid) + } + } + } + }; + if (bid.hasOwnProperty('mediaTypes')) { + for (mediaTypes in bid.mediaTypes) { + switch (mediaTypes) { + case BANNER: + var bannerObj = { + pos: 0, + topframe: utils.inIframe() ? 0 : 1, + format: (function() { + let arr = []; + for (let i = 0, l = bid.sizes.length; i < l; i++) { + arr.push({ + w: bid.sizes[i][0], + h: bid.sizes[i][1] + }); + } + return arr.length > 0 ? arr : UNDEFINED; + })() + } + impObj.banner = bannerObj; + break; + case VIDEO: + var videoObj = _createVideoRequest(bid); + if (videoObj !== UNDEFINED) { + impObj.video = videoObj; + } + break; + } + } + } else { + bannerObj = { pos: 0, topframe: utils.inIframe() ? 0 : 1, format: (function() { @@ -138,18 +205,10 @@ function _createImpressionObject(bid, conf) { } return arr.length > 0 ? arr : UNDEFINED; })() - }, - ext: { - wrapper: { - div: bid.params.divId - }, - bidder: { - pubmatic: { - pmZoneId: _parseSlotParam('pmzoneid', bid.params.pmzoneid) - } - } } - }; + impObj.banner = bannerObj; + } + return impObj; } function mandatoryParamCheck(paramName, paramValue) { @@ -241,9 +300,64 @@ function _createDummyBids (impData, bidResponses, errorCode) { } } +function _checkParamDataType(key, value, datatype) { + var errMsg = 'Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value; + var functionToExecute; + switch (datatype) { + case DATA_TYPES.BOOLEAN: + functionToExecute = utils.isBoolean; + break; + case DATA_TYPES.NUMBER: + functionToExecute = utils.isNumber; + break; + case DATA_TYPES.STRING: + functionToExecute = utils.isStr; + break; + case DATA_TYPES.ARRAY: + functionToExecute = utils.isArray; + break; + } + if (functionToExecute(value)) { + return value; + } + utils.logWarn(LOG_WARN_PREFIX + errMsg); + return UNDEFINED; +} + +function _createVideoRequest(bid) { + var videoData = bid.params.video; + var videoObj; + + if (videoData !== UNDEFINED) { + videoObj = {}; + for (var key in VIDEO_CUSTOM_PARAMS) { + if (videoData.hasOwnProperty(key)) { + videoObj[key] = _checkParamDataType(key, videoData[key], VIDEO_CUSTOM_PARAMS[key]); + } + } + // read playersize and assign to h and w. + if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0][0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[0][1], 10); + } else if (utils.isNumber(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[1], 10); + } + if (bid.params.video.hasOwnProperty('skippable')) { + videoObj.ext = { + 'video_skippable': bid.params.video.skippable ? 1 : 0 + }; + } + } else { + videoObj = UNDEFINED; + utils.logWarn(LOG_WARN_PREFIX + 'Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.'); + } + return videoObj; +} + export const spec = { code: BIDDER_CODE, - + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid * @@ -401,6 +515,12 @@ export const spec = { mi: miObj.hasOwnProperty(summary.bidder) ? miObj[summary.bidder] : UNDEFINED, regexPattern: summary.regex || undefined } + if (bid.ext.crtype) { + switch (bid.ext.crtype) { + case 'video': + newBid.vastXml = bid.adm; + } + } break; default: requestData.imp.forEach(function(impObj) { From d204ad31b938a433a4c0a13762a78b2aa0f4530e Mon Sep 17 00:00:00 2001 From: pm-shashank-jain Date: Thu, 13 Feb 2020 19:26:49 +0530 Subject: [PATCH 002/190] added newBid.mediaType for pubmaticServerBidAdapter --- modules/pubmaticServerBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/pubmaticServerBidAdapter.js b/modules/pubmaticServerBidAdapter.js index d02781ecd46..c7eff743a1d 100644 --- a/modules/pubmaticServerBidAdapter.js +++ b/modules/pubmaticServerBidAdapter.js @@ -520,6 +520,7 @@ export const spec = { case 'video': newBid.vastXml = bid.adm; } + newBid.mediaType = bid.ext.crtype; } break; default: From 8f8bcd781fbce227616ee984d3282b589516652e Mon Sep 17 00:00:00 2001 From: pm-shashank-jain Date: Wed, 5 Aug 2020 12:22:37 +0530 Subject: [PATCH 003/190] unit test case for video request --- .../modules/pubmaticServerBidAdapter_spec.js | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/test/spec/modules/pubmaticServerBidAdapter_spec.js b/test/spec/modules/pubmaticServerBidAdapter_spec.js index ed897ab8242..9a01d022038 100644 --- a/test/spec/modules/pubmaticServerBidAdapter_spec.js +++ b/test/spec/modules/pubmaticServerBidAdapter_spec.js @@ -4,7 +4,7 @@ import * as utils from 'src/utils'; const constants = require('src/constants.json'); describe('PubMaticServer adapter', () => { - let bidRequests; + let bidRequests, videoBidRequests; let bidResponses; let errorCodeBidResponses; @@ -79,6 +79,37 @@ describe('PubMaticServer adapter', () => { } ]; + videoBidRequests = [{ + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'pubmaticServer', + bidId: '22bddb28db77d', + params: { + publisherId: '5890', + adSlot: 'Div1', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30, + startdelay: 5, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + battr: [13, 14], + linearity: 1, + placement: 2, + minbitrate: 10, + maxbitrate: 10 + } + } + }]; + bidResponses = { 'body': { 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', @@ -321,6 +352,22 @@ describe('PubMaticServer adapter', () => { expect(data.imp[0].banner.format[0].w).to.equal(300); // width from 1st element of sizes array expect(data.imp[0].banner.format[0].h).to.equal(250); // height from 1st element of sizes array + }); + + it('request should have video params', () => { + let request = spec.buildRequests(videoBidRequests); + let data = JSON.parse(request.data); + // expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.ext.wrapper.wp).to.equal('pbjs'); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal(videoBidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID + // expect(data.ext.wrapper.profileid).to.equal(parseInt(videoBidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID + // expect(data.ext.wrapper.versionid).to.equal(parseInt(videoBidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + expect(data.imp[0].id).to.equal(videoBidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].tagid).to.equal(videoBidRequests[0].params.adUnitId); // tagid + expect(data.imp[0].video.mimes).to.deep.equal(videoBidRequests[0].params.video.mimes); + expect(data.imp[0].video.minduration).to.equal(videoBidRequests[0].params.video.minduration); + expect(data.imp[0].video.maxduration).to.equal(videoBidRequests[0].params.video.maxduration); }); it('Request params check with GDPR consent', () => { From 2abdebf8205f6c538fe3e6524da108eb60548663 Mon Sep 17 00:00:00 2001 From: pm-shashank-jain Date: Wed, 5 Aug 2020 15:31:23 +0530 Subject: [PATCH 004/190] reverted debug flag --- modules/pubmaticServerBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pubmaticServerBidAdapter.js b/modules/pubmaticServerBidAdapter.js index b06966a5dad..2603b63253f 100644 --- a/modules/pubmaticServerBidAdapter.js +++ b/modules/pubmaticServerBidAdapter.js @@ -565,7 +565,7 @@ export const spec = { _handleEids(payload, validBidRequests); return { method: 'POST', - url: utils.getParameterByName('pwtvc') ? ENDPOINT + '?debug=2' : ENDPOINT, + url: utils.getParameterByName('pwtvc') ? ENDPOINT + '?debug=1' : ENDPOINT, data: JSON.stringify(payload) }; }, From 540c7deff69364b98240f5426260eb1d6017232a Mon Sep 17 00:00:00 2001 From: Jason Snellbaker Date: Tue, 22 Sep 2020 15:52:05 -0400 Subject: [PATCH 005/190] increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6cca04d1c91..fc5a0441ad1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.9.0", + "version": "4.10.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 1b932a0fec23322af7b8e74c113c694f44f2be2e Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 23 Sep 2020 06:45:10 -0400 Subject: [PATCH 006/190] Britepool user id module update (#5750) * adding britepool_pubparams dynamic variable lookup and merge into submodule params if exists * adding support for gdpr consent string in query params * adding tests for britepool_pubparams * adding doc block for consentData * adding pixel on success * - ensures id resolution pixel only fires when authoritative information is not present - adds tests for id resolution pixel --- modules/britepoolIdSystem.js | 17 ++++-- test/spec/modules/britepoolIdSystem_spec.js | 64 ++++++++++++++++++++- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/modules/britepoolIdSystem.js b/modules/britepoolIdSystem.js index 17a39e96aad..90fd159571f 100644 --- a/modules/britepoolIdSystem.js +++ b/modules/britepoolIdSystem.js @@ -8,6 +8,7 @@ import * as utils from '../src/utils.js' import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; +const PIXEL = 'https://px.britepool.com/new?partner_id=t'; /** @type {Submodule} */ export const britepoolIdSubmodule = { @@ -28,7 +29,8 @@ export const britepoolIdSubmodule = { /** * Performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleParams} [submoduleConfigParams] + * @param {ConsentData|undefined} consentData * @returns {function(callback:function)} */ getId(submoduleConfigParams, consentData) { @@ -44,6 +46,9 @@ export const britepoolIdSubmodule = { }; } } + if (utils.isEmpty(params)) { + utils.triggerPixel(PIXEL); + } // Return for async operation return { callback: function(callback) { @@ -79,13 +84,17 @@ export const britepoolIdSubmodule = { }, /** * Helper method to create params for our API call - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleParams} [submoduleConfigParams] + * @param {ConsentData|undefined} consentData * @returns {object} Object with parsed out params */ createParams(submoduleConfigParams, consentData) { + const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; + const gdprConsentString = hasGdprData ? consentData.consentString : undefined; let errors = []; const headers = {}; - let params = Object.assign({}, submoduleConfigParams); + const dynamicVars = typeof britepool_pubparams !== 'undefined' ? britepool_pubparams : {}; // eslint-disable-line camelcase, no-undef + let params = Object.assign({}, submoduleConfigParams, dynamicVars); if (params.getter) { // Custom getter will not require other params if (typeof params.getter !== 'function') { @@ -98,7 +107,7 @@ export const britepoolIdSubmodule = { headers['x-api-key'] = params.api_key; } } - const url = params.url || 'https://api.britepool.com/v1/britepool/id'; + const url = params.url || `https://api.britepool.com/v1/britepool/id${gdprConsentString ? '?gdprString=' + encodeURIComponent(gdprConsentString) : ''}`; const getter = params.getter; delete params.api_key; delete params.url; diff --git a/test/spec/modules/britepoolIdSystem_spec.js b/test/spec/modules/britepoolIdSystem_spec.js index 2c6dd234a90..f2dd2ef533f 100644 --- a/test/spec/modules/britepoolIdSystem_spec.js +++ b/test/spec/modules/britepoolIdSystem_spec.js @@ -1,5 +1,5 @@ -import { expect } from 'chai'; import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; +import * as utils from '../../../src/utils.js'; describe('BritePool Submodule', () => { const api_key = '1111'; @@ -16,6 +16,32 @@ describe('BritePool Submodule', () => { }; }; + let triggerPixelStub; + + beforeEach(function (done) { + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + done(); + }); + + afterEach(function () { + triggerPixelStub.restore(); + }); + + it('trigger id resolution pixel when no identifiers set', () => { + britepoolIdSubmodule.getId({}); + expect(triggerPixelStub.called).to.be.true; + }); + + it('trigger id resolution pixel when no identifiers set with api_key param', () => { + britepoolIdSubmodule.getId({ api_key }); + expect(triggerPixelStub.called).to.be.true; + }); + + it('does not trigger id resolution pixel when identifiers set', () => { + britepoolIdSubmodule.getId({ api_key, aaid }); + expect(triggerPixelStub.called).to.be.false; + }); + it('sends x-api-key in header and one identifier', () => { const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); assert(errors.length === 0, errors); @@ -43,6 +69,42 @@ describe('BritePool Submodule', () => { expect(params.url).to.be.undefined; }); + it('test gdpr consent string in url', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }, { gdprApplies: true, consentString: 'expectedConsentString' }); + expect(url).to.equal('https://api.britepool.com/v1/britepool/id?gdprString=expectedConsentString'); + }); + + it('test gdpr consent string not in url if gdprApplies false', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }, { gdprApplies: false, consentString: 'expectedConsentString' }); + expect(url).to.equal('https://api.britepool.com/v1/britepool/id'); + }); + + it('test gdpr consent string not in url if consent string undefined', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }, { gdprApplies: true, consentString: undefined }); + expect(url).to.equal('https://api.britepool.com/v1/britepool/id'); + }); + + it('dynamic pub params should be added to params', () => { + window.britepool_pubparams = { ppid: '12345' }; + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); + expect(params).to.eql({ aaid, ppid: '12345' }); + window.britepool_pubparams = undefined; + }); + + it('dynamic pub params should override submodule params', () => { + window.britepool_pubparams = { ppid: '67890' }; + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, ppid: '12345' }); + expect(params).to.eql({ ppid: '67890' }); + window.britepool_pubparams = undefined; + }); + + it('if dynamic pub params undefined do nothing', () => { + window.britepool_pubparams = undefined; + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); + expect(params).to.eql({ aaid }); + window.britepool_pubparams = undefined; + }); + it('test getter override with value', () => { const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override, getter: getter_override }); expect(getter).to.equal(getter_override); From 5f598b42894e805017fda7a88c78d52518129e27 Mon Sep 17 00:00:00 2001 From: Rigel Cheng Date: Wed, 23 Sep 2020 22:34:28 +0800 Subject: [PATCH 007/190] Add a new param cid to bridgewellBidAdapter (#5764) * pass a new param cid to bridgewellBidAdapter * update the markdown file for bridgewellBidAdpter --- modules/bridgewellBidAdapter.js | 33 +++++++++---- modules/bridgewellBidAdapter.md | 6 +-- .../spec/modules/bridgewellBidAdapter_spec.js | 48 ++++++++++++++++++- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js index a2d3a2e70a2..5fca9acc0b3 100644 --- a/modules/bridgewellBidAdapter.js +++ b/modules/bridgewellBidAdapter.js @@ -38,15 +38,29 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const adUnits = []; utils._each(validBidRequests, function (bid) { - adUnits.push({ - ChannelID: bid.params.ChannelID, - adUnitCode: bid.adUnitCode, - mediaTypes: bid.mediaTypes || { - banner: { - sizes: bid.sizes + if (bid.params.cid) { + adUnits.push({ + cid: bid.params.cid, + adUnitCode: bid.adUnitCode, + requestId: bid.bidId, + mediaTypes: bid.mediaTypes || { + banner: { + sizes: bid.sizes + } } - } - }); + }); + } else { + adUnits.push({ + ChannelID: bid.params.ChannelID, + adUnitCode: bid.adUnitCode, + requestId: bid.bidId, + mediaTypes: bid.mediaTypes || { + banner: { + sizes: bid.sizes + } + } + }); + } }); let topUrl = ''; @@ -65,7 +79,8 @@ export const spec = { inIframe: utils.inIframe(), url: topUrl, referrer: getTopWindowReferrer(), - adUnits: adUnits + adUnits: adUnits, + refererInfo: bidderRequest.refererInfo, }, validBidRequests: validBidRequests }; diff --git a/modules/bridgewellBidAdapter.md b/modules/bridgewellBidAdapter.md index 6bcab4b8820..97e11f6eaf9 100644 --- a/modules/bridgewellBidAdapter.md +++ b/modules/bridgewellBidAdapter.md @@ -20,7 +20,7 @@ Module that connects to Bridgewell demand source to fetch bids. bids: [{ bidder: 'bridgewell', params: { - ChannelID: 'CgUxMjMzOBIBNiIFcGVubnkqCQisAhD6ARoBOQ' + cid: 12345 } }] }, { @@ -33,7 +33,7 @@ Module that connects to Bridgewell demand source to fetch bids. bids: [{ bidder: 'bridgewell', params: { - ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ' + cid: 56789 } }] }, { @@ -70,7 +70,7 @@ Module that connects to Bridgewell demand source to fetch bids. bids: [{ bidder: 'bridgewell', params: { - ChannelID: 'CgUxMjMzOBIBNiIGcGVubnkzKggI2AUQWhoBOQ' + cid: 2394 } }] }]; diff --git a/test/spec/modules/bridgewellBidAdapter_spec.js b/test/spec/modules/bridgewellBidAdapter_spec.js index fea2454393d..24ec523ac67 100644 --- a/test/spec/modules/bridgewellBidAdapter_spec.js +++ b/test/spec/modules/bridgewellBidAdapter_spec.js @@ -143,12 +143,58 @@ describe('bridgewellBidAdapter', function () { expect(payload.url).to.exist.and.to.equal('https://www.bridgewell.com/'); for (let i = 0, max_i = payload.adUnits.length; i < max_i; i++) { expect(payload.adUnits[i]).to.have.property('ChannelID').that.is.a('string'); + expect(payload.adUnits[i]).to.not.have.property('cid'); expect(payload.adUnits[i]).to.have.property('adUnitCode').and.to.equal('adunit-code-2'); + expect(payload.adUnits[i]).to.have.property('requestId').and.to.equal('3150ccb55da321'); + } + }); + + it('should attach valid params to the tag, part2', function() { + const bidderRequest = { + refererInfo: { + referer: 'https://www.bridgewell.com/' + } + } + const bidRequests2 = [ + { + 'bidder': 'bridgewell', + 'params': { + 'cid': 1234, + }, + 'adUnitCode': 'adunit-code-2', + 'mediaTypes': { + 'banner': { + 'sizes': [728, 90] + } + }, + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + ]; + + const request = spec.buildRequests(bidRequests2, bidderRequest); + const payload = request.data; + + expect(payload).to.be.an('object'); + expect(payload.adUnits).to.be.an('array'); + expect(payload.url).to.exist.and.to.equal('https://www.bridgewell.com/'); + for (let i = 0, max_i = payload.adUnits.length; i < max_i; i++) { + expect(payload.adUnits[i]).to.have.property('cid').that.is.a('number'); + expect(payload.adUnits[i]).to.not.have.property('ChannelID'); + expect(payload.adUnits[i]).to.have.property('adUnitCode').and.to.equal('adunit-code-2'); + expect(payload.adUnits[i]).to.have.property('requestId').and.to.equal('3150ccb55da321'); } }); it('should attach validBidRequests to the tag', function () { - const request = spec.buildRequests(bidRequests); + const bidderRequest = { + refererInfo: { + referer: 'https://www.bridgewell.com/' + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); const validBidRequests = request.validBidRequests; expect(validBidRequests).to.deep.equal(bidRequests); }); From 8b9b86a632b9c366fd3e5d9bfe3825a7dfd45c58 Mon Sep 17 00:00:00 2001 From: Michael Griego Date: Wed, 23 Sep 2020 17:03:06 -0500 Subject: [PATCH 008/190] Refactor refererDetection to allow for URL discovery on AMP pages. (#4846) * Refactor refererDetection to allow for URL discovery on AMP pages. * Update import to include extension. --- src/refererDetection.js | 256 ++++++++--------- test/spec/refererDetection_spec.js | 422 +++++++++++++++++++++++------ 2 files changed, 463 insertions(+), 215 deletions(-) diff --git a/src/refererDetection.js b/src/refererDetection.js index 60198678666..da68313736b 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -10,153 +10,48 @@ import { logWarn } from './utils.js'; +/** + * @param {Window} win Window + * @returns {Function} + */ export function detectReferer(win) { - /** - * Returns number of frames to reach top from current frame where prebid.js sits - * @returns {Array} levels - */ - function getLevels() { - let levels = walkUpWindows(); - let ancestors = getAncestorOrigins(); - - if (ancestors) { - for (let i = 0, l = ancestors.length; i < l; i++) { - levels[i].ancestor = ancestors[i]; - } - } - return levels; - } - /** * This function would return a read-only array of hostnames for all the parent frames. * win.location.ancestorOrigins is only supported in webkit browsers. For non-webkit browsers it will return undefined. + * + * @param {Window} win Window object * @returns {(undefined|Array)} Ancestor origins or undefined */ - function getAncestorOrigins() { + function getAncestorOrigins(win) { try { if (!win.location.ancestorOrigins) { return; } + return win.location.ancestorOrigins; } catch (e) { // Ignore error } } - /** - * This function would try to get referer and urls for all parent frames in case of win.location.ancestorOrigins undefined. - * @param {Array} levels - * @returns {Object} urls for all parent frames and top most detected referer url - */ - function getPubUrlStack(levels) { - let stack = []; - let defUrl = null; - let frameLocation = null; - let prevFrame = null; - let prevRef = null; - let ancestor = null; - let detectedRefererUrl = null; - - let i; - for (i = levels.length - 1; i >= 0; i--) { - try { - frameLocation = levels[i].location; - } catch (e) { - // Ignore error - } - - if (frameLocation) { - stack.push(frameLocation); - if (!detectedRefererUrl) { - detectedRefererUrl = frameLocation; - } - } else if (i !== 0) { - prevFrame = levels[i - 1]; - try { - prevRef = prevFrame.referrer; - ancestor = prevFrame.ancestor; - } catch (e) { - // Ignore error - } - - if (prevRef) { - stack.push(prevRef); - if (!detectedRefererUrl) { - detectedRefererUrl = prevRef; - } - } else if (ancestor) { - stack.push(ancestor); - if (!detectedRefererUrl) { - detectedRefererUrl = ancestor; - } - } else { - stack.push(defUrl); - } - } else { - stack.push(defUrl); - } - } - return { - stack, - detectedRefererUrl - }; - } - /** * This function returns canonical URL which refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage + * * @param {Object} doc document + * @returns {string|null} */ function getCanonicalUrl(doc) { try { - let element = doc.querySelector("link[rel='canonical']"); + const element = doc.querySelector("link[rel='canonical']"); + if (element !== null) { return element.href; } } catch (e) { + // Ignore error } - return null; - } - /** - * Walk up to the top of the window to detect origin, number of iframes, ancestor origins and canonical url - */ - function walkUpWindows() { - let acc = []; - let currentWindow; - do { - try { - currentWindow = currentWindow ? currentWindow.parent : win; - try { - let isTop = (currentWindow == win.top); - let refData = { - referrer: currentWindow.document.referrer || null, - location: currentWindow.location.href || null, - isTop - } - if (isTop) { - refData = Object.assign(refData, { - canonicalUrl: getCanonicalUrl(currentWindow.document) - }) - } - acc.push(refData); - } catch (e) { - acc.push({ - referrer: null, - location: null, - isTop: (currentWindow == win.top) - }); - logWarn('Trying to access cross domain iframe. Continuing without referrer and location'); - } - } catch (e) { - acc.push({ - referrer: null, - location: null, - isTop: false - }); - return acc; - } - } while (currentWindow != win.top); - return acc; + return null; } /** @@ -170,31 +65,114 @@ export function detectReferer(win) { */ /** - * Get referer info + * Walk up the windows to get the origin stack and best available referrer, canonical URL, etc. + * * @returns {refererInfo} */ function refererInfo() { - try { - let levels = getLevels(); - let numIframes = levels.length - 1; - let reachedTop = (levels[numIframes].location !== null || - (numIframes > 0 && levels[numIframes - 1].referrer !== null)); - let stackInfo = getPubUrlStack(levels); - let canonicalUrl; - if (levels[levels.length - 1].canonicalUrl) { - canonicalUrl = levels[levels.length - 1].canonicalUrl; + const stack = []; + const ancestors = getAncestorOrigins(win); + let currentWindow; + let bestReferrer; + let bestCanonicalUrl; + let reachedTop = false; + let level = 0; + let valuesFromAmp = false; + let inAmpFrame = false; + + do { + const previousWindow = currentWindow; + const wasInAmpFrame = inAmpFrame; + let currentLocation; + let crossOrigin = false; + let foundReferrer = null; + + inAmpFrame = false; + currentWindow = currentWindow ? currentWindow.parent : win; + + try { + currentLocation = currentWindow.location.href || null; + } catch (e) { + crossOrigin = true; } - return { - referer: stackInfo.detectedRefererUrl, - reachedTop, - numIframes, - stack: stackInfo.stack, - canonicalUrl - }; - } catch (e) { - // Ignore error - } + if (crossOrigin) { + if (wasInAmpFrame) { + const context = previousWindow.context; + + try { + foundReferrer = context.sourceUrl; + bestReferrer = foundReferrer; + + valuesFromAmp = true; + + if (currentWindow === win.top) { + reachedTop = true; + } + + if (context.canonicalUrl) { + bestCanonicalUrl = context.canonicalUrl; + } + } catch (e) { /* Do nothing */ } + } else { + logWarn('Trying to access cross domain iframe. Continuing without referrer and location'); + + try { + const referrer = previousWindow.document.referrer; + + if (referrer) { + foundReferrer = referrer; + + if (currentWindow === win.top) { + reachedTop = true; + } + } + } catch (e) { /* Do nothing */ } + + if (!foundReferrer && ancestors && ancestors[level - 1]) { + foundReferrer = ancestors[level - 1]; + } + + if (foundReferrer && !valuesFromAmp) { + bestReferrer = foundReferrer; + } + } + } else { + if (currentLocation) { + foundReferrer = currentLocation; + bestReferrer = foundReferrer; + valuesFromAmp = false; + + if (currentWindow === win.top) { + reachedTop = true; + + const canonicalUrl = getCanonicalUrl(currentWindow.document); + + if (canonicalUrl) { + bestCanonicalUrl = canonicalUrl; + } + } + } + + if (currentWindow.context && currentWindow.context.sourceUrl) { + inAmpFrame = true; + } + } + + stack.push(foundReferrer); + level++; + } while (currentWindow !== win.top); + + stack.reverse(); + + return { + referer: bestReferrer || null, + reachedTop, + isAmp: valuesFromAmp, + numIframes: level - 1, + stack, + canonicalUrl: bestCanonicalUrl || null + }; } return refererInfo; diff --git a/test/spec/refererDetection_spec.js b/test/spec/refererDetection_spec.js index 90892d915fe..46990ae841f 100644 --- a/test/spec/refererDetection_spec.js +++ b/test/spec/refererDetection_spec.js @@ -1,87 +1,357 @@ import { detectReferer } from 'src/refererDetection.js'; import { expect } from 'chai'; -var mocks = { - createFakeWindow: function (referrer, href) { - return { - document: { - referrer: referrer - }, - location: { - href: href, - // TODO: add ancestorOrigins to increase test coverage - }, - parent: null, - top: null - }; +/** + * Build a walkable linked list of window-like objects for testing. + * + * @param {Array} urls Array of URL strings starting from the top window. + * @param {string} [topReferrer] + * @param {string} [canonicalUrl] + * @param {boolean} [ancestorOrigins] + * @returns {Object} + */ +function buildWindowTree(urls, topReferrer = '', canonicalUrl = null, ancestorOrigins = false) { + /** + * Find the origin from a given fully-qualified URL. + * + * @param {string} url The fully qualified URL + * @returns {string|null} + */ + function getOrigin(url) { + const originRegex = new RegExp('^(https?://[^/]+/?)'); + + const result = originRegex.exec(url); + + if (result && result[0]) { + return result[0]; + } + + return null; } -} -describe('referer detection', () => { - it('should return referer details in nested friendly iframes', function() { - // Fake window object to test friendly iframes - // - Main page http://example.com/page.html - // - - Iframe1 http://example.com/iframe1.html - // - - - Iframe2 http://example.com/iframe2.html - let mockIframe2WinObject = mocks.createFakeWindow('http://example.com/iframe1.html', 'http://example.com/iframe2.html'); - let mockIframe1WinObject = mocks.createFakeWindow('http://example.com/page.html', 'http://example.com/iframe1.html'); - let mainWinObject = mocks.createFakeWindow('http://example.com/page.html', 'http://example.com/page.html'); - mainWinObject.document.querySelector = function() { - return { - href: 'http://prebid.org' + let previousWindow; + const myOrigin = getOrigin(urls[urls.length - 1]); + + const windowList = urls.map((url, index) => { + const theirOrigin = getOrigin(url), + sameOrigin = (myOrigin === theirOrigin); + + const win = {}; + + if (sameOrigin) { + win.location = { + href: url + }; + + if (ancestorOrigins) { + win.location.ancestorOrigins = urls.slice(0, index).reverse().map(getOrigin); + } + + if (index === 0) { + win.document = { + referrer: topReferrer + }; + + if (canonicalUrl) { + win.document.querySelector = function(selector) { + if (selector === "link[rel='canonical']") { + return { + href: canonicalUrl + }; + } + + return null; + }; + } + } else { + win.document = { + referrer: urls[index - 1] + }; } } - mockIframe2WinObject.parent = mockIframe1WinObject; - mockIframe2WinObject.top = mainWinObject; - mockIframe1WinObject.parent = mainWinObject; - mockIframe1WinObject.top = mainWinObject; - mainWinObject.top = mainWinObject; - - const getRefererInfo = detectReferer(mockIframe2WinObject); - let result = getRefererInfo(); - let expectedResult = { - referer: 'http://example.com/page.html', - reachedTop: true, - numIframes: 2, - stack: [ - 'http://example.com/page.html', - 'http://example.com/iframe1.html', - 'http://example.com/iframe2.html' - ], - canonicalUrl: 'http://prebid.org' - }; - expect(result).to.deep.equal(expectedResult); + + previousWindow = win; + + return win; + }); + + const topWindow = windowList[0]; + + previousWindow = null; + + windowList.forEach((win) => { + win.top = topWindow; + win.parent = previousWindow || topWindow; + previousWindow = win; + }); + + return windowList[windowList.length - 1]; +} + +describe('Referer detection', () => { + describe('Non cross-origin scenarios', () => { + describe('No iframes', () => { + it('Should return the current window location and no canonical URL', () => { + const testWindow = buildWindowTree(['https://example.com/some/page'], 'https://othersite.com/'), + result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['https://example.com/some/page'], + canonicalUrl: null + }); + }); + + it('Should return the current window location and a canonical URL', () => { + const testWindow = buildWindowTree(['https://example.com/some/page'], 'https://othersite.com/', 'https://example.com/canonical/page'), + result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['https://example.com/some/page'], + canonicalUrl: 'https://example.com/canonical/page' + }); + }); + }); + + describe('Friendly iframes', () => { + it('Should return the top window location and no canonical URL', () => { + const testWindow = buildWindowTree(['https://example.com/some/page', 'https://example.com/other/page', 'https://example.com/third/page'], 'https://othersite.com/'), + result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page', + reachedTop: true, + isAmp: false, + numIframes: 2, + stack: [ + 'https://example.com/some/page', + 'https://example.com/other/page', + 'https://example.com/third/page' + ], + canonicalUrl: null + }); + }); + + it('Should return the top window location and a canonical URL', () => { + const testWindow = buildWindowTree(['https://example.com/some/page', 'https://example.com/other/page', 'https://example.com/third/page'], 'https://othersite.com/', 'https://example.com/canonical/page'), + result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page', + reachedTop: true, + isAmp: false, + numIframes: 2, + stack: [ + 'https://example.com/some/page', + 'https://example.com/other/page', + 'https://example.com/third/page' + ], + canonicalUrl: 'https://example.com/canonical/page' + }); + }); + }); }); - it('should return referer details in nested cross domain iframes', function() { - // Fake window object to test cross domain iframes. - // - Main page http://example.com/page.html - // - - Iframe1 http://aaa.com/iframe1.html - // - - - Iframe2 http://bbb.com/iframe2.html - let mockIframe2WinObject = mocks.createFakeWindow('http://aaa.com/iframe1.html', 'http://bbb.com/iframe2.html'); - // Sinon cannot throw exception when accessing a propery so passing null to create cross domain - // environment for refererDetection module - let mockIframe1WinObject = mocks.createFakeWindow(null, null); - let mainWinObject = mocks.createFakeWindow(null, null); - mockIframe2WinObject.parent = mockIframe1WinObject; - mockIframe2WinObject.top = mainWinObject; - mockIframe1WinObject.parent = mainWinObject; - mockIframe1WinObject.top = mainWinObject; - mainWinObject.top = mainWinObject; - - const getRefererInfo = detectReferer(mockIframe2WinObject); - let result = getRefererInfo(); - let expectedResult = { - referer: 'http://aaa.com/iframe1.html', - reachedTop: false, - numIframes: 2, - stack: [ - null, - 'http://aaa.com/iframe1.html', - 'http://bbb.com/iframe2.html' - ], - canonicalUrl: undefined - }; - expect(result).to.deep.equal(expectedResult); + describe('Cross-origin scenarios', () => { + it('Should return the top URL and no canonical URL with one cross-origin iframe', () => { + const testWindow = buildWindowTree(['https://example.com/some/page', 'https://safe.frame/ad'], 'https://othersite.com/', 'https://canonical.example.com/'), + result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page', + reachedTop: true, + isAmp: false, + numIframes: 1, + stack: [ + 'https://example.com/some/page', + 'https://safe.frame/ad' + ], + canonicalUrl: null + }); + }); + + it('Should return the top URL and no canonical URL with one cross-origin iframe and one friendly iframe', () => { + const testWindow = buildWindowTree(['https://example.com/some/page', 'https://safe.frame/ad', 'https://safe.frame/ad'], 'https://othersite.com/', 'https://canonical.example.com/'), + result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page', + reachedTop: true, + isAmp: false, + numIframes: 2, + stack: [ + 'https://example.com/some/page', + 'https://safe.frame/ad', + 'https://safe.frame/ad' + ], + canonicalUrl: null + }); + }); + + it('Should return the second iframe location with three cross-origin windows and no ancessorOrigins', () => { + const testWindow = buildWindowTree(['https://example.com/some/page', 'https://safe.frame/ad', 'https://otherfr.ame/ad'], 'https://othersite.com/', 'https://canonical.example.com/'), + result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://safe.frame/ad', + reachedTop: false, + isAmp: false, + numIframes: 2, + stack: [ + null, + 'https://safe.frame/ad', + 'https://otherfr.ame/ad' + ], + canonicalUrl: null + }); + }); + + it('Should return the top window origin with three cross-origin windows with ancessorOrigins', () => { + const testWindow = buildWindowTree(['https://example.com/some/page', 'https://safe.frame/ad', 'https://otherfr.ame/ad'], 'https://othersite.com/', 'https://canonical.example.com/', true), + result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/', + reachedTop: false, + isAmp: false, + numIframes: 2, + stack: [ + 'https://example.com/', + 'https://safe.frame/ad', + 'https://otherfr.ame/ad' + ], + canonicalUrl: null + }); + }); + }); + + describe('Cross-origin AMP page scenarios', () => { + it('Should return the AMP page source and canonical URLs in an amp-ad iframe for a non-cached AMP page', () => { + const testWindow = buildWindowTree(['https://example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad']); + + testWindow.context = { + sourceUrl: 'https://example.com/some/page/amp/', + canonicalUrl: 'https://example.com/some/page/' + }; + + const result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page/amp/', + reachedTop: true, + isAmp: true, + numIframes: 1, + stack: [ + 'https://example.com/some/page/amp/', + 'https://ad-iframe.ampproject.org/ad' + ], + canonicalUrl: 'https://example.com/some/page/' + }); + }); + + it('Should return the AMP page source and canonical URLs in an amp-ad iframe for a cached AMP page on top', () => { + const testWindow = buildWindowTree(['https://example-com.amp-cache.example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad']); + + testWindow.context = { + sourceUrl: 'https://example.com/some/page/amp/', + canonicalUrl: 'https://example.com/some/page/' + }; + + const result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page/amp/', + reachedTop: true, + isAmp: true, + numIframes: 1, + stack: [ + 'https://example.com/some/page/amp/', + 'https://ad-iframe.ampproject.org/ad' + ], + canonicalUrl: 'https://example.com/some/page/' + }); + }); + + describe('Cached AMP page in iframed search result', () => { + it('Should return the AMP source and canonical URLs but with a null top-level stack location Without ancesorOrigins', () => { + const testWindow = buildWindowTree(['https://google.com/amp/example-com/some/page/amp/', 'https://example-com.amp-cache.example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad']); + + testWindow.context = { + sourceUrl: 'https://example.com/some/page/amp/', + canonicalUrl: 'https://example.com/some/page/' + }; + + const result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page/amp/', + reachedTop: false, + isAmp: true, + numIframes: 2, + stack: [ + null, + 'https://example.com/some/page/amp/', + 'https://ad-iframe.ampproject.org/ad' + ], + canonicalUrl: 'https://example.com/some/page/' + }); + }); + + it('Should return the AMP source and canonical URLs and include the top window origin in the stack with ancesorOrigins', () => { + const testWindow = buildWindowTree(['https://google.com/amp/example-com/some/page/amp/', 'https://example-com.amp-cache.example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad'], null, null, true); + + testWindow.context = { + sourceUrl: 'https://example.com/some/page/amp/', + canonicalUrl: 'https://example.com/some/page/' + }; + + const result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page/amp/', + reachedTop: false, + isAmp: true, + numIframes: 2, + stack: [ + 'https://google.com/', + 'https://example.com/some/page/amp/', + 'https://ad-iframe.ampproject.org/ad' + ], + canonicalUrl: 'https://example.com/some/page/' + }); + }); + + it('Should return the AMP source and canonical URLs and include the top window origin in the stack with ancesorOrigins and a friendly iframe under the amp-ad iframe', () => { + const testWindow = buildWindowTree(['https://google.com/amp/example-com/some/page/amp/', 'https://example-com.amp-cache.example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad', 'https://ad-iframe.ampproject.org/ad'], null, null, true); + + testWindow.parent.context = { + sourceUrl: 'https://example.com/some/page/amp/', + canonicalUrl: 'https://example.com/some/page/' + }; + + const result = detectReferer(testWindow)(); + + expect(result).to.deep.equal({ + referer: 'https://example.com/some/page/amp/', + reachedTop: false, + isAmp: true, + numIframes: 3, + stack: [ + 'https://google.com/', + 'https://example.com/some/page/amp/', + 'https://ad-iframe.ampproject.org/ad', + 'https://ad-iframe.ampproject.org/ad' + ], + canonicalUrl: 'https://example.com/some/page/' + }); + }); + }); }); }); From 6ea04f97acb0403f31214eaa5366d811dd98aaac Mon Sep 17 00:00:00 2001 From: yuvalgg <57989766+yuvalgg@users.noreply.github.com> Date: Thu, 24 Sep 2020 12:40:47 +0300 Subject: [PATCH 009/190] Intentiq id add url params (#5771) * Add new url params from config * Add intentIqIdSystem_spec.js tests class --- modules/intentIqIdSystem.js | 5 +- test/spec/modules/intentIqIdSystem_spec.js | 143 +++++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 test/spec/modules/intentIqIdSystem_spec.js diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 242b227f89f..88da7d44481 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -40,7 +40,10 @@ export const intentIqIdSubmodule = { } // use protocol relative urls for http or https - const url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; + let url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; + url += configParams.pcid ? '&pcid=' + encodeURIComponent(configParams.pcid) : ''; + url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : ''; + const resp = function (callback) { const callbacks = { success: response => { diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js new file mode 100644 index 00000000000..2dccde18855 --- /dev/null +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -0,0 +1,143 @@ +import { expect } from 'chai'; +import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; +import * as utils from 'src/utils.js'; +import {server} from 'test/mocks/xhr.js'; + +const partner = 10; +const pai = '11'; +const pcid = '12'; +const defaultConfigParams = {partner: partner}; +const paiConfigParams = {partner: partner, pai: pai}; +const pcidConfigParams = {partner: partner, pcid: pcid}; +const allConfigParams = {partner: partner, pai: pai, pcid: pcid}; +const responseHeader = {'Content-Type': 'application/json'} + +describe('IntentIQ tests', function () { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + }); + + it('should log an error if no configParams were passed when getId', function () { + let submodule = intentIqIdSubmodule.getId(); + expect(logErrorStub.calledOnce).to.be.true; + expect(submodule).to.be.undefined; + }); + + it('should log an error if partner configParam was not passed when getId', function () { + let submodule = intentIqIdSubmodule.getId({}); + expect(logErrorStub.calledOnce).to.be.true; + expect(submodule).to.be.undefined; + }); + + it('should log an error if partner configParam was not a numeric value', function () { + let submodule = intentIqIdSubmodule.getId({partner: '10'}); + expect(logErrorStub.calledOnce).to.be.true; + expect(submodule).to.be.undefined; + }); + + it('should call the IntentIQ endpoint with only partner', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call the IntentIQ endpoint with only partner, pai', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(paiConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pai=11'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call the IntentIQ endpoint with only partner, pcid', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(pcidConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call the IntentIQ endpoint with partner, pcid, pai', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should not throw Uncaught TypeError when IntentIQ endpoint returns empty response', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1'); + request.respond( + 204, + responseHeader, + '' + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(request.response).to.equal(''); + expect(logErrorStub.calledOnce).to.not.be.true; + }); + + it('should log an error and continue to callback if ajax request errors', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1'); + request.respond( + 503, + responseHeader, + 'Unavailable' + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should log an error and continue to callback if ajax request errors', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1'); + request.respond( + 503, + responseHeader, + 'Unavailable' + ); + expect(callBackSpy.calledOnce).to.be.true; + }); +}); From 7f2d81a87bebdd2d2d03efbba51726116f8f069c Mon Sep 17 00:00:00 2001 From: Drilon Kastrati Date: Thu, 24 Sep 2020 12:16:38 +0200 Subject: [PATCH 010/190] added instream video ad support (#5766) * added adapters for gjirafa and malltv * interpretResponse fix for empty result * updated testing propertyId and placementId * added instream video ad support * Single request for multple bids --- modules/gjirafaBidAdapter.js | 64 ++++++++++++++------- modules/gjirafaBidAdapter.md | 14 ++--- modules/malltvBidAdapter.js | 64 ++++++++++++++------- modules/malltvBidAdapter.md | 14 ++--- test/spec/modules/gjirafaBidAdapter_spec.js | 12 +++- test/spec/modules/malltvBidAdapter_spec.js | 10 +++- 6 files changed, 109 insertions(+), 69 deletions(-) diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js index ca7fb4af32d..48496b52c05 100644 --- a/modules/gjirafaBidAdapter.js +++ b/modules/gjirafaBidAdapter.js @@ -1,4 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'gjirafa'; const ENDPOINT_URL = 'https://central.gjirafa.com/bid'; @@ -7,6 +8,7 @@ const SIZE_SEPARATOR = ';'; export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -23,31 +25,48 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - let response = validBidRequests.map(bidRequest => { - let sizes = generateSizeParam(bidRequest.sizes); - let propertyId = bidRequest.params.propertyId; - let placementId = bidRequest.params.placementId; + let propertyId = ''; + let pageViewGuid = ''; + let storageId = ''; + let bidderRequestId = ''; + let url = ''; + let contents = []; + + let placements = validBidRequests.map(bidRequest => { + if (!propertyId) { propertyId = bidRequest.params.propertyId; } + if (!pageViewGuid && bidRequest.params) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } + if (!storageId && bidRequest.params) { storageId = bidRequest.params.storageId || ''; } + if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } + if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents } + let adUnitId = bidRequest.adUnitCode; - let pageViewGuid = bidRequest.params.pageViewGuid || ''; - let contents = bidRequest.params.contents || []; - const body = { + let placementId = bidRequest.params.placementId; + let sizes = generateSizeParam(bidRequest.sizes); + + return { sizes: sizes, adUnitId: adUnitId, placementId: placementId, - propertyId: propertyId, - pageViewGuid: pageViewGuid, - url: bidderRequest ? bidderRequest.refererInfo.referer : '', - requestid: bidRequest.bidderRequestId, bidid: bidRequest.bidId, - contents: contents - }; - return { - method: 'POST', - url: ENDPOINT_URL, - data: body }; }); - return response + + let body = { + propertyId: propertyId, + pageViewGuid: pageViewGuid, + storageId: storageId, + url: url, + requestid: bidderRequestId, + placements: placements, + contents: contents + } + + return [{ + method: 'POST', + url: ENDPOINT_URL, + data: body + }]; }, /** * Unpack the response from the server into a list of bids. @@ -55,13 +74,12 @@ export const spec = { * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse, bidRequest) { - window.adnResponse = serverResponse; + interpretResponse: function (serverResponse) { const responses = serverResponse.body; const bidResponses = []; for (var i = 0; i < responses.length; i++) { const bidResponse = { - requestId: bidRequest.data.bidid, + requestId: responses[i].BidId, cpm: responses[i].CPM, width: responses[i].Width, height: responses[i].Height, @@ -70,7 +88,9 @@ export const spec = { netRevenue: responses[i].NetRevenue, ttl: responses[i].TTL, referrer: responses[i].Referrer, - ad: responses[i].Ad + ad: responses[i].Ad, + vastUrl: responses[i].VastUrl, + mediaType: responses[i].MediaType }; bidResponses.push(bidResponse); } diff --git a/modules/gjirafaBidAdapter.md b/modules/gjirafaBidAdapter.md index 53d3a76c5ed..deb06e74a27 100644 --- a/modules/gjirafaBidAdapter.md +++ b/modules/gjirafaBidAdapter.md @@ -28,22 +28,16 @@ var adUnits = [ { code: 'test-div', mediaTypes: { - banner: { - sizes: [[300, 250]] - } + video: { + context: 'instream' + } }, bids: [ { bidder: 'gjirafa', params: { propertyId: '105227', - placementId: '846848', - contents: [ //optional - { - type: 'article', - id: '123' - } - ] + placementId: '846836' } } ] diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js index 4cdb5d45328..846935a5522 100644 --- a/modules/malltvBidAdapter.js +++ b/modules/malltvBidAdapter.js @@ -1,4 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'malltv'; const ENDPOINT_URL = 'https://central.mall.tv/bid'; @@ -7,6 +8,7 @@ const SIZE_SEPARATOR = ';'; export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -23,31 +25,48 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - let response = validBidRequests.map(bidRequest => { - let sizes = generateSizeParam(bidRequest.sizes); - let propertyId = bidRequest.params.propertyId; - let placementId = bidRequest.params.placementId; + let propertyId = ''; + let pageViewGuid = ''; + let storageId = ''; + let bidderRequestId = ''; + let url = ''; + let contents = []; + + let placements = validBidRequests.map(bidRequest => { + if (!propertyId) { propertyId = bidRequest.params.propertyId; } + if (!pageViewGuid && bidRequest.params) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } + if (!storageId && bidRequest.params) { storageId = bidRequest.params.storageId || ''; } + if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } + if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents } + let adUnitId = bidRequest.adUnitCode; - let pageViewGuid = bidRequest.params.pageViewGuid || ''; - let contents = bidRequest.params.contents || []; - const body = { + let placementId = bidRequest.params.placementId; + let sizes = generateSizeParam(bidRequest.sizes); + + return { sizes: sizes, adUnitId: adUnitId, placementId: placementId, - propertyId: propertyId, - pageViewGuid: pageViewGuid, - url: bidderRequest ? bidderRequest.refererInfo.referer : '', - requestid: bidRequest.bidderRequestId, bidid: bidRequest.bidId, - contents: contents - }; - return { - method: 'POST', - url: ENDPOINT_URL, - data: body }; }); - return response + + let body = { + propertyId: propertyId, + pageViewGuid: pageViewGuid, + storageId: storageId, + url: url, + requestid: bidderRequestId, + placements: placements, + contents: contents + } + + return [{ + method: 'POST', + url: ENDPOINT_URL, + data: body + }]; }, /** * Unpack the response from the server into a list of bids. @@ -55,13 +74,12 @@ export const spec = { * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse, bidRequest) { - window.adnResponse = serverResponse; + interpretResponse: function (serverResponse) { const responses = serverResponse.body; const bidResponses = []; for (var i = 0; i < responses.length; i++) { const bidResponse = { - requestId: bidRequest.data.bidid, + requestId: responses[i].BidId, cpm: responses[i].CPM, width: responses[i].Width, height: responses[i].Height, @@ -70,7 +88,9 @@ export const spec = { netRevenue: responses[i].NetRevenue, ttl: responses[i].TTL, referrer: responses[i].Referrer, - ad: responses[i].Ad + ad: responses[i].Ad, + vastUrl: responses[i].VastUrl, + mediaType: responses[i].MediaType }; bidResponses.push(bidResponse); } diff --git a/modules/malltvBidAdapter.md b/modules/malltvBidAdapter.md index 72db0cef6c7..3d419fa0916 100644 --- a/modules/malltvBidAdapter.md +++ b/modules/malltvBidAdapter.md @@ -28,22 +28,16 @@ var adUnits = [ { code: 'test-div', mediaTypes: { - banner: { - sizes: [[300, 250], [300, 300]] - } + video: { + context: 'instream' + } }, bids: [ { bidder: 'malltv', params: { propertyId: '105134', - placementId: '846832', - contents: [ //optional - { - type: 'video', - id: '123' - } - ] + placementId: '846841' } } ] diff --git a/test/spec/modules/gjirafaBidAdapter_spec.js b/test/spec/modules/gjirafaBidAdapter_spec.js index 566b1243f62..db9b82e0a10 100644 --- a/test/spec/modules/gjirafaBidAdapter_spec.js +++ b/test/spec/modules/gjirafaBidAdapter_spec.js @@ -31,7 +31,7 @@ describe('gjirafaAdapterTest', () => { })).to.equal(false); }); - it('bidRequest without propertyId orplacementId', () => { + it('bidRequest without propertyId or placementId', () => { expect(spec.isBidRequestValid({ bidder: 'gjirafa', params: { @@ -80,7 +80,11 @@ describe('gjirafaAdapterTest', () => { it('bidRequest sizes', () => { const requests = spec.buildRequests(bidRequests); - expect(requests[0].data.sizes).to.equal('728x90'); + requests.forEach(function (requestItem) { + expect(requestItem.data.placements).to.exist; + expect(requestItem.data.placements.length).to.equal(1); + expect(requestItem.data.placements[0].sizes).to.equal('728x90'); + }); }); }); @@ -128,7 +132,9 @@ describe('gjirafaAdapterTest', () => { 'netRevenue', 'ttl', 'referrer', - 'ad' + 'ad', + 'vastUrl', + 'mediaType' ]; let resultKeys = Object.keys(result[0]); diff --git a/test/spec/modules/malltvBidAdapter_spec.js b/test/spec/modules/malltvBidAdapter_spec.js index 10161b319c5..e1e9ad867e7 100644 --- a/test/spec/modules/malltvBidAdapter_spec.js +++ b/test/spec/modules/malltvBidAdapter_spec.js @@ -80,7 +80,11 @@ describe('malltvAdapterTest', () => { it('bidRequest sizes', () => { const requests = spec.buildRequests(bidRequests); - expect(requests[0].data.sizes).to.equal('300x250'); + requests.forEach(function (requestItem) { + expect(requestItem.data.placements).to.exist; + expect(requestItem.data.placements.length).to.equal(1); + expect(requestItem.data.placements[0].sizes).to.equal('300x250'); + }); }); }); @@ -128,7 +132,9 @@ describe('malltvAdapterTest', () => { 'netRevenue', 'ttl', 'referrer', - 'ad' + 'ad', + 'vastUrl', + 'mediaType' ]; let resultKeys = Object.keys(result[0]); From 80fc5b6c16fb32ed84691d3119e4d01d2c9061d7 Mon Sep 17 00:00:00 2001 From: fgcloutier Date: Thu, 24 Sep 2020 13:14:23 +0200 Subject: [PATCH 011/190] feat(sublimeBidAdapter): updating sublimeBidAdapter module (#5726) - handle new notifyId parameter; - bumping version to 0.6.0. --- modules/sublimeBidAdapter.js | 11 +++++++---- modules/sublimeBidAdapter.md | 9 ++++++--- test/spec/modules/sublimeBidAdapter_spec.js | 6 +++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/modules/sublimeBidAdapter.js b/modules/sublimeBidAdapter.js index 1f8cb59f442..e9f7cf19033 100644 --- a/modules/sublimeBidAdapter.js +++ b/modules/sublimeBidAdapter.js @@ -9,7 +9,7 @@ const DEFAULT_CURRENCY = 'EUR'; const DEFAULT_PROTOCOL = 'https'; const DEFAULT_TTL = 600; const SUBLIME_ANTENNA = 'antenna.ayads.co'; -const SUBLIME_VERSION = '0.5.2'; +const SUBLIME_VERSION = '0.6.0'; /** * Debug log message @@ -23,7 +23,8 @@ export function log(msg, obj) { // Default state export const state = { zoneId: '', - transactionId: '' + transactionId: '', + notifyId: '' }; /** @@ -47,8 +48,8 @@ export function sendEvent(eventName) { z: state.zoneId, e: eventName, src: 'pa', - puid: state.transactionId, - trId: state.transactionId, + puid: state.transactionId || state.notifyId, + trId: state.transactionId || state.notifyId, ver: SUBLIME_VERSION, }; @@ -101,6 +102,7 @@ function buildRequests(validBidRequests, bidderRequest) { setState({ transactionId: bid.transactionId, + notifyId: bid.params.notifyId, zoneId: bid.params.zoneId, debug: bid.params.debug || false, }); @@ -117,6 +119,7 @@ function buildRequests(validBidRequests, bidderRequest) { h: size[1], })), transactionId: bid.transactionId, + notifyId: bid.params.notifyId, zoneId: bid.params.zoneId, }; diff --git a/modules/sublimeBidAdapter.md b/modules/sublimeBidAdapter.md index e57f4a1fdb0..5cd1c95b682 100644 --- a/modules/sublimeBidAdapter.md +++ b/modules/sublimeBidAdapter.md @@ -9,7 +9,7 @@ Maintainer: pbjs@sublimeskinz.com # Description Connects to Sublime for bids. -Sublime bid adapter supports Skinz and M-Skinz formats. +Sublime bid adapter supports Skinz. # Nota Bene @@ -53,10 +53,13 @@ var adUnits = [{ bids: [{ bidder: 'sublime', params: { - zoneId: + zoneId: , + notifyId: } }] }]; ``` -Where you replace `` by your Sublime Zone id +Where you replace: +- `` by your Sublime Zone id; +- `` by your Sublime Notify id diff --git a/test/spec/modules/sublimeBidAdapter_spec.js b/test/spec/modules/sublimeBidAdapter_spec.js index ae9e293a757..008f24730bc 100644 --- a/test/spec/modules/sublimeBidAdapter_spec.js +++ b/test/spec/modules/sublimeBidAdapter_spec.js @@ -149,7 +149,7 @@ describe('Sublime Adapter', function() { currency: 'USD', netRevenue: true, ttl: 600, - pbav: '0.5.2', + pbav: '0.6.0', ad: '', }, ]; @@ -191,7 +191,7 @@ describe('Sublime Adapter', function() { netRevenue: true, ttl: 600, ad: '', - pbav: '0.5.2', + pbav: '0.6.0', }; expect(result[0]).to.deep.equal(expectedResponse); @@ -241,7 +241,7 @@ describe('Sublime Adapter', function() { netRevenue: true, ttl: 600, ad: '', - pbav: '0.5.2', + pbav: '0.6.0', }; expect(result[0]).to.deep.equal(expectedResponse); From 27fe52aacd95dcc9548da646976ed6c7539914da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Alberto=20Polo=20Garc=C3=ADa?= Date: Thu, 24 Sep 2020 15:44:47 +0200 Subject: [PATCH 012/190] Add GVL ID and bidder code to CriteoId module (#5781) * Add GVL ID and bidder code to CriteoId module * Add gvlid as property to CriteoIdSubmodule Co-authored-by: Jesus Alberto Polo Garcia --- modules/criteoIdSystem.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index c44f0c843ae..017194d0e86 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -11,7 +11,9 @@ import { getRefererInfo } from '../src/refererDetection.js' import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; -export const storage = getStorageManager(); +const gvlid = 91; +const bidderCode = 'criteo'; +export const storage = getStorageManager(gvlid, bidderCode); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; @@ -111,7 +113,8 @@ export const criteoIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'criteo', + name: bidderCode, + gvlid: gvlid, /** * decode the stored id value for passing to bid requests * @function From 3612308e6fd679ba4e94ac4419da157f11e8c19e Mon Sep 17 00:00:00 2001 From: BrightMountainMedia <69471268+BrightMountainMediaInc@users.noreply.github.com> Date: Thu, 24 Sep 2020 20:25:40 +0530 Subject: [PATCH 013/190] Update BrightMountainMedia cookie sync URL (#5740) --- modules/brightMountainMediaBidAdapter.js | 2 +- test/spec/modules/brightMountainMediaBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js index 5a285be71c0..aa1076e798a 100644 --- a/modules/brightMountainMediaBidAdapter.js +++ b/modules/brightMountainMediaBidAdapter.js @@ -79,7 +79,7 @@ export const spec = { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: 'https://console.brightmountainmedia.com:4444/cookieSync' + url: 'https://console.brightmountainmedia.com:8443/cookieSync' }]; } }, diff --git a/test/spec/modules/brightMountainMediaBidAdapter_spec.js b/test/spec/modules/brightMountainMediaBidAdapter_spec.js index 3b7e46e55a7..f6312253722 100644 --- a/test/spec/modules/brightMountainMediaBidAdapter_spec.js +++ b/test/spec/modules/brightMountainMediaBidAdapter_spec.js @@ -133,7 +133,7 @@ describe('brightMountainMediaBidAdapter_spec', function () { expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.exist; expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.exist; expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe') - expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal('https://console.brightmountainmedia.com:4444/cookieSync') + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal('https://console.brightmountainmedia.com:8443/cookieSync') }); }); }); From f1ea594016d761c66ddd3ed0a190c9f1bb501927 Mon Sep 17 00:00:00 2001 From: Scott Date: Thu, 24 Sep 2020 18:06:47 +0200 Subject: [PATCH 014/190] Convert id5id to an object to support passing additional data points to platforms (#5756) * move id5id to an object to support passing linkType and other data in the future * update bid adapters supporting the ID5 ID to use the new object instead of a string * remove `.only` from test --- modules/adheseBidAdapter.js | 4 +- modules/adxcgBidAdapter.js | 4 +- modules/avocetBidAdapter.js | 4 +- modules/colossussspBidAdapter.js | 2 +- modules/districtmDMXBidAdapter.js | 2 +- modules/gamoshiBidAdapter.js | 2 +- modules/gridBidAdapter.js | 4 +- modules/id5IdSystem.js | 17 +++++++- modules/livewrappedBidAdapter.js | 2 +- modules/openxBidAdapter.js | 3 ++ modules/ozoneBidAdapter.js | 8 +++- modules/pulsepointBidAdapter.js | 2 +- modules/richaudienceBidAdapter.js | 2 +- modules/spotxBidAdapter.js | 7 ++-- modules/userId/eids.js | 10 ++++- modules/vidazooBidAdapter.js | 3 ++ modules/visxBidAdapter.js | 4 +- test/spec/modules/adheseBidAdapter_spec.js | 2 +- test/spec/modules/adxcgBidAdapter_spec.js | 2 +- test/spec/modules/amxBidAdapter_spec.js | 2 +- test/spec/modules/avocetBidAdapter_spec.js | 4 +- .../modules/colossussspBidAdapter_spec.js | 2 +- .../modules/districtmDmxBidAdapter_spec.js | 4 +- test/spec/modules/eids_spec.js | 42 +++++++++++++++---- test/spec/modules/gamoshiBidAdapter_spec.js | 2 +- test/spec/modules/gridBidAdapter_spec.js | 2 +- test/spec/modules/id5IdSystem_spec.js | 19 +++++---- .../modules/justpremiumBidAdapter_spec.js | 6 ++- .../modules/livewrappedBidAdapter_spec.js | 2 +- .../modules/mediasquareBidAdapter_spec.js | 2 +- test/spec/modules/openxBidAdapter_spec.js | 5 ++- test/spec/modules/ozoneBidAdapter_spec.js | 8 ++-- .../modules/prebidServerBidAdapter_spec.js | 11 ++++- test/spec/modules/pubmaticBidAdapter_spec.js | 10 ++--- .../spec/modules/pulsepointBidAdapter_spec.js | 2 +- .../modules/richaudienceBidAdapter_spec.js | 16 +++++-- .../modules/smartadserverBidAdapter_spec.js | 2 +- test/spec/modules/spotxBidAdapter_spec.js | 5 ++- test/spec/modules/undertoneBidAdapter_spec.js | 4 +- test/spec/modules/userId_spec.js | 12 +++--- test/spec/modules/vidazooBidAdapter_spec.js | 1 + test/spec/modules/visxBidAdapter_spec.js | 2 +- .../yuktamediaAnalyticsAdapter_spec.js | 2 +- 43 files changed, 172 insertions(+), 79 deletions(-) diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 606e56c13d5..80758668a95 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -150,8 +150,8 @@ function getAccount(validBidRequests) { } function getId5Id(validBidRequests) { - if (validBidRequests[0] && validBidRequests[0].userId && validBidRequests[0].userId.id5id) { - return validBidRequests[0].userId.id5id; + if (validBidRequests[0] && validBidRequests[0].userId && validBidRequests[0].userId.id5id && validBidRequests[0].userId.id5id.uid) { + return validBidRequests[0].userId.id5id.uid; } } diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 2d5c64dfe53..e61792288ed 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -170,8 +170,8 @@ export const spec = { beaconParams.tdid = validBidRequests[0].userId.tdid; } - if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.id5id'))) { - beaconParams.id5id = validBidRequests[0].userId.id5id; + if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.id5id.uid'))) { + beaconParams.id5id = validBidRequests[0].userId.id5id.uid; } if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.idl_env'))) { diff --git a/modules/avocetBidAdapter.js b/modules/avocetBidAdapter.js index 1163ac830ba..7a9e5062c0f 100644 --- a/modules/avocetBidAdapter.js +++ b/modules/avocetBidAdapter.js @@ -77,8 +77,8 @@ export const spec = { // ID5 identifier let id5id; - if (bidRequests[0].userId && bidRequests[0].userId.id5id) { - id5id = bidRequests[0].userId.id5id; + if (bidRequests[0].userId && bidRequests[0].userId.id5id && bidRequests[0].userId.id5id.uid) { + id5id = bidRequests[0].userId.id5id.uid; } // Build the avocet ext object diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index baa60a76a0d..a3beb723528 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -102,7 +102,7 @@ export const spec = { if (bid.userId) { getUserId(placement.eids, bid.userId.britepoolid, 'britepool.com'); getUserId(placement.eids, bid.userId.idl_env, 'identityLink'); - getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com') + getUserId(placement.eids, utils.deepAccess(bid, 'userId.id5id.uid'), 'id5-sync.com', utils.deepAccess(bid, 'userId.id5id.ext')); getUserId(placement.eids, bid.userId.tdid, 'adserver.org', { rtiPartner: 'TDID' }); diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index 21f3b7b3586..bcb2bb97210 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -106,7 +106,7 @@ export const spec = { let eids = []; if (bidRequest[0] && bidRequest[0].userId) { bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.idl_env`), 'liveramp.com', 1); - bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.id5id`), 'id5-sync.com', 1); + bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.id5id.uid`), 'id5-sync.com', 1); bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.pubcid`), 'pubcid.org', 1); bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.tdid`), 'adserver.org', 1); bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.criteoId`), 'criteo.com', 1); diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index 1316d74e430..2e09cf55d0a 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { let eids = []; if (bidRequest && bidRequest.userId) { - addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 'ID5ID'); + addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 'ID5ID'); addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID'); } if (eids.length > 0) { diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 32274fed2e6..378fc5a7efe 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -431,8 +431,8 @@ function buildNewRequest(validBidRequests, bidderRequest) { if (userId.tdid) { userExt.unifiedid = userId.tdid; } - if (userId.id5id) { - userExt.id5id = userId.id5id; + if (userId.id5id && userId.id5id.uid) { + userExt.id5id = userId.id5id.uid; } if (userId.digitrustid && userId.digitrustid.data && userId.digitrustid.data.id) { userExt.digitrustid = userId.digitrustid.data.id; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index ae3c7e55863..47064e0a1a9 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -39,14 +39,27 @@ export const id5IdSubmodule = { * @returns {(Object|undefined)} */ decode(value) { + let uid; + let linkType = 0; + if (value && typeof value.ID5ID === 'string') { // don't lose our legacy value from cache - return { 'id5id': value.ID5ID }; + uid = value.ID5ID; } else if (value && typeof value.universal_uid === 'string') { - return { 'id5id': value.universal_uid }; + uid = value.universal_uid; + linkType = value.link_type || linkType; } else { return undefined; } + + return { + 'id5id': { + 'uid': uid, + 'ext': { + 'linkType': linkType + } + } + }; }, /** diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 50e9eea768b..0a5464fd21f 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -277,7 +277,7 @@ function handleEids(bidRequests) { const bidRequest = bidRequests[0]; if (bidRequest && bidRequest.userId) { AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcommon', 1); // Also add this to eids - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1); + AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 1); } if (eids.length > 0) { return {user: {ext: {eids}}}; diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index f4b6288cd55..bbf3d6fdea1 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -281,6 +281,9 @@ function appendUserIdsToQueryParams(queryParams, userIds) { case 'parrableId': queryParams[key] = userIdObjectOrValue.eid; break; + case 'id5id': + queryParams[key] = userIdObjectOrValue.uid; + break; default: queryParams[key] = userIdObjectOrValue; } diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 451ab654c53..4246a39bc69 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -539,7 +539,7 @@ export const spec = { */ findAllUserIds(bidRequest) { var ret = {}; - let searchKeysSingle = ['pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'digitrustid', 'criteortus']; + let searchKeysSingle = ['pubcid', 'tdid', 'parrableId', 'idl_env', 'digitrustid', 'criteortus']; if (bidRequest.hasOwnProperty('userId')) { for (let arrayId in searchKeysSingle) { let key = searchKeysSingle[arrayId]; @@ -551,6 +551,10 @@ export const spec = { if (lipbid) { ret['lipb'] = {'lipbid': lipbid}; } + var id5id = utils.deepAccess(bidRequest.userId, 'id5id.uid'); + if (id5id) { + ret['id5id'] = id5id; + } } if (!ret.hasOwnProperty('pubcid')) { var pubcid = utils.deepAccess(bidRequest, 'crumbs.pubcid'); @@ -675,7 +679,7 @@ export const spec = { if (bidRequest && bidRequest.userId) { this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid', 1); this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcommon', 1); - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1); + this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 1); this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteortus.${BIDDER_CODE}.userid`), 'criteortus', 1); this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1); this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.lipb.lipbid`), 'liveintent.com', 1); diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 33fdaa44100..12937dbec9d 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -420,7 +420,7 @@ function user(bidRequest, bidderRequest) { addExternalUserId(ext.eids, bidRequest.userId.britepoolid, 'britepool.com'); addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo'); addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'identityLink'); - addExternalUserId(ext.eids, bidRequest.userId.id5id, 'id5-sync.com'); + addExternalUserId(ext.eids, utils.deepAccess(bidRequest, 'userId.id5id.uid'), 'id5-sync.com', utils.deepAccess(bidRequest, 'userId.id5id.ext')); addExternalUserId(ext.eids, utils.deepAccess(bidRequest, 'userId.parrableId.eid'), 'parrable.com'); // liveintent if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index e51cc79eb82..3b899e2179d 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -201,7 +201,7 @@ function raiSetEids(bid) { let eids = []; if (bid && bid.userId) { - raiSetUserId(bid, eids, 'id5-sync.com', utils.deepAccess(bid, `userId.id5id`)); + raiSetUserId(bid, eids, 'id5-sync.com', utils.deepAccess(bid, `userId.id5id.uid`)); raiSetUserId(bid, eids, 'pubcommon', utils.deepAccess(bid, `userId.pubcid`)); raiSetUserId(bid, eids, 'criteo.com', utils.deepAccess(bid, `userId.criteoId`)); raiSetUserId(bid, eids, 'liveramp.com', utils.deepAccess(bid, `userId.idl_env`)); diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 6a80fd6dc0d..7c4e2e3ef63 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -228,14 +228,15 @@ export const spec = { } // ID5 fied - if (bid && bid.userId && bid.userId.id5id) { + if (utils.deepAccess(bid, 'userId.id5id.uid')) { userExt.eids = userExt.eids || []; userExt.eids.push( { source: 'id5-sync.com', uids: [{ - id: bid.userId.id5id - }] + id: bid.userId.id5id.uid + }], + ext: bid.userId.id5id.ext || {} } ) } diff --git a/modules/userId/eids.js b/modules/userId/eids.js index eebd0146d50..9e39b548a57 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -30,8 +30,16 @@ const USER_IDS_CONFIG = { // id5Id 'id5id': { + getValue: function(data) { + return data.uid + }, source: 'id5-sync.com', - atype: 1 + atype: 1, + getEidExt: function(data) { + if (data.ext) { + return data.ext; + } + } }, // parrableId diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 0718f22d0d2..4b3b1767cec 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -117,6 +117,9 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { case 'parrableId': payloadRef[key] = userId.eid; break; + case 'id5id': + payloadRef[key] = userId.uid; + break; default: payloadRef[key] = userId; } diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index 511e658c947..725482d07c3 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -100,8 +100,8 @@ export const spec = { if (payloadUserId.tdid) { payload.tdid = payloadUserId.tdid; } - if (payloadUserId.id5id) { - payload.id5 = payloadUserId.id5id; + if (payloadUserId.id5id && payloadUserId.id5id.uid) { + payload.id5 = payloadUserId.id5id.uid; } if (payloadUserId.digitrustid && payloadUserId.digitrustid.data && payloadUserId.digitrustid.data.id) { payload.dtid = payloadUserId.digitrustid.data.id; diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index aa4872641b4..4d888db269d 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -116,7 +116,7 @@ describe('AdheseAdapter', function () { }); it('should include id5 id as /x5 param', function () { - let req = spec.buildRequests([ bidWithParams({}, { 'id5id': 'ID5-1234567890' }) ], bidderRequest); + let req = spec.buildRequests([ bidWithParams({}, { 'id5id': { 'uid': 'ID5-1234567890' } }) ], bidderRequest); expect(JSON.parse(req.data).parameters).to.deep.include({ 'x5': [ 'ID5-1234567890' ] }); }); diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 306914960c3..f9aaea308a7 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -281,7 +281,7 @@ describe('AdxcgAdapter', function () { let bid = deepClone([bidBanner]); let bidderRequests = {}; - bid[0].userId = {id5id: 'id5idsample'}; + bid[0].userId = {id5id: {uid: 'id5idsample'}}; it('should send pubcid if available', function () { let request = spec.buildRequests(bid, bidderRequests); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 4b21244501d..91315da8801 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -224,7 +224,7 @@ describe('AmxBidAdapter', () => { britepoolid: 'sample-britepool', criteoId: 'sample-criteo', digitrustid: {data: {id: 'sample-digitrust'}}, - id5id: 'sample-id5', + id5id: {uid: 'sample-id5'}, idl_env: 'sample-liveramp', lipb: {lipbid: 'sample-liveintent'}, netId: 'sample-netid', diff --git a/test/spec/modules/avocetBidAdapter_spec.js b/test/spec/modules/avocetBidAdapter_spec.js index 4cfd8ab89d4..2a2f29e48d2 100644 --- a/test/spec/modules/avocetBidAdapter_spec.js +++ b/test/spec/modules/avocetBidAdapter_spec.js @@ -69,7 +69,9 @@ describe('Avocet adapter', function () { placement: '012345678901234567890123', }, userId: { - id5id: 'test' + id5id: { + uid: 'test' + } } }, { diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index df9bdcbd47b..a10a7590677 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -108,7 +108,7 @@ describe('ColossussspAdapter', function () { bid.userId.britepoolid = 'britepoolid123'; bid.userId.idl_env = 'idl_env123'; bid.userId.tdid = 'tdid123'; - bid.userId.id5id = 'id5id123' + bid.userId.id5id = { uid: 'id5id123' }; let serverRequest = spec.buildRequests([bid], bidderRequest); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; diff --git a/test/spec/modules/districtmDmxBidAdapter_spec.js b/test/spec/modules/districtmDmxBidAdapter_spec.js index 9b65ab855d8..90e6957fc2c 100644 --- a/test/spec/modules/districtmDmxBidAdapter_spec.js +++ b/test/spec/modules/districtmDmxBidAdapter_spec.js @@ -103,7 +103,9 @@ const bidRequest = [{ id: {} } }, - id5id: {}, + id5id: { + uid: '' + }, pubcid: {}, tdid: {}, criteoId: {}, diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index a6a44f9296d..830f542ef40 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -29,15 +29,39 @@ describe('eids array generation for known sub-modules', function() { }); }); - it('id5Id', function() { - const userId = { - id5id: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'id5-sync.com', - uids: [{id: 'some-random-id-value', atype: 1}] + describe('id5Id', function() { + it('does not include an ext if not provided', function() { + const userId = { + id5id: { + uid: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'id5-sync.com', + uids: [{ id: 'some-random-id-value', atype: 1 }] + }); + }); + + it('includes ext if provided', function() { + const userId = { + id5id: { + uid: 'some-random-id-value', + ext: { + linkType: 0 + } + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'id5-sync.com', + uids: [{ id: 'some-random-id-value', atype: 1 }], + ext: { + linkType: 0 + } + }); }); }); diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js index 79f58470cb3..2996b853046 100644 --- a/test/spec/modules/gamoshiBidAdapter_spec.js +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -423,7 +423,7 @@ describe('GamoshiAdapter', () => { it('build request with ID5 Id', () => { const bidRequestClone = utils.deepClone(bidRequest); bidRequestClone.userId = {}; - bidRequestClone.userId.id5id = 'id5-user-id'; + bidRequestClone.userId.id5id = { uid: 'id5-user-id' }; let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; expect(request.data.user.ext.eids).to.deep.equal([{ 'source': 'id5-sync.com', diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 650712e435f..1cfca4779ad 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -536,7 +536,7 @@ describe('TheMediaGrid Adapter', function () { const bidRequestsWithUserIds = bidRequests.map((bid) => { return Object.assign({ userId: { - id5id: 'id5id_1', + id5id: { uid: 'id5id_1' }, tdid: 'tdid_1', digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, lipb: {lipbid: 'lipb_1'} diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 05ecec8dc36..cea6bdf92b9 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -50,7 +50,9 @@ describe('ID5 ID System', function() { return { name: ID5_MODULE_NAME, value: { - id5id: value + id5id: { + uid: value + } } } } @@ -238,10 +240,13 @@ describe('ID5 ID System', function() { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); - expect(bid.userId.id5id).to.equal(ID5_STORED_ID); + expect(bid.userId.id5id.uid).to.equal(ID5_STORED_ID); expect(bid.userIdAsEids[0]).to.deep.equal({ source: ID5_SOURCE, - uids: [{ id: ID5_STORED_ID, atype: 1 }] + uids: [{ id: ID5_STORED_ID, atype: 1 }], + ext: { + linkType: 0 + } }); }); }); @@ -258,7 +263,7 @@ describe('ID5 ID System', function() { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); - expect(bid.userId.id5id).to.equal(ID5_STORED_ID); + expect(bid.userId.id5id.uid).to.equal(ID5_STORED_ID); expect(bid.userIdAsEids[0]).to.deep.equal({ source: ID5_SOURCE, uids: [{ id: ID5_STORED_ID, atype: 1 }] @@ -368,13 +373,13 @@ describe('ID5 ID System', function() { }); describe('Decode stored object', function() { - const decodedObject = { 'id5id': ID5_STORED_ID }; + const expectedDecodedObject = { id5id: { uid: ID5_STORED_ID, ext: { linkType: 0 } } }; it('should properly decode from a stored object', function() { - expect(id5IdSubmodule.decode(ID5_STORED_OBJ)).to.deep.equal(decodedObject); + expect(id5IdSubmodule.decode(ID5_STORED_OBJ)).to.deep.equal(expectedDecodedObject); }); it('should properly decode from a legacy stored object', function() { - expect(id5IdSubmodule.decode(ID5_LEGACY_STORED_OBJ)).to.deep.equal(decodedObject); + expect(id5IdSubmodule.decode(ID5_LEGACY_STORED_OBJ)).to.deep.equal(expectedDecodedObject); }); it('should return undefined if passed a string', function() { expect(id5IdSubmodule.decode('somestring')).to.eq(undefined); diff --git a/test/spec/modules/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index c162b785aed..7cc154254ff 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -21,7 +21,9 @@ describe('justpremium adapter', function () { }, userId: { tdid: '1111111', - id5id: '2222222', + id5id: { + uid: '2222222' + }, digitrustid: { data: { id: '3333333' @@ -84,7 +86,7 @@ describe('justpremium adapter', function () { expect(jpxRequest.version.jp_adapter).to.equal('1.7') expect(jpxRequest.pubcid).to.equal('0000000') expect(jpxRequest.uids.tdid).to.equal('1111111') - expect(jpxRequest.uids.id5id).to.equal('2222222') + expect(jpxRequest.uids.id5id.uid).to.equal('2222222') expect(jpxRequest.uids.digitrustid.data.id).to.equal('3333333') expect(jpxRequest.us_privacy).to.equal('1YYN') }) diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 053e5102cbf..2d5ba3f48df 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -787,7 +787,7 @@ describe('Livewrapped adapter tests', function () { let testbidRequest = clone(bidderRequest); delete testbidRequest.bids[0].params.userId; testbidRequest.bids[0].userId = {}; - testbidRequest.bids[0].userId.id5id = 'id5-user-id'; + testbidRequest.bids[0].userId.id5id = { uid: 'id5-user-id' }; let result = spec.buildRequests(testbidRequest.bids, testbidRequest); let data = JSON.parse(result.data); diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index 351fbb40228..5d930f2b6ac 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -53,7 +53,7 @@ describe('MediaSquare bid adapter tests', function () { canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' }, uspConsent: '111222333', - userId: {'id5id': '1111'}, + userId: { 'id5id': { uid: '1111' } }, schain: { 'ver': '1.0', 'complete': 1, diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 0808d78c0aa..121f8e76a07 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1043,7 +1043,7 @@ describe('OpenxAdapter', function () { britepoolid: '1111-britepoolid', criteoId: '1111-criteoId', digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - id5id: '1111-id5id', + id5id: {uid: '1111-id5id'}, idl_env: '1111-idl_env', lipb: {lipbid: '1111-lipb'}, netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', @@ -1096,6 +1096,9 @@ describe('OpenxAdapter', function () { case 'parrableId': userIdValue = EXAMPLE_DATA_BY_ATTR.parrableId.eid; break; + case 'id5id': + userIdValue = EXAMPLE_DATA_BY_ATTR.id5id.uid; + break; default: userIdValue = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; } diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index f20e75dfedd..c1022608b4a 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -66,7 +66,7 @@ var validBidRequestsWithUserIdData = [ params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87', - userId: {'pubcid': '12345678', 'id5id': 'ID5-someId', 'criteortus': {'ozone': {'userid': 'critId123'}}, 'idl_env': 'liverampId', 'lipb': {'lipbid': 'lipbidId123'}, 'parrableId': {eid: 'parrableid123'}} + userId: {'pubcid': '12345678', 'id5id': { 'uid': 'ID5-someId' }, 'criteortus': {'ozone': {'userid': 'critId123'}}, 'idl_env': 'liverampId', 'lipb': {'lipbid': 'lipbidId123'}, 'parrableId': {eid: 'parrableid123'}} } ]; var validBidRequestsMinimal = [ @@ -297,7 +297,7 @@ var validBidderRequest1OutstreamVideo2020 = { } }, 'userId': { - 'id5id': 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA', + 'id5id': { uid: 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA' }, 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' }, 'userIdAsEids': [ @@ -2118,7 +2118,7 @@ describe('ozone Adapter', function () { bidRequests[0]['userId'] = { 'criteortus': '1111', 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': '2222', + 'id5id': {'uid': '2222'}, 'idl_env': '3333', 'lipb': {'lipbid': '4444'}, 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, @@ -2138,7 +2138,7 @@ describe('ozone Adapter', function () { bidRequests[0]['userId'] = { 'criteortus': '1111', 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': '2222', + 'id5id': {'uid': '2222'}, 'idl_env': '3333', 'lipb': {'lipbid': '4444'}, 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index d6f755914e5..d069bb74944 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1178,7 +1178,13 @@ describe('S2S Adapter', function () { lipbid: 'li-xyz', segments: ['segA', 'segB'] }, - idl_env: '0000-1111-2222-3333' + idl_env: '0000-1111-2222-3333', + id5id: { + uid: '11111', + ext: { + linkType: 'some-link-type' + } + } }; userIdBidRequest[0].bids[0].userIdAsEids = createEidsArray(userIdBidRequest[0].bids[0].userId); @@ -1199,6 +1205,9 @@ describe('S2S Adapter', function () { expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments.length).is.equal(2); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[0]).is.equal('segA'); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[1]).is.equal('segB'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')).is.not.empty; + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].uids[0].id).is.equal('11111'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].ext.linkType).is.equal('some-link-type'); // LiveRamp should exist expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveramp.com')[0].uids[0].id).is.equal('0000-1111-2222-3333'); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index c1f62ee9f2b..37deb0bca9c 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1605,7 +1605,7 @@ describe('PubMatic adapter', function () { describe('ID5 Id', function() { it('send the id5 id if it is present', function() { bidRequests[0].userId = {}; - bidRequests[0].userId.id5id = 'id5-user-id'; + bidRequests[0].userId.id5id = { uid: 'id5-user-id' }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); @@ -1620,22 +1620,22 @@ describe('PubMatic adapter', function () { it('do not pass if not string', function() { bidRequests[0].userId = {}; - bidRequests[0].userId.id5id = 1; + bidRequests[0].userId.id5id = { uid: 1 }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.id5id = []; + bidRequests[0].userId.id5id = { uid: [] }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.id5id = null; + bidRequests[0].userId.id5id = { uid: null }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.id5id = {}; + bidRequests[0].userId.id5id = { uid: {} }; bidRequests[0].userIdAsEids = createEidsArray(bidRequests[0].userId); request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index d71bd018ab3..a6f5ff2a0dc 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -630,7 +630,7 @@ describe('PulsePoint Adapter Tests', function () { britepoolid: 'britepool_id123', criteoId: 'criteo_id234', idl_env: 'idl_id123', - id5id: 'id5id_234', + id5id: { uid: 'id5id_234' }, parrableId: { eid: 'parrable_id234' }, lipb: { lipbid: 'liveintent_id123' diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index f18e67a3ac5..1c710c46ea2 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -357,7 +357,7 @@ describe('Richaudience adapter tests', function () { }); it('Verify build id5', function () { DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = 'id5-user-id'; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 'id5-user-id' }; var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); @@ -369,13 +369,23 @@ describe('Richaudience adapter tests', function () { var request; DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = 1; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 1 }; request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); expect(requestContent.user.eids).to.equal(undefined); - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = []; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: [] }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: null }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: {} }; request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); requestContent = JSON.parse(request[0].data); expect(requestContent.user.eids).to.equal(undefined); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 2faad47aca8..2de7cb2c9ff 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -71,7 +71,7 @@ describe('Smart bid adapter tests', function () { britepoolid: '1111', criteoId: '1111', digitrustid: { data: { id: 'DTID', keyv: 4, privacy: { optout: false }, producer: 'ABC', version: 2 } }, - id5id: '1111', + id5id: { uid: '1111' }, idl_env: '1111', lipbid: '1111', parrableid: 'eidVersion.encryptionKeyReference.encryptedValue', diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index baece710ee1..cf651670518 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -152,7 +152,7 @@ describe('the spotx adapter', function () { }; bid.userId = { - id5id: 'id5id_1', + id5id: { uid: 'id5id_1' }, tdid: 'tdid_1' }; @@ -202,7 +202,8 @@ describe('the spotx adapter', function () { source: 'id5-sync.com', uids: [{ id: 'id5id_1' - }] + }], + ext: {} }, { source: 'adserver.org', diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index 72321ce415b..e8729965c50 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -94,7 +94,8 @@ const bidReqUserIds = [{ userId: { idl_env: '1111', tdid: '123456', - digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}} + digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + id5id: { uid: '1111' } } }, { @@ -316,6 +317,7 @@ describe('Undertone Adapter', () => { expect(bidCommons.uids.tdid).to.equal('123456'); expect(bidCommons.uids.idl_env).to.equal('1111'); expect(bidCommons.uids.digitrustid.data.id).to.equal('DTID'); + expect(bidCommons.uids.id5id.uid).to.equal('1111'); }); it('should send page sizes sizes correctly', function () { const request = spec.buildRequests(bidReqUserIds, bidderReq); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index e9057ee26d9..749dd653865 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1235,8 +1235,8 @@ describe('User ID', function() { expect(bid).to.have.deep.nested.property('userId.tdid'); expect(bid.userId.tdid).to.equal('testunifiedid'); // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id'); - expect(bid.userId.id5id).to.equal('testid5id'); + expect(bid).to.have.deep.nested.property('userId.id5id.uid'); + expect(bid.userId.id5id.uid).to.equal('testid5id'); // check that identityLink id data was copied to bid expect(bid).to.have.deep.nested.property('userId.idl_env'); expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); @@ -1328,8 +1328,8 @@ describe('User ID', function() { expect(bid).to.have.deep.nested.property('userId.tdid'); expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id'); - expect(bid.userId.id5id).to.equal('testid5id'); + expect(bid).to.have.deep.nested.property('userId.id5id.uid'); + expect(bid.userId.id5id.uid).to.equal('testid5id'); // also check that identityLink id data was copied to bid expect(bid).to.have.deep.nested.property('userId.idl_env'); expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); @@ -1461,8 +1461,8 @@ describe('User ID', function() { expect(bid).to.have.deep.nested.property('userId.tdid'); expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id'); - expect(bid.userId.id5id).to.equal('testid5id'); + expect(bid).to.have.deep.nested.property('userId.id5id.uid'); + expect(bid.userId.id5id.uid).to.equal('testid5id'); // also check that identityLink id data was copied to bid expect(bid).to.have.deep.nested.property('userId.idl_env'); expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 1a503e46b5c..d7f20c434ca 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -244,6 +244,7 @@ describe('VidazooBidAdapter', function () { case 'digitrustid': return { data: { id: id } }; case 'lipb': return { lipbid: id }; case 'parrableId': return { eid: id }; + case 'id5id': return { uid: id }; default: return id; } })(); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index 2a4f8e4c7b5..a06f530e145 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -231,7 +231,7 @@ describe('VisxAdapter', function () { const schainBidRequests = [ Object.assign({userId: { tdid: '111', - id5id: '222', + id5id: { uid: '222' }, digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}} }}, bidRequests[0]), bidRequests[1], diff --git a/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js index c8643c547e0..e8eb4ab73be 100644 --- a/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js +++ b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js @@ -30,7 +30,7 @@ let prebidAuction = { } }, 'userId': { - 'id5id': 'ID5-ZHMOxXeRXPe3inZKGD-Lj0g7y8UWdDbsYXQ_n6aWMQ', + 'id5id': { uid: 'ID5-ZHMOxXeRXPe3inZKGD-Lj0g7y8UWdDbsYXQ_n6aWMQ' }, 'parrableid': '01.1595590997.46d951017bdc272ca50b88dbcfb0545cfc636bec3e3d8c02091fb1b413328fb2fd3baf65cb4114b3f782895fd09f82f02c9042b85b42c4654d08ba06dc77f0ded936c8ea3fc4085b4a99', 'pubcid': '100a8bc9-f588-4c22-873e-a721cb68bc34' }, From 7aff389003092410fb722326e4c6042cf7fefbff Mon Sep 17 00:00:00 2001 From: Stephan Brosinski Date: Fri, 25 Sep 2020 15:03:32 +0200 Subject: [PATCH 015/190] Smaato: Support in-app use cases (#5765) --- modules/smaatoBidAdapter.js | 9 +++- test/spec/modules/smaatoBidAdapter_spec.js | 57 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index ce0edb1e19c..49b4ed6aa34 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -110,6 +110,13 @@ const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + if (utils.deepAccess(validBidRequests[0], 'params.app')) { + const geo = utils.deepAccess(validBidRequests[0], 'params.app.geo'); + utils.deepSetValue(request, 'device.geo', geo); + const ifa = utils.deepAccess(validBidRequests[0], 'params.app.ifa') + utils.deepSetValue(request, 'device.ifa', ifa); + } + utils.logInfo('[SMAATO] OpenRTB Request:', request); return JSON.stringify(request); } @@ -185,7 +192,7 @@ export const spec = { ttl: ttlSec, creativeId: b.crid, dealId: b.dealid || null, - netRevenue: true, + netRevenue: utils.deepAccess(b, 'ext.net', true), currency: res.cur, meta: { advertiserDomains: b.adomain, diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 95eb36d8a0d..13716b51436 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -291,6 +291,36 @@ const combinedBannerAndVideoBidRequest = { bidderWinsCount: 0 }; +const inAppBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId', + app: { + ifa: 'aDeviceId', + geo: { + lat: 33.3, + lon: -88.8 + } + } + }, + mediaTypes: { + banner: { + sizes: [[300, 50]] + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 +}; + describe('smaatoBidAdapterTest', () => { describe('isBidRequestValid', () => { it('has valid params', () => { @@ -462,6 +492,27 @@ describe('smaatoBidAdapterTest', () => { }); }); + describe('in-app requests', () => { + it('add geo and ifa info to device object', () => { + let req = JSON.parse(spec.buildRequests([inAppBidRequest], defaultBidderRequest).data); + expect(req.device.geo).to.deep.equal({'lat': 33.3, 'lon': -88.8}); + expect(req.device.ifa).to.equal('aDeviceId'); + }); + it('add only ifa to device object', () => { + let inAppBidRequestWithoutGeo = utils.deepClone(inAppBidRequest); + delete inAppBidRequestWithoutGeo.params.app.geo + let req = JSON.parse(spec.buildRequests([inAppBidRequestWithoutGeo], defaultBidderRequest).data); + + expect(req.device.geo).to.not.exist; + expect(req.device.ifa).to.equal('aDeviceId'); + }); + it('add no specific device info if param does not exist', () => { + let req = JSON.parse(spec.buildRequests([singleBannerBidRequest], defaultBidderRequest).data); + expect(req.device.geo).to.not.exist; + expect(req.device.ifa).to.not.exist; + }); + }); + describe('interpretResponse', () => { it('single image reponse', () => { const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_IMG), request); @@ -501,5 +552,11 @@ describe('smaatoBidAdapterTest', () => { expect(bids[0].ttl).to.equal(400); clock.restore(); }); + it('uses net revenue flag send from server', () => { + let resp = openRtbBidResponse(ADTYPE_IMG); + resp.body.seatbid[0].bid[0].ext = {net: false}; + const bids = spec.interpretResponse(resp, request); + expect(bids[0].netRevenue).to.equal(false); + }) }); }); From 0fd72056f44a2af9dead69d19d194eac02aed089 Mon Sep 17 00:00:00 2001 From: Monis Qadri Date: Fri, 25 Sep 2020 19:14:03 +0530 Subject: [PATCH 016/190] Added GVLID to Media.net Analytics Adapter (#5789) Co-authored-by: monis.q --- modules/medianetAnalyticsAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 62958cfbfd2..c76f695cc2b 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -728,7 +728,8 @@ medianetAnalytics.enableAnalytics = function (configuration) { adapterManager.registerAnalyticsAdapter({ adapter: medianetAnalytics, - code: 'medianetAnalytics' + code: 'medianetAnalytics', + gvlid: 142, }); export default medianetAnalytics; From c8176d7b093c54d42fd6660ede6dfceb246b0c1c Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 25 Sep 2020 16:45:26 +0200 Subject: [PATCH 017/190] Add video ad support to ablida bid adapter (#5782) * add onBidWon function, add bidder adapter version to bid requests * add support for native * use triggerPxel instead of ajax, because ajax was called 3 times with native * add gdpr consent to bid requests * update tests * add video ad support --- modules/ablidaBidAdapter.js | 8 +++++--- modules/ablidaBidAdapter.md | 16 ++++++++++++++++ test/spec/modules/ablidaBidAdapter_spec.js | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js index af9c206700f..2400952367f 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -1,14 +1,14 @@ import * as utils from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'ablida'; const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, NATIVE], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -35,6 +35,8 @@ export const spec = { let sizes = [] if (bidRequest.mediaTypes && bidRequest.mediaTypes[BANNER] && bidRequest.mediaTypes[BANNER].sizes) { sizes = bidRequest.mediaTypes[BANNER].sizes; + } else if (bidRequest.mediaTypes[VIDEO] && bidRequest.mediaTypes[VIDEO].playerSize) { + sizes = bidRequest.mediaTypes[VIDEO].playerSize } const jaySupported = 'atob' in window && 'currentScript' in document; const device = getDevice(); @@ -46,7 +48,7 @@ export const spec = { referer: bidderRequest.refererInfo.referer, jaySupported: jaySupported, device: device, - adapterVersion: 4, + adapterVersion: 5, mediaTypes: bidRequest.mediaTypes, gdprConsent: bidderRequest.gdprConsent }; diff --git a/modules/ablidaBidAdapter.md b/modules/ablidaBidAdapter.md index 001bee4f35c..e0a9f3f9405 100644 --- a/modules/ablidaBidAdapter.md +++ b/modules/ablidaBidAdapter.md @@ -51,6 +51,22 @@ Module that connects to Ablida's bidder for bids. } } ] + }, { + code: 'video-ad', + mediaTypes: { + video: { + playerSize: [[640, 360]], + context: 'instream' + } + }, + bids: [ + { + bidder: 'ablida', + params: { + placementId: 'instream-demo' + } + } + ] } ]; ``` diff --git a/test/spec/modules/ablidaBidAdapter_spec.js b/test/spec/modules/ablidaBidAdapter_spec.js index 0743bf0a896..73109d8cf16 100644 --- a/test/spec/modules/ablidaBidAdapter_spec.js +++ b/test/spec/modules/ablidaBidAdapter_spec.js @@ -75,7 +75,7 @@ describe('ablidaBidAdapter', function () { method: 'POST', url: ENDPOINT_URL, data: { - adapterVersion: 4, + adapterVersion: 5, bidId: '2b8c4de0116e54', categories: undefined, device: 'desktop', From 05a5a969b8f3a679308c3546386565838ae472d7 Mon Sep 17 00:00:00 2001 From: ghguo Date: Fri, 25 Sep 2020 14:40:13 -0400 Subject: [PATCH 018/190] Add adrelevantis adapter (#5735) * Update adrelevantis adapter * Update Adrelevantis Bid Adapter and Add Unit Tests Commit changes suggested by @jsnellbaker on pull request #5735 --- modules/adrelevantisBidAdapter.js | 603 ++++++++++++++ modules/adrelevantisBidAdapter.md | 120 +++ .../modules/adrelevantisBidAdapter_spec.js | 769 ++++++++++++++++++ 3 files changed, 1492 insertions(+) create mode 100644 modules/adrelevantisBidAdapter.js create mode 100644 modules/adrelevantisBidAdapter.md create mode 100644 test/spec/modules/adrelevantisBidAdapter_spec.js diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js new file mode 100644 index 00000000000..5da941c65ca --- /dev/null +++ b/modules/adrelevantisBidAdapter.js @@ -0,0 +1,603 @@ +import { Renderer } from '../src/Renderer.js'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { OUTSTREAM, INSTREAM } from '../src/video.js'; + +const BIDDER_CODE = 'adrelevantis'; +const URL = 'https://ssp.adrelevantis.com/prebid'; +const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', + 'startdelay', 'skippable', 'playback_method', 'frameworks']; +const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; +const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately +const SOURCE = 'pbjs'; +const MAX_IMPS_PER_REQUEST = 15; + +const NATIVE_MAPPING = { + body: 'description', + body2: 'desc2', + cta: 'ctatext', + image: { + serverName: 'main_image', + requiredParams: { required: true } + }, + icon: { + serverName: 'icon', + requiredParams: { required: true } + }, + sponsoredBy: 'sponsored_by', + privacyLink: 'privacy_link', + salePrice: 'saleprice', + displayUrl: 'displayurl' +}; + +export const spec = { + code: BIDDER_CODE, + aliases: ['adr', 'adsmart', 'compariola'], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + const tags = bidRequests.map(bidToTag); + const userObjBid = find(bidRequests, hasUserInfo); + let userObj; + if (config.getConfig('coppa') === true) { + userObj = {'coppa': true}; + } + if (userObjBid) { + userObj = {}; + Object.keys(userObjBid.params.user) + .filter(param => includes(USER_PARAMS, param)) + .forEach(param => userObj[param] = userObjBid.params.user[param]); + } + + const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo); + let appDeviceObj; + if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { + appDeviceObj = {}; + Object.keys(appDeviceObjBid.params.app) + .filter(param => includes(APP_DEVICE_PARAMS, param)) + .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]); + } + + const appIdObjBid = find(bidRequests, hasAppId); + let appIdObj; + if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = { + appid: appIdObjBid.params.app.id + }; + } + + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + } + }; + + if (appDeviceObjBid) { + payload.device = appDeviceObj + } + if (appIdObjBid) { + payload.app = appIdObj; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + // note - objects for impbus use underscore instead of camelCase + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + + if (bidderRequest && bidderRequest.refererInfo) { + let refererinfo = { + rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + } + payload.referrer_detection = refererinfo; + } + + let fpdcfg = config.getConfig('fpd') + if (fpdcfg && fpdcfg.context) { + let fdata = { + keywords: fpdcfg.context.keywords, + category: fpdcfg.context.data.category + } + payload.fpd = fdata; + } + + const request = formatRequest(payload, bidderRequest); + return request; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, {bidderRequest}) { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } + utils.logError(errorMessage); + return bids; + } + + if (serverResponse.tags) { + serverResponse.tags.forEach(serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + bids.push(bid); + } + } + }); + } + + return bids; + }, + + transformBidParams: function(params, isOpenRtb) { + params = utils.convertTypes({ + 'placementId': 'number', + 'keywords': utils.transformBidderParamKeywords + }, params); + + if (isOpenRtb) { + params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; + if (params.usePaymentRule) { delete params.usePaymentRule; } + + if (isPopulatedArray(params.keywords)) { + params.keywords.forEach(deleteValues); + } + + Object.keys(params).forEach(paramKey => { + let convertedKey = utils.convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + } + + return params; + } +} + +function isPopulatedArray(arr) { + return !!(utils.isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function formatRequest(payload, bidderRequest) { + let request = []; + + if (payload.tags.length > MAX_IMPS_PER_REQUEST) { + const clonedPayload = utils.deepClone(payload); + + utils.chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { + clonedPayload.tags = tags; + const payloadString = JSON.stringify(clonedPayload); + request.push({ + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }); + }); + } else { + const payloadString = JSON.stringify(payload); + request = { + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }; + } + + return request; +} + +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: rtbBid.renderer_id, + url: rtbBid.renderer_url, + config: rendererOptions, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + renderer.setEventHandlers({ + impression: () => utils.logMessage('AdRelevantis outstream video impression event'), + loaded: () => utils.logMessage('AdRelevantis outstream video loaded event'), + ended: () => { + utils.logMessage('AdRelevantis outstream renderer video event'); + document.querySelector(`#${adUnitCode}`).style.display = 'none'; + } + }); + return renderer; +} + +/** + * This function hides google div container for outstream bids to remove unwanted space on page. Appnexus renderer creates a new iframe outside of google iframe to render the outstream creative. + * @param {string} elementId element id + */ +function hidedfpContainer(elementId) { + var el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); + if (el[0]) { + el[0].style.setProperty('display', 'none'); + } +} + +function outstreamRender(bid) { + // push to render queue because ANOutstreamVideo may not be loaded yet + hidedfpContainer(bid.adUnitCode); + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + tagId: bid.adResponse.tag_id, + sizes: [bid.getSize().split('x')], + targetId: bid.adUnitCode, // target div id to render video + uuid: bid.adResponse.uuid, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, handleOutstreamRendererEvents.bind(null, bid)); + }); +} + +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, rtbBid, bidderRequest) { + const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm, + creativeId: rtbBid.creative_id, + dealId: rtbBid.deal_id, + currency: 'USD', + netRevenue: true, + ttl: 300, + adUnitCode: bidRequest.adUnitCode, + adrelevantis: { + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code + } + }; + + if (rtbBid.advertiser_id) { + bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); + } + + if (rtbBid.rtb.video) { + Object.assign(bid, { + width: rtbBid.rtb.video.player_width, + height: rtbBid.rtb.video.player_height, + vastImpUrl: rtbBid.notify_url, + ttl: 3600 + }); + + const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + switch (videoContext) { + case OUTSTREAM: + bid.adResponse = serverBid; + bid.adResponse.ad = bid.adResponse.ads[0]; + bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; + bid.vastXml = rtbBid.rtb.video.content; + + if (rtbBid.renderer_url) { + const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); + const rendererOptions = utils.deepAccess(videoBid, 'renderer.options'); + bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); + } + break; + case INSTREAM: + bid.vastUrl = rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url); + break; + } + } else if (rtbBid.rtb[NATIVE]) { + const nativeAd = rtbBid.rtb[NATIVE]; + + // setting up the jsTracker: + // we put it as a data-src attribute so that the tracker isn't called + // until we have the adId (see onBidWon) + let jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src='); + + let jsTrackers = nativeAd.javascript_trackers; + + if (jsTrackers == undefined) { + jsTrackers = jsTrackerDisarmed; + } else if (utils.isStr(jsTrackers)) { + jsTrackers = [jsTrackers, jsTrackerDisarmed]; + } else { + jsTrackers.push(jsTrackerDisarmed); + } + + bid[NATIVE] = { + title: nativeAd.title, + body: nativeAd.desc, + body2: nativeAd.desc2, + cta: nativeAd.ctatext, + rating: nativeAd.rating, + sponsoredBy: nativeAd.sponsored, + privacyLink: nativeAd.privacy_link, + address: nativeAd.address, + downloads: nativeAd.downloads, + likes: nativeAd.likes, + phone: nativeAd.phone, + price: nativeAd.price, + salePrice: nativeAd.saleprice, + clickUrl: nativeAd.link.url, + displayUrl: nativeAd.displayurl, + clickTrackers: nativeAd.link.click_trackers, + impressionTrackers: nativeAd.impression_trackers, + javascriptTrackers: jsTrackers + }; + if (nativeAd.main_img) { + bid['native'].image = { + url: nativeAd.main_img.url, + height: nativeAd.main_img.height, + width: nativeAd.main_img.width, + }; + } + if (nativeAd.icon) { + bid['native'].icon = { + url: nativeAd.icon.url, + height: nativeAd.icon.height, + width: nativeAd.icon.width, + }; + } + } else { + Object.assign(bid, { + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + ad: rtbBid.rtb.banner.content + }); + try { + const url = rtbBid.rtb.trackers[0].impression_urls[0]; + const tracker = utils.createTrackPixelHtml(url); + bid.ad += tracker; + } catch (error) { + utils.logError('Error appending tracking pixel', error); + } + } + + return bid; +} + +function bidToTag(bid) { + const tag = {}; + tag.sizes = transformSizes(bid.sizes); + tag.primary_size = tag.sizes[0]; + tag.ad_types = []; + tag.uuid = bid.bidId; + if (bid.params.placementId) { + tag.id = parseInt(bid.params.placementId, 10); + } + if (bid.params.cpm) { + tag.cpm = bid.params.cpm; + } + tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.prebid = true; + tag.disable_psa = true; + if (bid.params.reserve) { + tag.reserve = bid.params.reserve; + } + if (bid.params.position) { + tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = transformSizes(bid.params.privateSizes); + } + if (bid.params.supplyType) { + tag.supply_type = bid.params.supplyType; + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.extInvCode) { + tag.ext_inv_code = bid.params.extInvCode; + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } + if (!utils.isEmpty(bid.params.keywords)) { + let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + if (bid.params.category) { + tag.category = bid.params.category; + } + + if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { + tag.ad_types.push(NATIVE); + if (tag.sizes.length === 0) { + tag.sizes = transformSizes([1, 1]); + } + + if (bid.nativeParams) { + const nativeRequest = buildNativeRequest(bid.nativeParams); + tag[NATIVE] = {layouts: [nativeRequest]}; + } + } + + const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + + tag.hb_source = 1; + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } + + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; + } + + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => tag.video[param] = bid.params.video[param]); + } + + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, {custom_renderer_present: true}); + } + + if ( + (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || + (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) + ) { + tag.ad_types.push(BANNER); + } + + return tag; +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if (utils.isArray(requestSizes) && requestSizes.length === 2 && + !utils.isArray(requestSizes[0])) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function hasAppDeviceInfo(bid) { + if (bid.params) { + return !!bid.params.app + } +} + +function hasAppId(bid) { + if (bid.params && bid.params.app) { + return !!bid.params.app.id + } + return !!bid.params.app +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); +} + +function buildNativeRequest(params) { + const request = {}; + + // map standard prebid native asset identifier to /ut parameters + // e.g., tag specifies `body` but /ut only knows `description`. + // mapping may be in form {tag: ''} or + // {tag: {serverName: '', requiredParams: {...}}} + Object.keys(params).forEach(key => { + // check if one of the forms is used, otherwise + // a mapping wasn't specified so pass the key straight through + const requestKey = + (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || + NATIVE_MAPPING[key] || + key; + + // required params are always passed on request + const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; + request[requestKey] = Object.assign({}, requiredParams, params[key]); + + // convert the sizes of image/icon assets to proper format (if needed) + const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); + if (isImageAsset && request[requestKey].sizes) { + let sizes = request[requestKey].sizes; + if (utils.isArrayOfNums(sizes) || (utils.isArray(sizes) && sizes.length > 0 && sizes.every(sz => utils.isArrayOfNums(sz)))) { + request[requestKey].sizes = transformSizes(request[requestKey].sizes); + } + } + + if (requestKey === NATIVE_MAPPING.privacyLink) { + request.privacy_supported = true; + } + }); + + return request; +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else { + return BANNER; + } +} + +registerBidder(spec); diff --git a/modules/adrelevantisBidAdapter.md b/modules/adrelevantisBidAdapter.md new file mode 100644 index 00000000000..a60a47508ff --- /dev/null +++ b/modules/adrelevantisBidAdapter.md @@ -0,0 +1,120 @@ +# Overview + +``` +Module Name: Adrelevantis Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@adrelevantis.com +``` + +# Description + +Connects to Adrelevantis exchange for bids. + +Adrelevantis bid adapter supports Banner, Video (outstream) and Native. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'adrelevantis', + params: { + placementId: 13144370, + cpm: 0.50 + } + }] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'adrelevantis', + params: { + placementId: 13232354, + allowSmallerSizes: true + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + mediaTypes: { + video: { + playerSize: [[640, 360]], + context: 'outstream' + } + }, + bids: [ + { + bidder: 'adrelevantis', + params: { + placementId: 13232385, + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + } + ] + }, + + // Banner adUnit in a App Webview + // Only use this for situations where prebid.js is in a webview of an App + // See Prebid Mobile for displaying ads via an SDK + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + } + bids: [{ + bidder: 'adrelevantis', + params: { + placementId: 13144370, + app: { + id: "B1O2W3M4AN.com.prebid.webview", + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier + aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier + md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID + sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID + windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier + } + } + } + }] + } +]; +``` diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js new file mode 100644 index 00000000000..11a6a14a353 --- /dev/null +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -0,0 +1,769 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adrelevantisBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config.js'; + +const ENDPOINT = 'https://ssp.adrelevantis.com/prebid'; + +describe('AdrelevantisAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'adrelevantis', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'adrelevantis', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should parse out private sizes', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + privateSizes: [300, 250] + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].private_sizes).to.exist; + expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); + }); + + it('should add source and verison to the tag', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.sdk).to.exist; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + }); + + it('should populate the ad_types array on all requests', function () { + ['banner', 'video', 'native'].forEach(type => { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes[type] = {}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal([type]); + }); + }); + + it('should populate the ad_types array on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + }); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should attach valid video params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + video: { + id: 123, + minduration: 100, + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + }); + + it('should add video property when adUnit includes a renderer', function () { + const videoData = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + }, + params: { + placementId: '10433394', + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + }; + + let bidRequest1 = deepClone(bidRequests[0]); + bidRequest1 = Object.assign({}, bidRequest1, videoData, { + renderer: { + url: 'http://test.renderer.url', + render: function () {} + } + }); + + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + bidRequest2 = Object.assign({}, bidRequest2, videoData); + + const request = spec.buildRequests([bidRequest1, bidRequest2]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + skippable: true, + playback_method: ['auto_play_sound_off'], + custom_renderer_present: true + }); + expect(payload.tags[1].video).to.deep.equal({ + skippable: true, + playback_method: ['auto_play_sound_off'] + }); + }); + + it('should attach valid user params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + user: { + externalUid: '123', + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.user).to.exist; + expect(payload.user).to.deep.equal({ + externalUid: '123', + }); + }); + + it('should contain hb_source value for other media', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'banner', + params: { + sizes: [[300, 250], [300, 600]], + placementId: 10433394 + } + } + ); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('adds context data (category and keywords) to request when set', function() { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon + .stub(config, 'getConfig') + .withArgs('fpd') + .returns({ + context: { + keywords: 'US Open', + data: { + category: 'sports/tennis' + } + } + }); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.fpd.keywords).to.equal('US Open'); + expect(payload.fpd.category).to.equal('sports/tennis'); + + config.getConfig.restore(); + }); + + it('should attach native params to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + title: {required: true}, + body: {required: true}, + body2: {required: true}, + image: {required: true, sizes: [100, 100]}, + icon: {required: true}, + cta: {required: false}, + rating: {required: true}, + sponsoredBy: {required: true}, + privacyLink: {required: true}, + displayUrl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + salePrice: {required: true} + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].native.layouts[0]).to.deep.equal({ + title: {required: true}, + description: {required: true}, + desc2: {required: true}, + main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, + icon: {required: true}, + ctatext: {required: false}, + rating: {required: true}, + sponsored_by: {required: true}, + privacy_link: {required: true}, + displayurl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + saleprice: {required: true}, + privacy_supported: true + }); + expect(payload.tags[0].hb_source).to.equal(1); + }); + + it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + image: { required: true } + } + } + ); + bidRequest.sizes = [[150, 100], [300, 250]]; + + let request = spec.buildRequests([bidRequest]); + let payload = JSON.parse(request.data); + expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); + + delete bidRequest.sizes; + + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); + + expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); + }); + + it('should convert keyword params to proper form and attaches to request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [5], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + emptyStr: '', + emptyArr: [''], + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['5'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' + }]); + }); + + it('should add payment rules to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + usePaymentRule: true + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].use_pmt_rule).to.equal(true); + }); + + it('should add gdpr consent information to the request', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'adrelevantis', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_consent).to.exist; + expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); + expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; + }); + + it('supports sending hybrid mobile app parameters', function () { + let appRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + app: { + id: 'B1O2W3M4AN.com.prebid.webview', + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', // Apple advertising identifier + aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', // Android advertising identifier + md5udid: '5756ae9022b2ea1e47d84fead75220c8', // MD5 hash of the ANDROID_ID + sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', // SHA1 hash of the ANDROID_ID + windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' // Windows advertising identifier + } + } + } + } + ); + const request = spec.buildRequests([appRequest]); + const payload = JSON.parse(request.data); + expect(payload.app).to.exist; + expect(payload.app).to.deep.equal({ + appid: 'B1O2W3M4AN.com.prebid.webview' + }); + expect(payload.device.device_id).to.exist; + expect(payload.device.device_id).to.deep.equal({ + aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', + idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', + md5udid: '5756ae9022b2ea1e47d84fead75220c8', + sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', + windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' + }); + expect(payload.device.geo).to.exist; + expect(payload.device.geo).to.deep.equal({ + lat: 40.0964439, + lng: -75.3009142 + }); + }); + + it('should add referer info to payload', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'http://example.com/page.html', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ] + } + } + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.referrer_detection).to.exist; + expect(payload.referrer_detection).to.deep.equal({ + rd_ref: 'http%3A%2F%2Fexample.com%2Fpage.html', + rd_top: true, + rd_ifs: 2, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + }); + }); + + it('should populate coppa if set in config', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.user.coppa).to.equal(true); + + config.getConfig.restore(); + }); + }) + + describe('interpretResponse', function () { + let bfStub; + before(function() { + bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + }); + + after(function() { + bfStub.restore(); + }); + + let response = { + 'version': '3.0.0', + 'tags': [ + { + 'uuid': '3db3773286ee59', + 'tag_id': 10433394, + 'auction_id': '4534722592064951574', + 'nobid': false, + 'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 10000, + 'ad_profile_id': 27079, + 'ads': [ + { + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 29681110, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.5, + 'cpm_publisher_currency': 0.5, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'viewability': { + 'config': '' + }, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'http://lax1-ib.adnxs.com/impression' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'adUnitCode': 'code', + 'adrelevantis': { + 'buyerMemberId': 958 + } + } + ]; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + let response = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + + it('handles outstream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles instream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/vid' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles native responses', function () { + let response1 = deepClone(response); + response1.tags[0].ads[0].ad_type = 'native'; + response1.tags[0].ads[0].rtb.native = { + 'title': 'Native Creative', + 'desc': 'Cool description great stuff', + 'desc2': 'Additional body text', + 'ctatext': 'Do it', + 'sponsored': 'AppNexus', + 'icon': { + 'width': 0, + 'height': 0, + 'url': 'https://cdn.adnxs.com/icon.png' + }, + 'main_img': { + 'width': 2352, + 'height': 1516, + 'url': 'https://cdn.adnxs.com/img.png' + }, + 'link': { + 'url': 'https://www.appnexus.com', + 'fallback_url': '', + 'click_trackers': ['https://nym1-ib.adnxs.com/click'] + }, + 'impression_trackers': ['https://example.com'], + 'rating': '5', + 'displayurl': 'https://AppNexus.com/?url=display_url', + 'likes': '38908320', + 'downloads': '874983', + 'price': '9.99', + 'saleprice': 'FREE', + 'phone': '1234567890', + 'address': '28 W 23rd St, New York, NY 10010', + 'privacy_link': 'https://appnexus.com/?url=privacy_url', + 'javascriptTrackers': '' + }; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + + let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); + expect(result[0].native.title).to.equal('Native Creative'); + expect(result[0].native.body).to.equal('Cool description great stuff'); + expect(result[0].native.cta).to.equal('Do it'); + expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + }); + + it('supports configuring outstream renderers', function () { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + renderer: { + options: { + adText: 'configured' + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + }; + + const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); + + it('should add deal_priority and deal_code', function() { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].deal_priority = 'high'; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); + expect(Object.keys(result[0].adrelevantis)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + }); + + it('should add advertiser id', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); + }) + }); +}); From ecd05a322eda3d9e1cfaba3a28feed834b28c71d Mon Sep 17 00:00:00 2001 From: vingood Date: Fri, 25 Sep 2020 21:55:58 +0300 Subject: [PATCH 019/190] Adnow bidder (#5738) * Add AdNow bid Adaptor * Fix problems by PR comments. * PR comments: - Use only secure endpoint. - Use adUnit mediaTypes instead of mediaType param in buildRequests. - Pass correct sizes to the endpoint for banner and native. - Fix adnowBidAdaper.md examples. - Fix and add new tests in adnowBidAdaper_spec.js * rename test * Restore package-lock.json from master * Fix sizes of bid response object for banners. * Fix adapters tests. --- modules/adnowBidAdapter.js | 178 ++++++++++++++++++++++ modules/adnowBidAdapter.md | 46 ++++++ test/spec/modules/adnowBidAdapter_spec.js | 163 ++++++++++++++++++++ 3 files changed, 387 insertions(+) create mode 100644 modules/adnowBidAdapter.js create mode 100644 modules/adnowBidAdapter.md create mode 100644 test/spec/modules/adnowBidAdapter_spec.js diff --git a/modules/adnowBidAdapter.js b/modules/adnowBidAdapter.js new file mode 100644 index 00000000000..7412db8d7b6 --- /dev/null +++ b/modules/adnowBidAdapter.js @@ -0,0 +1,178 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const BIDDER_CODE = 'adnow'; +const ENDPOINT = 'https://n.ads3-adnow.com/a'; + +/** + * @typedef {object} CommonBidData + * + * @property {string} requestId The specific BidRequest which this bid is aimed at. + * This should match the BidRequest.bidId which this Bid targets. + * @property {string} currency The currency code for the cpm value + * @property {number} cpm The bid price, in US cents per thousand impressions. + * @property {string} creativeId The id of ad content + * @property {number} ttl Time-to-live - how long (in seconds) Prebid can use this bid. + * @property {boolean} netRevenue Boolean defining whether the bid is Net or Gross. The default is true (Net). + * @property {object} [meta] Object for storing bid meta data + * @property {string} [meta.mediaType] banner or native + */ + +/** @type {BidderSpec} */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [ NATIVE, BANNER ], + + /** + * @param {object} bid + * @return {boolean} + */ + isBidRequestValid(bid) { + if (!bid || !bid.params) return false; + + const codeId = parseInt(bid.params.codeId, 10); + if (!codeId) { + return false; + } + + const mediaType = bid.params.mediaType || NATIVE; + + return includes(this.supportedMediaTypes, mediaType); + }, + + /** + * @param {BidRequest[]} validBidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ + buildRequests(validBidRequests, bidderRequest) { + return validBidRequests.map(req => { + const mediaType = this._isBannerRequest(req) ? BANNER : NATIVE; + const codeId = parseInt(req.params.codeId, 10); + + const data = { + Id: codeId, + mediaType: mediaType, + out: 'prebid', + d_user_agent: navigator.userAgent, + requestid: req.bidId + }; + + if (mediaType === BANNER) { + data.sizes = utils.parseSizesInput( + req.mediaTypes && req.mediaTypes.banner && req.mediaTypes.banner.sizes + ).join('|') + } else { + data.width = data.height = 200; + + let sizes = utils.deepAccess(req, 'mediaTypes.native.image.sizes', []); + + if (sizes.length > 0) { + const size = Array.isArray(sizes[0]) ? sizes[0] : sizes; + + data.width = size[0] || data.width; + data.height = size[1] || data.height; + } + } + + /** @type {ServerRequest} */ + return { + method: 'GET', + url: ENDPOINT, + data: utils.parseQueryStringParameters(data), + options: { + withCredentials: false, + crossOrigin: true + }, + bidRequest: req + }; + }); + }, + + /** + * @param {*} response + * @param {ServerRequest} request + * @return {Bid[]} + */ + interpretResponse(response, request) { + const bidObj = request.bidRequest; + let bid = response.body; + + if (!bid || !bid.currency || !bid.cpm) { + return []; + } + + const mediaType = bid.meta.mediaType || NATIVE; + if (!includes(this.supportedMediaTypes, mediaType)) { + return []; + } + + bid.requestId = bidObj.bidId; + + if (mediaType === BANNER) { + return [ this._getBannerBid(bid) ]; + } + + if (mediaType === NATIVE) { + return [ this._getNativeBid(bid) ]; + } + + return []; + }, + + /** + * @private + * @param {object} bid + * @return {CommonBidData} + */ + _commonBidData(bid) { + return { + requestId: bid.requestId, + currency: bid.currency || 'USD', + cpm: bid.cpm || 0.00, + creativeId: bid.creativeId || 'undefined-creative', + netRevenue: bid.netRevenue || true, + ttl: bid.ttl || 360, + meta: bid.meta || {} + }; + }, + + /** + * @param {BidRequest} req + * @return {boolean} + * @private + */ + _isBannerRequest(req) { + return !!(req.mediaTypes && req.mediaTypes.banner); + }, + + /** + * @private + * @param {object} bid + * @return {Bid} + */ + _getBannerBid(bid) { + return { + ...this._commonBidData(bid), + width: bid.width || 300, + height: bid.height || 250, + ad: bid.ad || '
Empty Ad
' + }; + }, + + /** + * @private + * @param {object} bid + * @return {Bid} + */ + _getNativeBid(bid) { + return { + ...this._commonBidData(bid), + native: bid.native || {} + }; + } +} + +registerBidder(spec); diff --git a/modules/adnowBidAdapter.md b/modules/adnowBidAdapter.md new file mode 100644 index 00000000000..9ad99a67fc5 --- /dev/null +++ b/modules/adnowBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: AdNow Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@adnow.com +``` + +# Description + +AdNow Bidder Adapter for Prebid.js. +Banner and Native format are supported. +Please use ```adnow``` as the bidder code. + +# Test Parameters +```javascript + const adUnits = [{ + code: 'test', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'adnow', + params: { + codeId: 794934 + } + }] + }, { + code: 'test', + mediaTypes: { + native: { + image: { + sizes: [200, 200] + } + } + }, + bids: [{ + bidder: 'adnow', + params: { + codeId: 794934 + } + }] + }]; +``` diff --git a/test/spec/modules/adnowBidAdapter_spec.js b/test/spec/modules/adnowBidAdapter_spec.js new file mode 100644 index 00000000000..a8013e3fa04 --- /dev/null +++ b/test/spec/modules/adnowBidAdapter_spec.js @@ -0,0 +1,163 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adnowBidAdapter.js'; + +describe('adnowBidAdapter', function () { + describe('isBidRequestValid', function () { + it('Should return true', function() { + expect(spec.isBidRequestValid({ + bidder: 'adnow', + params: { + codeId: 12345 + } + })).to.equal(true); + }); + + it('Should return false when required params is not passed', function() { + expect(spec.isBidRequestValid({ + bidder: 'adnow', + params: {} + })).to.equal(false); + }); + }); + + describe('buildRequests', function() { + it('Common settings', function() { + const bidRequestData = [{ + bidId: 'bid12345', + params: { + codeId: 12345 + } + }]; + + const req = spec.buildRequests(bidRequestData); + const reqData = req[0].data; + + expect(reqData) + .to.match(/Id=12345/) + .to.match(/mediaType=native/) + .to.match(/out=prebid/) + .to.match(/requestid=bid12345/) + .to.match(/d_user_agent=.+/); + }); + + it('Banner sizes', function () { + const bidRequestData = [{ + bidId: 'bid12345', + params: { + codeId: 12345 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }]; + + const req = spec.buildRequests(bidRequestData); + const reqData = req[0].data; + + expect(reqData).to.match(/sizes=300x250/); + }); + + it('Native sizes', function () { + const bidRequestData = [{ + bidId: 'bid12345', + params: { + codeId: 12345 + }, + mediaTypes: { + native: { + image: { + sizes: [100, 100] + } + } + } + }]; + + const req = spec.buildRequests(bidRequestData); + const reqData = req[0].data; + + expect(reqData) + .to.match(/width=100/) + .to.match(/height=100/); + }); + }); + + describe('interpretResponse', function() { + const request = { + bidRequest: { + bidId: 'bid12345' + } + }; + + it('Response with native bid', function() { + const response = { + currency: 'USD', + cpm: 0.5, + native: { + title: 'Title', + body: 'Body', + sponsoredBy: 'AdNow', + clickUrl: '//click.url', + image: { + url: '//img.url', + height: 200, + width: 200 + } + }, + meta: { + mediaType: 'native' + } + }; + + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + const bid = bids[0]; + expect(bid).to.have.keys('requestId', 'cpm', 'currency', 'native', 'creativeId', 'netRevenue', 'meta', 'ttl'); + + const nativePart = bid.native; + + expect(nativePart.title).to.be.equal('Title'); + expect(nativePart.body).to.be.equal('Body'); + expect(nativePart.clickUrl).to.be.equal('//click.url'); + expect(nativePart.image.url).to.be.equal('//img.url'); + expect(nativePart.image.height).to.be.equal(200); + expect(nativePart.image.width).to.be.equal(200); + }); + + it('Response with banner bid', function() { + const response = { + currency: 'USD', + cpm: 0.5, + ad: '
Banner
', + meta: { + mediaType: 'banner' + } + }; + + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + const bid = bids[0]; + expect(bid).to.have.keys( + 'requestId', 'cpm', 'currency', 'ad', 'creativeId', 'netRevenue', 'meta', 'ttl', 'width', 'height' + ); + + expect(bid.ad).to.be.equal('
Banner
'); + }); + + it('Response with no bid should return an empty array', function() { + const noBidResponses = [ + false, + {}, + {body: false}, + {body: {}} + ]; + + noBidResponses.forEach(response => { + return expect(spec.interpretResponse(response, request)).to.be.an('array').that.is.empty; + }); + }); + }); +}); From b2f0c6d26417e74d16cd7785917bfbb31318485f Mon Sep 17 00:00:00 2001 From: jsut Date: Fri, 25 Sep 2020 15:39:08 -0400 Subject: [PATCH 020/190] Improve error and documentation for publisherId (#5788) - The error message you get if you use a publisherId that is a JS numeric instead of a JS string is not super helpful if you aren't familiar with JS internals. Update the warning message to give a suggestion on a solution, and update the markdown documentation to explictly state that the ID needs to be wrapped in quotes. --- modules/pubmaticBidAdapter.js | 2 +- modules/pubmaticBidAdapter.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 1651f1f5cc0..b5514ab0344 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -847,7 +847,7 @@ export const spec = { isBidRequestValid: bid => { if (bid && bid.params) { if (!utils.isStr(bid.params.publisherId)) { - utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric (wrap it in quotes in your config). Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); return false; } // video ad validation diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index cd9398477f4..0ef89a22cbd 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -24,7 +24,7 @@ var adUnits = [ bids: [{ bidder: 'pubmatic', params: { - publisherId: '156209', // required + publisherId: '156209', // required, must be wrapped in quotes oustreamAU: 'renderer_test_pubmatic', // required if mediaTypes-> video-> context is 'outstream'. This value can be get by BlueBillyWig Team. adSlot: 'pubmatic_test2', // optional pmzoneid: 'zone1, zone11', // optional From 280e9577f24c370979c2712c5657ae8fda145e04 Mon Sep 17 00:00:00 2001 From: Amanda Dillon <41923726+agdillon@users.noreply.github.com> Date: Mon, 28 Sep 2020 04:32:09 -0600 Subject: [PATCH 021/190] SpotX bid adapter: add page parameter (#5784) --- modules/spotxBidAdapter.js | 23 ++++++++--- test/spec/modules/spotxBidAdapter_spec.js | 47 +++++++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 7c4e2e3ef63..6104fce1d97 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -1,4 +1,5 @@ import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; @@ -19,7 +20,7 @@ export const spec = { * From Prebid.js: isBidRequestValid - Verify the the AdUnits.bids, respond with true (valid) or false (invalid). * * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. + * @return {boolean} True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { if (bid && typeof bid.params !== 'object') { @@ -64,14 +65,24 @@ export const spec = { * from Prebid.js: buildRequests - Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. * * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest Info describing the request to the server. + * @param {object} bidderRequest - The master bidRequest object. + * @return {ServerRequest} Info describing the request to the server. */ buildRequests: function(bidRequests, bidderRequest) { - const page = bidderRequest.refererInfo.referer; - const isPageSecure = !!page.match(/^https:/) + const referer = bidderRequest.refererInfo.referer; + const isPageSecure = !!referer.match(/^https:/); const siteId = ''; const spotxRequests = bidRequests.map(function(bid) { + let page; + if (utils.getBidIdParameter('page', bid.params)) { + page = utils.getBidIdParameter('page', bid.params); + } else if (config.getConfig('pageUrl')) { + page = config.getConfig('pageUrl'); + } else { + page = referer; + } + const channelId = utils.getBidIdParameter('channel_id', bid.params); let pubcid = null; @@ -436,11 +447,11 @@ function createOutstreamScript(bid) { const customOverride = utils.getBidIdParameter('custom_override', bid.renderer.config.outstream_options); if (customOverride && utils.isPlainObject(customOverride)) { - utils.logMessage('[SPOTX][renderer] Custom beahavior.'); + utils.logMessage('[SPOTX][renderer] Custom behavior.'); for (let name in customOverride) { if (customOverride.hasOwnProperty(name)) { if (name === 'channel_id' || name === 'vast_url' || name === 'content_page_url' || name === 'ad_unit') { - utils.logWarn('[SPOTX][renderer] Custom beahavior: following option cannot be overrided: ' + name); + utils.logWarn('[SPOTX][renderer] Custom behavior: following option cannot be overridden: ' + name); } else { dataSpotXParams['data-spotx_' + name] = customOverride[name]; } diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index cf651670518..798fb3eec10 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -1,4 +1,5 @@ import {expect} from 'chai'; +import {config} from 'src/config.js'; import {spec, GOOGLE_CONSENT} from 'modules/spotxBidAdapter.js'; describe('the spotx adapter', function () { @@ -89,6 +90,7 @@ describe('the spotx adapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); + describe('buildRequests', function() { var bid, bidRequestObj; @@ -125,6 +127,7 @@ describe('the spotx adapter', function () { page: 'prebid.js' }); }); + it('should change request parameters based on options sent', function() { var request = spec.buildRequests([bid], bidRequestObj)[0]; expect(request.data.imp.video.ext).to.deep.equal({ @@ -332,6 +335,50 @@ describe('the spotx adapter', function () { expect(request.data.imp.video.ext.placement).to.equal(2); expect(request.data.imp.video.ext.pos).to.equal(5); }); + + it('should pass page param and override refererInfo.referer', function() { + var request; + + bid.params.page = 'https://example.com'; + + var origGetConfig = config.getConfig; + sinon.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'pageUrl') { + return 'https://www.spotx.tv'; + } + return origGetConfig.apply(config, arguments); + }); + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.site.page).to.equal('https://example.com'); + config.getConfig.restore(); + }); + + it('should use pageUrl from config if page param is not passed', function() { + var request; + + var origGetConfig = config.getConfig; + sinon.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'pageUrl') { + return 'https://www.spotx.tv'; + } + return origGetConfig.apply(config, arguments); + }); + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.site.page).to.equal('https://www.spotx.tv'); + config.getConfig.restore(); + }); + + it('should use refererInfo.referer if no page or pageUrl are passed', function() { + var request; + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.site.page).to.equal('prebid.js'); + }); }); describe('interpretResponse', function() { From ebb51fbdca68643e7f966e99c4bb8e9474b397b2 Mon Sep 17 00:00:00 2001 From: Monis Qadri Date: Mon, 28 Sep 2020 20:04:25 +0530 Subject: [PATCH 022/190] Media.net Analytics improvements (#5755) * medianetAnalyticsAdapter improvements * medianetAnalyticsAdapter improvements * review changes * fixed eslint Co-authored-by: monis.q --- modules/medianetAnalyticsAdapter.js | 281 +++++++++++++----- .../modules/medianetAnalyticsAdapter_spec.js | 39 +-- 2 files changed, 236 insertions(+), 84 deletions(-) diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index c76f695cc2b..35c9273f951 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -11,6 +11,7 @@ const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics const CONFIG_URL = 'https://prebid.media.net/rtb/prebid/analytics/config'; const EVENT_PIXEL_URL = 'https://qsearch-a.akamaihd.net/log'; const DEFAULT_LOGGING_PERCENT = 50; +const ANALYTICS_VERSION = '1.0.0'; const PRICE_GRANULARITY = { 'auto': 'pbAg', @@ -39,6 +40,18 @@ const CONFIG_ERROR = 3; const VALID_URL_KEY = ['canonical_url', 'og_url', 'twitter_url']; const DEFAULT_URL_KEY = 'page'; +const LOG_TYPE = { + AP: 'AP', + PR: 'PR', + APPR: 'APPR', + RA: 'RA' +}; + +const BATCHING = { + SINGLE: 'SINGLE', + MULTI: 'MULTI' +} + let auctions = {}; let config; let pageDetails; @@ -73,7 +86,14 @@ class Configure { this.urlToConsume = DEFAULT_URL_KEY; this.debug = false; this.gdprConsent = undefined; + this.gdprApplies = undefined; this.uspConsent = undefined; + this.pixelWaitTime = 0; + this.apLoggingPct = 0; + this.prLoggingPct = 0; + this.batching = BATCHING.SINGLE; + this.shouldBeLogged = {}; + this.mnetDebugConfig = ''; } set publisherLper(plper) { @@ -85,10 +105,12 @@ class Configure { cid: this.cid, lper: Math.round(100 / this.loggingPercent), plper: this.pubLper, - gdpr: this.gdprConsent, + gdpr: this.gdprApplies ? '1' : '0', + gdprConsent: this.gdprConsent, ccpa: this.uspConsent, ajx: this.ajaxState, pbv: PREBID_VERSION, + pbav: ANALYTICS_VERSION, flt: 1, } } @@ -100,10 +122,9 @@ class Configure { _parseResponse(response) { try { response = JSON.parse(response); - if (isNaN(response.percentage)) { - throw new Error('not a number'); - } - this.loggingPercent = response.percentage; + this.setDataFromResponse(response); + this.overrideDomainLevelData(response); + this.overrideToDebug(this.mnetDebugConfig); this.urlToConsume = VALID_URL_KEY.includes(response.urlKey) ? response.urlKey : this.urlToConsume; this.ajaxState = CONFIG_PASS; } catch (e) { @@ -113,6 +134,41 @@ class Configure { } } + setDataFromResponse(response) { + if (!isNaN(parseInt(response.percentage, 10))) { + this.loggingPercent = response.percentage; + } + + if (!isNaN(parseInt(response.pixelwaittime, 10))) { + this.pixelWaitTime = response.pixelwaittime; + } + + if (!isNaN(parseInt(response.aplper, 10))) { + this.apLoggingPct = response.aplper; + this.batching = BATCHING.MULTI; + } + + if (!isNaN(parseInt(response.prlper, 10))) { + this.prLoggingPct = response.prlper; + this.batching = BATCHING.MULTI; + } + } + + overrideDomainLevelData(response) { + const domain = utils.deepAccess(response, 'domain.' + pageDetails.domain); + if (domain) { + this.setDataFromResponse(domain); + } + } + + overrideToDebug(response) { + if (response === '') return; + try { + this.setDataFromResponse(JSON.parse(decodeURIComponent(response))); + } catch (e) { + } + } + _errorFetch() { this.ajaxState = CONFIG_ERROR; /* eslint no-new: "error" */ @@ -128,6 +184,9 @@ class Configure { this.debug = true; return; } + if (utils.deepAccess(urlObj, 'search.mnet_setconfig')) { + this.mnetDebugConfig = utils.deepAccess(urlObj, 'search.mnet_setconfig'); + } ajax( this._configURL(), { @@ -145,7 +204,7 @@ class PageDetail { const twitterUrl = this._getUrlFromSelector('meta[name="twitter:url"]', 'content'); const refererInfo = getRefererInfo(); - this.domain = URL.parseUrl(refererInfo.referer).host; + this.domain = URL.parseUrl(refererInfo.referer).hostname; this.page = refererInfo.referer; this.is_top = refererInfo.reachedTop; this.referrer = this._getTopWindowReferrer(); @@ -202,39 +261,34 @@ class PageDetail { } class AdSlot { - constructor(mediaTypes, allMediaTypeSizes, tmax, supplyAdCode, adext, context, adSize) { - this.mediaTypes = mediaTypes; - this.allMediaTypeSizes = allMediaTypeSizes; + constructor(tmax, supplyAdCode, context, adext) { this.tmax = tmax; this.supplyAdCode = supplyAdCode; + this.context = context; this.adext = adext; - this.logged = false; + this.logged = {}; + this.logged[LOG_TYPE.PR] = false; + this.logged[LOG_TYPE.AP] = false; + this.logged[LOG_TYPE.APPR] = false; this.targeting = undefined; this.medianetPresent = 0; - // shouldBeLogged is assigned when requested, - // since we are waiting for logging percent response - this.shouldBeLogged = undefined; - this.context = context; - this.adSize = adSize; // old ad unit sizes } - getShouldBeLogged() { - if (this.shouldBeLogged === undefined) { - this.shouldBeLogged = isSampled(); + getShouldBeLogged(logType) { + if (!config.shouldBeLogged.hasOwnProperty(logType)) { + config.shouldBeLogged[logType] = isSampled(logType); } - return this.shouldBeLogged; + config.shouldBeLogged[logType] = isSampled(logType); + return config.shouldBeLogged[logType]; } getLoggingData() { return Object.assign({ supcrid: this.supplyAdCode, - mediaTypes: this.mediaTypes && this.mediaTypes.join('|'), - szs: this.allMediaTypeSizes.map(sz => sz.join('x')).join('|'), tmax: this.tmax, targ: JSON.stringify(this.targeting), ismn: this.medianetPresent, vplcmtt: this.context, - sz2: this.adSize.map(sz => sz.join('x')).join('|'), }, this.adext && {'adext': JSON.stringify(this.adext)}, ); @@ -242,12 +296,13 @@ class AdSlot { } class Bid { - constructor(bidId, bidder, src, start, supplyAdCode) { + constructor(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes) { this.bidId = bidId; this.bidder = bidder; this.src = src; this.start = start; - this.supplyAdCode = supplyAdCode; + this.adUnitCode = adUnitCode; + this.allMediaTypeSizes = allMediaTypeSizes; this.iwb = 0; this.winner = 0; this.status = bidder === DUMMY_BIDDER ? BID_SUCCESS : BID_TIMEOUT; @@ -257,7 +312,7 @@ class Bid { this.dfpbd = undefined; this.width = undefined; this.height = undefined; - this.mediaType = undefined; + this.mediaType = mediaType; this.timeToRespond = undefined; this.dealId = undefined; this.creativeId = undefined; @@ -268,6 +323,7 @@ class Bid { this.mpvid = undefined; this.floorPrice = undefined; this.floorRule = undefined; + this.serverLatencyMillis = undefined; } get size() { @@ -286,6 +342,7 @@ class Bid { bdp: this.cpm, cbdp: this.dfpbd, dfpbd: this.dfpbd, + szs: this.allMediaTypeSizes.map(sz => sz.join('x')).join('|'), size: this.size, mtype: this.mediaType, dId: this.dealId, @@ -299,7 +356,8 @@ class Bid { mpvid: this.mpvid, bidflr: this.floorPrice, flrrule: this.floorRule, - ext: JSON.stringify(this.ext) + ext: JSON.stringify(this.ext), + rtime: this.serverLatencyMillis, } } } @@ -342,12 +400,10 @@ class Auction { } } - addSlot(supplyAdCode, { mediaTypes, allMediaTypeSizes, tmax, adext, context, adSize }) { - if (supplyAdCode && this.adSlots[supplyAdCode] === undefined) { - this.adSlots[supplyAdCode] = new AdSlot(mediaTypes, allMediaTypeSizes, tmax, supplyAdCode, adext, context, adSize); - this.addBid( - new Bid('-1', DUMMY_BIDDER, 'client', '-1', supplyAdCode) - ); + addSlot({ adUnitCode, supplyAdCode, mediaTypes, allMediaTypeSizes, tmax, adext, context }) { + if (adUnitCode && this.adSlots[adUnitCode] === undefined) { + this.adSlots[adUnitCode] = new AdSlot(tmax, supplyAdCode, context, adext); + this.addBid(new Bid('-1', DUMMY_BIDDER, 'client', '-1', adUnitCode, mediaTypes, allMediaTypeSizes)); } } @@ -363,7 +419,7 @@ class Auction { getAdslotBids(adslot) { return this.bids - .filter((bid) => bid.supplyAdCode === adslot) + .filter((bid) => bid.adUnitCode === adslot) .map((bid) => bid.getLoggingData()); } @@ -382,46 +438,68 @@ class Auction { } } -function auctionInitHandler({auctionId, timestamp, bidderRequests}) { +function auctionInitHandler({auctionId, adUnits, timeout, timestamp, bidderRequests}) { if (auctionId && auctions[auctionId] === undefined) { auctions[auctionId] = new Auction(auctionId); auctions[auctionId].auctionInitTime = timestamp; } + addAddSlots(auctionId, adUnits, timeout); const floorData = utils.deepAccess(bidderRequests, '0.bids.0.floorData'); if (floorData) { auctions[auctionId].floorData = {...floorData}; } } -function bidRequestedHandler({ auctionId, auctionStart, bids, start, timeout, uspConsent, gdpr }) { +function addAddSlots(auctionId, adUnits, tmax) { + adUnits = adUnits || []; + const groupedAdUnits = utils.groupBy(adUnits, 'code'); + Object.keys(groupedAdUnits).forEach((adUnitCode) => { + const adUnits = groupedAdUnits[adUnitCode]; + const supplyAdCode = utils.deepAccess(adUnits, '0.adUnitCode') || adUnitCode; + let context = ''; + let adext = {}; + + const mediaTypeMap = {}; + const oSizes = {banner: [], video: []}; + adUnits.forEach(({mediaTypes, sizes, ext}) => { + mediaTypes = mediaTypes || {}; + adext = Object.assign(adext, ext || utils.deepAccess(mediaTypes, 'banner.ext')); + context = utils.deepAccess(mediaTypes, 'video.context') || context; + Object.keys(mediaTypes).forEach((mediaType) => mediaTypeMap[mediaType] = 1); + const sizeObject = _getSizes(mediaTypes, sizes); + sizeObject.banner.forEach(size => oSizes.banner.push(size)); + sizeObject.video.forEach(size => oSizes.video.push(size)); + }); + + adext = utils.isEmpty(adext) ? undefined : adext; + oSizes.banner = oSizes.banner.filter(utils.uniques); + oSizes.video = oSizes.video.filter(utils.uniques); + oSizes.native = mediaTypeMap.native === 1 ? [[1, 1]] : []; + const allMediaTypeSizes = [].concat(oSizes.banner, oSizes.native, oSizes.video); + const mediaTypes = Object.keys(mediaTypeMap).join('|'); + + auctions[auctionId].addSlot({adUnitCode, supplyAdCode, mediaTypes, allMediaTypeSizes, context, tmax, adext}); + }); +} + +function bidRequestedHandler({ auctionId, auctionStart, bids, start, uspConsent, gdpr }) { if (!(auctions[auctionId] instanceof Auction)) { return; } - if (gdpr && gdpr.gdprApplies) { + config.gdprApplies = !!(gdpr && gdpr.gdprApplies); + if (config.gdprApplies) { config.gdprConsent = gdpr.consentString || ''; } config.uspConsent = config.uspConsent || uspConsent; + auctions[auctionId].auctionStartTime = auctionStart; bids.forEach(bid => { - const { adUnitCode, bidder, mediaTypes, sizes, bidId, src } = bid; - if (!auctions[auctionId].adSlots[adUnitCode]) { - auctions[auctionId].auctionStartTime = auctionStart; - const sizeObject = _getSizes(mediaTypes, sizes); - auctions[auctionId].addSlot( - adUnitCode, - Object.assign({}, - (mediaTypes instanceof Object) && { mediaTypes: Object.keys(mediaTypes) }, - { allMediaTypeSizes: [].concat(sizeObject.banner, sizeObject.native, sizeObject.video) }, - { adext: utils.deepAccess(mediaTypes, 'banner.ext') || '' }, - { tmax: timeout }, - { context: utils.deepAccess(mediaTypes, 'video.context') || '' }, - { adSize: sizeObject.banner } - ) - ); - } - let bidObj = new Bid(bidId, bidder, src, start, adUnitCode); + const { adUnitCode, bidder, bidId, src, mediaTypes, sizes } = bid; + const sizeObject = _getSizes(mediaTypes, sizes); + const requestSizes = [].concat(sizeObject.banner, sizeObject.native, sizeObject.video); + const bidObj = new Bid(bidId, bidder, src, start, adUnitCode, mediaTypes && Object.keys(mediaTypes).join('|'), requestSizes); auctions[auctionId].addBid(bidObj); if (bidder === MEDIANET_BIDDER_CODE) { bidObj.crid = utils.deepAccess(bid, 'params.crid'); @@ -482,6 +560,9 @@ function bidResponseHandler(bid) { bid.ext.crid && { 'crid': bid.ext.crid } ); } + if (typeof bid.serverResponseTimeMs !== 'undefined') { + bidObj.serverLatencyMillis = bid.serverResponseTimeMs; + } } function noBidResponseHandler({ auctionId, bidId }) { @@ -511,12 +592,18 @@ function bidTimeoutHandler(timedOutBids) { }) } -function auctionEndHandler({ auctionId, auctionEnd }) { +function auctionEndHandler({auctionId, auctionEnd, adUnitCodes}) { if (!(auctions[auctionId] instanceof Auction)) { return; } auctions[auctionId].status = AUCTION_COMPLETED; auctions[auctionId].auctionEndTime = auctionEnd; + + if (config.batching === BATCHING.MULTI) { + adUnitCodes.forEach(function (adUnitCode) { + sendEvent(auctionId, adUnitCode, LOG_TYPE.PR); + }); + } } function setTargetingHandler(params) { @@ -530,20 +617,51 @@ function setTargetingHandler(params) { adunitObj.targeting = params[adunit]; auctionObj.setTargetingTime = Date.now(); let targetingObj = Object.keys(params[adunit]).reduce((result, key) => { - if (key.indexOf('hb_adid') !== -1) { + if (key.indexOf(CONSTANTS.TARGETING_KEYS.AD_ID) !== -1) { result[key] = params[adunit][key] } return result; }, {}); + const winnerAdId = params[adunit][CONSTANTS.TARGETING_KEYS.AD_ID]; + let winningBid; let bidAdIds = Object.keys(targetingObj).map(k => targetingObj[k]); auctionObj.bids.filter((bid) => bidAdIds.indexOf(bid.adId) !== -1).map(function(bid) { bid.iwb = 1; + if (bid.adId === winnerAdId) { + winningBid = bid; + } + }); + auctionObj.bids.forEach(bid => { + if (bid.bidder === DUMMY_BIDDER && bid.adUnitCode === adunit) { + bid.iwb = bidAdIds.length === 0 ? 0 : 1; + bid.width = utils.deepAccess(winningBid, 'width'); + bid.height = utils.deepAccess(winningBid, 'height'); + } }); - sendEvent(auctionId, adunit, false); + sendEvent(auctionId, adunit, getLogType()); } } } +function getLogType() { + if (config.batching === BATCHING.SINGLE) { + return LOG_TYPE.APPR; + } + return LOG_TYPE.AP; +} + +function setBidderDone(params) { + if (config.pixelWaitTime != null && config.pixelWaitTime > 0) { + setTimeout(fireApAfterWait, config.pixelWaitTime, params) + } +} + +function fireApAfterWait(params) { + params.bids.forEach(function (adUnit) { + sendEvent(params.auctionId, adUnit.adUnitCode, LOG_TYPE.AP); + }); +} + function bidWonHandler(bid) { const { requestId, auctionId, adUnitCode } = bid; if (!(auctions[auctionId] instanceof Auction)) { @@ -555,26 +673,50 @@ function bidWonHandler(bid) { } auctions[auctionId].bidWonTime = Date.now(); bidObj.winner = 1; - sendEvent(auctionId, adUnitCode, true); + sendEvent(auctionId, adUnitCode, LOG_TYPE.RA); +} + +function isSampled(logType) { + return Math.random() * 100 < parseFloat(getLogPercentage(logType)); } -function isSampled() { - return Math.random() * 100 < parseFloat(config.loggingPercent); +function getLogPercentage(logType) { + let logPercentage = config.loggingPercent; + if (config.batching === BATCHING.MULTI) { + if (logType === LOG_TYPE.AP) { + logPercentage = config.apLoggingPct; + } else if (logType === LOG_TYPE.PR) { + logPercentage = config.prLoggingPct; + } + } + return logPercentage; } function isValidAuctionAdSlot(acid, adtag) { return (auctions[acid] instanceof Auction) && (auctions[acid].adSlots[adtag] instanceof AdSlot); } -function sendEvent(id, adunit, isBidWonEvent) { +function sendEvent(id, adunit, logType) { if (!isValidAuctionAdSlot(id, adunit)) { return; } - if (isBidWonEvent) { - fireAuctionLog(id, adunit, isBidWonEvent); - } else if (auctions[id].adSlots[adunit].getShouldBeLogged() && !auctions[id].adSlots[adunit].logged) { - auctions[id].adSlots[adunit].logged = true; - fireAuctionLog(id, adunit, isBidWonEvent); + if (logType === LOG_TYPE.RA) { + fireAuctionLog(id, adunit, logType); + } else { + fireApPrLog(id, adunit, logType) + } +} + +function fireApPrLog(auctionId, adUnitName, logType) { + const adSlot = auctions[auctionId].adSlots[adUnitName]; + if (adSlot.getShouldBeLogged(logType) && !adSlot.logged[logType]) { + if (config.batching === BATCHING.SINGLE) { + adSlot.logged[LOG_TYPE.AP] = true; + adSlot.logged[LOG_TYPE.PR] = true; + } else { + adSlot.logged[logType] = true; + } + fireAuctionLog(auctionId, adUnitName, logType); } } @@ -585,8 +727,9 @@ function getCommonLoggingData(acid, adtag) { return Object.assign(commonParams, adunitParams, auctionParams); } -function fireAuctionLog(acid, adtag, isBidWonEvent) { +function fireAuctionLog(acid, adtag, logType) { let commonParams = getCommonLoggingData(acid, adtag); + commonParams.lgtp = logType; let targeting = utils.deepAccess(commonParams, 'targ'); Object.keys(commonParams).forEach((key) => (commonParams[key] == null) && delete commonParams[key]); @@ -594,7 +737,7 @@ function fireAuctionLog(acid, adtag, isBidWonEvent) { let bidParams; - if (isBidWonEvent) { + if (logType === LOG_TYPE.RA) { bidParams = auctions[acid].getWinnerAdslotBid(adtag); commonParams.lper = 1; } else { @@ -700,6 +843,10 @@ let medianetAnalytics = Object.assign(adapter({URL, analyticsType}), { setTargetingHandler(args); break; } + case CONSTANTS.EVENTS.BIDDER_DONE : { + setBidderDone(args); + break; + } case CONSTANTS.EVENTS.BID_WON: { bidWonHandler(args); break; diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index 97b45cef00c..41a6338225e 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -9,20 +9,23 @@ const { } = CONSTANTS; const MOCK = { - AUCTION_INIT: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739}, - AUCTION_INIT_WITH_FLOOR: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'bidderRequests': [{'bids': [{ 'floorData': {'enforcements': {'enforceJS': true}} }]}]}, + Ad_Units: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'bids': [], 'ext': {'prop1': 'value1'}}], + MULTI_FORMAT_TWIN_AD_UNITS: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'native': {'image': {'required': true, 'sizes': [150, 50]}}}, 'bids': [], 'ext': {'prop1': 'value1'}}, {'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'video': {'playerSize': [640, 480], 'context': 'instream'}}, 'bids': [], 'ext': {'prop1': 'value1'}}], + AUCTION_INIT: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'timeout': 6000}, + AUCTION_INIT_WITH_FLOOR: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'timeout': 6000, 'bidderRequests': [{'bids': [{ 'floorData': {'enforcements': {'enforceJS': true}} }]}]}, BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, - MULTI_FORMAT_BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'video': {'playerSize': [640, 480], 'context': 'instream'}, 'native': {'image': {'required': true, 'sizes': [150, 50]}, 'title': {'required': true, 'len': 80}}, 'ext': ['asdads']}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, + MULTI_FORMAT_BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'video': {'playerSize': [640, 480], 'context': 'instream'}, 'native': {'image': {'required': true, 'sizes': [150, 50]}, 'title': {'required': true, 'len': 80}}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, BID_RESPONSE: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, AUCTION_END: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'auctionEnd': 1584563605739}, SET_TARGETING: {'div-gpt-ad-1460505748561-0': {'prebid_test': '1', 'hb_format': 'banner', 'hb_source': 'client', 'hb_size': '300x250', 'hb_pb': '2.00', 'hb_adid': '3e6e4bce5c8fb3', 'hb_bidder': 'medianet', 'hb_format_medianet': 'banner', 'hb_source_medianet': 'client', 'hb_size_medianet': '300x250', 'hb_pb_medianet': '2.00', 'hb_adid_medianet': '3e6e4bce5c8fb3', 'hb_bidder_medianet': 'medianet'}}, + NO_BID_SET_TARGETING: {'div-gpt-ad-1460505748561-0': {}}, BID_WON: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, NO_BID: {'bidder': 'medianet', 'params': {'cid': 'test123', 'crid': '451466393', 'site': {}}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '303fa0c6-682f-4aea-8e4a-dc68f0d5c7d5', 'sizes': [[300, 250], [300, 600]], 'bidId': '28248b0e6aece2', 'bidderRequestId': '13fccf3809fe43', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}, BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}] } function performAuctionWithFloorConfig() { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT_WITH_FLOOR); + events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT_WITH_FLOOR, adUnits: MOCK.Ad_Units}); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); events.emit(AUCTION_END, MOCK.AUCTION_END); @@ -31,7 +34,7 @@ function performAuctionWithFloorConfig() { } function performStandardAuctionWithWinner() { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT, adUnits: MOCK.Ad_Units}); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); events.emit(AUCTION_END, MOCK.AUCTION_END); @@ -40,27 +43,27 @@ function performStandardAuctionWithWinner() { } function performMultiFormatAuctionWithNoBid() { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT, adUnits: MOCK.MULTI_FORMAT_TWIN_AD_UNITS}); events.emit(BID_REQUESTED, MOCK.MULTI_FORMAT_BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(NO_BID, MOCK.NO_BID); events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); } function performStandardAuctionWithNoBid() { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT, adUnits: MOCK.Ad_Units}); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(NO_BID, MOCK.NO_BID); events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); } function performStandardAuctionWithTimeout() { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(AUCTION_INIT, {...MOCK.AUCTION_INIT, adUnits: MOCK.Ad_Units}); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); } function getQueryData(url) { @@ -123,8 +126,10 @@ describe('Media.net Analytics Adapter', function() { cid: 'test123' } }); + medianetAnalytics.clearlogsQueue(); }); afterEach(function () { + medianetAnalytics.clearlogsQueue(); medianetAnalytics.disableAnalytics(); }); @@ -139,10 +144,9 @@ describe('Media.net Analytics Adapter', function() { performMultiFormatAuctionWithNoBid(); const noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log))[0]; medianetAnalytics.clearlogsQueue(); - - expect(noBidLog.szs).to.equal(encodeURIComponent('300x250|1x1|640x480')); + expect(noBidLog.mtype).to.have.ordered.members([encodeURIComponent('banner|native|video'), encodeURIComponent('banner|video|native')]); + expect(noBidLog.szs).to.have.ordered.members([encodeURIComponent('300x250|1x1|640x480'), encodeURIComponent('300x250|1x1|640x480')]); expect(noBidLog.vplcmtt).to.equal('instream'); - expect(noBidLog.sz2).to.equal(encodeURIComponent('300x250')); }); it('should have winner log in standard auction', function() { @@ -167,6 +171,7 @@ describe('Media.net Analytics Adapter', function() { src: 'client', size: '300x250', mtype: 'banner', + gdpr: '0', cid: 'test123', lper: '1', ogbdp: '1.1495', @@ -205,7 +210,7 @@ describe('Media.net Analytics Adapter', function() { expect(noBidLog.status).to.have.ordered.members(['1', '2']); expect(noBidLog.src).to.have.ordered.members(['client', 'client']); expect(noBidLog.curr).to.have.ordered.members(['', '']); - expect(noBidLog.mtype).to.have.ordered.members(['', '']); + expect(noBidLog.mtype).to.have.ordered.members(['banner', 'banner']); expect(noBidLog.ogbdp).to.have.ordered.members(['', '']); expect(noBidLog.mpvid).to.have.ordered.members(['', '']); expect(noBidLog.crid).to.have.ordered.members(['', '451466393']); @@ -223,7 +228,7 @@ describe('Media.net Analytics Adapter', function() { expect(timeoutLog.status).to.have.ordered.members(['1', '3']); expect(timeoutLog.src).to.have.ordered.members(['client', 'client']); expect(timeoutLog.curr).to.have.ordered.members(['', '']); - expect(timeoutLog.mtype).to.have.ordered.members(['', '']); + expect(timeoutLog.mtype).to.have.ordered.members(['banner', 'banner']); expect(timeoutLog.ogbdp).to.have.ordered.members(['', '']); expect(timeoutLog.mpvid).to.have.ordered.members(['', '']); expect(timeoutLog.crid).to.have.ordered.members(['', '451466393']); From c696e0019d3120e1c2b4d96ba16f263c3ee1c0f2 Mon Sep 17 00:00:00 2001 From: Olivier Date: Mon, 28 Sep 2020 17:45:57 +0200 Subject: [PATCH 023/190] adagio Bid Adapter: add support for CCPA, COPPA (#5749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément besse --- modules/adagioBidAdapter.js | 21 ++++++- test/spec/modules/adagioBidAdapter_spec.js | 71 ++++++++++++++++++++-- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index b4c2a6ac0d6..65a35284d1b 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,5 +1,6 @@ import find from 'core-js-pure/features/array/find.js'; import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { loadExternalScript } from '../src/adloader.js' import JSEncrypt from 'jsencrypt/bin/jsencrypt.js'; @@ -9,7 +10,7 @@ import { getRefererInfo } from '../src/refererDetection.js'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.3.0'; +export const VERSION = '2.4.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; export const SUPPORTED_MEDIA_TYPES = ['banner']; @@ -555,6 +556,16 @@ function _getGdprConsent(bidderRequest) { return consent; } +function _getCoppa() { + return { + required: config.getConfig('coppa') === true ? 1 : 0 + }; +} + +function _getUspConsent(bidderRequest) { + return (utils.deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; +} + function _getSchain(bidRequest) { if (utils.deepAccess(bidRequest, 'schain')) { return bidRequest.schain; @@ -643,6 +654,8 @@ export const spec = { const site = internal.getSite(bidderRequest); const pageviewId = internal.getPageviewId(); const gdprConsent = _getGdprConsent(bidderRequest) || {}; + const uspConsent = _getUspConsent(bidderRequest) || {}; + const coppa = _getCoppa(); const schain = _getSchain(validBidRequests[0]); const adUnits = utils._map(validBidRequests, (bidRequest) => { bidRequest.features = internal.getFeatures(bidRequest, bidderRequest); @@ -672,7 +685,11 @@ export const spec = { site: site, pageviewId: pageviewId, adUnits: groupedAdUnits[organizationId], - gdpr: gdprConsent, + regs: { + gdpr: gdprConsent, + coppa: coppa, + ccpa: uspConsent + }, schain: schain, prebidVersion: '$prebid.version$', adapterVersion: VERSION, diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 52b99f274d5..a18cd797d68 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { _features, internal as adagio, adagioScriptFromLocalStorageCb, getAdagioScript, storage, spec, ENDPOINT, VERSION } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; +import { config } from 'src/config.js'; const BidRequestBuilder = function BidRequestBuilder(options) { const defaults = { @@ -285,7 +286,7 @@ describe('Adagio bid adapter', () => { 'site', 'pageviewId', 'adUnits', - 'gdpr', + 'regs', 'schain', 'prebidVersion', 'adapterVersion', @@ -450,7 +451,7 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.gdpr).to.deep.equal(expected); + expect(requests[0].data.regs.gdpr).to.deep.equal(expected); }); it('send data.gdpr object to the server from TCF v.2 cmp', function() { @@ -466,7 +467,7 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.gdpr).to.deep.equal(expected); + expect(requests[0].data.regs.gdpr).to.deep.equal(expected); }); }); @@ -485,7 +486,7 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.gdpr).to.deep.equal(expected); + expect(requests[0].data.regs.gdpr).to.deep.equal(expected); }); it('send data.gdpr object to the server from TCF v.2 cmp', function() { @@ -501,7 +502,7 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.gdpr).to.deep.equal(expected); + expect(requests[0].data.regs.gdpr).to.deep.equal(expected); }); }); @@ -510,10 +511,68 @@ describe('Adagio bid adapter', () => { const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.gdpr).to.be.empty; + expect(requests[0].data.regs.gdpr).to.be.empty; }); }); }); + + describe('with COPPA', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + const bidderRequest = new BidderRequestBuilder().build(); + + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.regs.coppa.required).to.equal(1); + + config.getConfig.restore(); + }); + }); + + describe('without COPPA', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + + it('should send the Coppa "required" flag set to "0" in the request', function () { + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.regs.coppa.required).to.equal(0); + }); + }); + + describe('with USPrivacy', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + + const consent = 'Y11N' + + it('should send the USPrivacy "ccpa.uspConsent" in the request', function () { + const bidderRequest = new BidderRequestBuilder({ + uspConsent: consent + }).build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.regs.ccpa.uspConsent).to.equal(consent); + }); + }); + + describe('without USPrivacy', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + + it('should have an empty "ccpa" field in the request', function () { + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.regs.ccpa).to.be.empty; + }); + }); }); describe('interpretResponse()', function() { From a054aa09b35cc1bd19ccdb2201d5e83d23848e10 Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Mon, 28 Sep 2020 10:56:10 -0700 Subject: [PATCH 024/190] PubMatic analytics adapter: Not passing GDPR information (#5791) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * not passing GDPR data in analytics --- modules/pubmaticAnalyticsAdapter.js | 8 -------- test/spec/modules/pubmaticAnalyticsAdapter_spec.js | 10 ++++------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index d94c098db5d..04c6606a299 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -228,13 +228,6 @@ function executeBidsLoggerCall(e, highestCpmBids) { return 0; })(); - // GDPR support - if (auctionCache.gdprConsent) { - outputObj['cns'] = auctionCache.gdprConsent.consentString || ''; - outputObj['gdpr'] = auctionCache.gdprConsent.gdprApplies === true ? 1 : 0; - pixelURL += '&gdEn=1'; - } - outputObj.s = Object.keys(auctionCache.adUnitCodes).reduce(function(slotsArray, adUnitId) { let adUnit = auctionCache.adUnitCodes[adUnitId]; let slotObject = { @@ -307,7 +300,6 @@ function auctionInitHandler(args) { } function bidRequestedHandler(args) { - cache.auctions[args.auctionId].gdprConsent = args.gdprConsent || undefined; args.bids.forEach(function(bid) { if (!cache.auctions[args.auctionId].adUnitCodes.hasOwnProperty(bid.adUnitCode)) { cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode] = { diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 537ba4483cf..d38037f40a1 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -316,7 +316,7 @@ describe('pubmatic analytics adapter', function () { clock.tick(2000 + 1000); expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker let request = requests[2]; // logger is executed late, trackers execute first - expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); expect(data.pubid).to.equal('9999'); expect(data.pid).to.equal('1111'); @@ -326,8 +326,6 @@ describe('pubmatic analytics adapter', function () { expect(data.purl).to.equal('http://www.test.com/page.html'); expect(data.orig).to.equal('www.test.com'); expect(data.tst).to.equal(1519767016); - expect(data.cns).to.equal('here-goes-gdpr-consent-string'); - expect(data.gdpr).to.equal(1); expect(data.tgid).to.equal(15); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -423,7 +421,7 @@ describe('pubmatic analytics adapter', function () { clock.tick(2000 + 1000); expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker let request = requests[2]; // logger is executed late, trackers execute first - expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); expect(data.pubid).to.equal('9999'); expect(data.pid).to.equal('1111'); @@ -490,7 +488,7 @@ describe('pubmatic analytics adapter', function () { clock.tick(2000 + 1000); expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker let request = requests[2]; // logger is executed late, trackers execute first - expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); expect(data.pubid).to.equal('9999'); expect(data.pid).to.equal('1111'); @@ -672,7 +670,7 @@ describe('pubmatic analytics adapter', function () { clock.tick(2000 + 1000); expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker let request = requests[2]; // logger is executed late, trackers execute first - expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999&gdEn=1'); + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); From 8168453bc4fd6433c0b21dae0edafa91685f7f70 Mon Sep 17 00:00:00 2001 From: susyt Date: Mon, 28 Sep 2020 21:07:29 -0700 Subject: [PATCH 025/190] GumGum: adds support for new field - iriscat (#5790) * adds support for zone and pubId params * adds support for iriscat field --- modules/gumgumBidAdapter.js | 4 ++++ test/spec/modules/gumgumBidAdapter_spec.js | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 9714a3eeeca..3206b7e1727 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -269,6 +269,10 @@ function buildRequests (validBidRequests, bidderRequest) { data.fp = bidFloor; } + if (params.iriscat && typeof params.iriscat === 'string') { + data.iriscat = params.iriscat; + } + if (params.zone) { data.t = params.zone; data.pi = 2; // inscreen diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index f24fdc87e45..701ce9a7e81 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -252,6 +252,25 @@ describe('gumgumAdapter', function () { expect(bidRequest.data).to.include.any.keys('t'); expect(bidRequest.data).to.include.any.keys('fp'); }); + it('should set iriscat parameter if iriscat param is found and is of type string', function () { + const iriscat = 'segment'; + const request = { ...bidRequests[0] }; + request.params = { ...request.params, iriscat }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.iriscat).to.equal(iriscat); + }); + it('should not send iriscat parameter if iriscat param is not found', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.iriscat).to.be.undefined; + }); + it('should not send iriscat parameter if iriscat param is not of type string', function () { + const iriscat = 123; + const request = { ...bidRequests[0] }; + request.params = { ...request.params, iriscat }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.iriscat).to.be.undefined; + }); it('should send pubId if inScreenPubID param is specified', function () { const request = Object.assign({}, bidRequests[0]); delete request.params; From eb9cf3f3fbd39bf468a847c7fa547c0c1cc52e09 Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 29 Sep 2020 06:36:21 +0200 Subject: [PATCH 026/190] fix a few id5 docs (#5793) * update id5 eids value and add html storage example * html5, not html --- modules/userId/eids.md | 8 ++++++-- modules/userId/userId.md | 42 ++++++++++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 03aec46cf48..7dc149cd47a 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -1,7 +1,8 @@ ## Example of eids array generated by UserId Module. + ``` userIdAsEids = [ - { + { source: 'pubcid.org', uids: [{ id: 'some-random-id-value', @@ -25,6 +26,9 @@ userIdAsEids = [ uids: [{ id: 'some-random-id-value', atype: 1 + }, + ext: { + linkType: 2 }] }, @@ -91,7 +95,7 @@ userIdAsEids = [ uids: [{ id: 'some-random-id-value', atype: 1, - ext: { + ext: { third: 'some-random-id-value' } }] diff --git a/modules/userId/userId.md b/modules/userId/userId.md index a47ecd9f08c..a9ab6ccc483 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -26,7 +26,7 @@ pbjs.setConfig({ name: "id5Id", params: { partner: 173, // Set your real ID5 partner ID here for production, please ask for one at https://id5.io/universal-id - pd: "some-pd-string" // See https://wiki.id5.io/display/PD/Prebid.js+UserId+Module for details + pd: "some-pd-string" // See https://wiki.id5.io/x/BIAZ for details }, storage: { type: "cookie", @@ -43,7 +43,7 @@ pbjs.setConfig({ }, { name: 'identityLink', params: { - pid: '999' // Set your real identityLink placement ID here + pid: '999' // Set your real identityLink placement ID here }, storage: { type: 'cookie', @@ -53,7 +53,7 @@ pbjs.setConfig({ }, { name: 'liveIntentId', params: { - publisherId: '7798696' // Set an identifier of a publisher know to your systems + publisherId: '7798696' // Set an identifier of a publisher know to your systems }, storage: { type: 'cookie', @@ -102,7 +102,7 @@ pbjs.setConfig({ }, { name: 'identityLink', params: { - pid: '999' // Set your real identityLink placement ID here + pid: '999' // Set your real identityLink placement ID here }, storage: { type: 'html5', @@ -110,25 +110,37 @@ pbjs.setConfig({ expires: 30 } }, { - name: 'liveIntentId', - params: { - publisherId: '7798696' // Set an identifier of a publisher know to your systems - }, - storage: { - type: 'html5', - name: '_li_pbid', - expires: 60 - } + name: 'liveIntentId', + params: { + publisherId: '7798696' // Set an identifier of a publisher know to your systems + }, + storage: { + type: 'html5', + name: '_li_pbid', + expires: 60 + } }, { - name: 'sharedId', + name: 'sharedId', params: { syncTime: 60 // in seconds, default is 24 hours }, storage: { - type: 'cookie', + type: 'html5', name: 'sharedid', expires: 28 } + }, { + name: 'id5Id', + params: { + partner: 173, // Set your real ID5 partner ID here for production, please ask for one at https://id5.io/universal-id + pd: 'some-pd-string' // See https://wiki.id5.io/x/BIAZ for details + }, + storage: { + type: 'html5', + name: 'id5id.1st', + expires: 90, // Expiration of cookies in days + refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' + }, }], syncDelay: 5000 } From 739bad87a5cd46786e57cd082d342e5330f4cf2b Mon Sep 17 00:00:00 2001 From: YerkovichM <48519843+YerkovichM@users.noreply.github.com> Date: Tue, 29 Sep 2020 18:26:41 +0300 Subject: [PATCH 027/190] New PubProvided Id UserId Submodule (#5767) * PubProvided Module * - * formatting * formatting * Added rubiconBidAdapter support Added unit tests * formatting * formatting * formatting * formatting * commit to rerun build * type changes * type changes * type changes * Revert "type changes" This reverts commit af408b0a * Revert "type changes" This reverts commit af408b0a * formatting * formatting * formatting * formatting * formatting * Revert "type changes" This reverts commit 114005a5 * formatting * formatting * formatting * formatting * commit to rerun build * commit to rerun build * commit to rerun build * rubiconBidAdapter changes * rubiconBidAdapter changes * rubiconBidAdapter changes * trigger build * fix * fix * fix * rebuild Co-authored-by: myerkovich --- integrationExamples/gpt/userId_example.html | 24 ++ modules/pubProvidedSystem.js | 53 +++ modules/rubiconBidAdapter.js | 11 + modules/userId/eids.js | 10 +- test/spec/modules/eids_spec.js | 36 +- test/spec/modules/rubiconBidAdapter_spec.js | 164 +++++--- test/spec/modules/userId_spec.js | 416 +++++++++++++------- 7 files changed, 513 insertions(+), 201 deletions(-) create mode 100644 modules/pubProvidedSystem.js diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 8e69bc6c6a7..dddc0915db2 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -134,6 +134,30 @@ // }, userSync: { userIds: [{ + name: "pubProvidedId", + params: { + eids: [{ + source: "domain.com", + uids:[{ + id: "value read from cookie or local storage", + atype: 1, + ext: { + stype: "ppuid" // allowable options are sha256email, DMP, ppuid for now + } + }] + },{ + source: "3rdpartyprovided.com", + uids:[{ + id: "value read from cookie or local storage", + atype: 3, + ext: { + stype: "sha256email" + } + }] + }], + eidsFunction: getHashedEmail // any user defined function that exists in the page + } + },{ name: "unifiedId", params: { partner: "prebid", diff --git a/modules/pubProvidedSystem.js b/modules/pubProvidedSystem.js new file mode 100644 index 00000000000..575633e622f --- /dev/null +++ b/modules/pubProvidedSystem.js @@ -0,0 +1,53 @@ +/** + * This module adds Publisher Provided ids support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/pubProvidedSystem + * @requires module:modules/userId + */ + +import {submodule} from '../src/hook.js'; +import * as utils from '../src/utils.js'; + +const MODULE_NAME = 'pubProvidedId'; + +/** @type {Submodule} */ +export const pubProvidedIdSubmodule = { + + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid request + * @function + * @param {string} value + * @returns {{pubProvidedId: array}} or undefined if value doesn't exists + */ + decode(value) { + const res = value ? {pubProvidedId: value} : undefined; + utils.logInfo('PubProvidedId: Decoded value ' + JSON.stringify(res)); + return res; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleParams} [configParams] + * @returns {{id: array}} + */ + getId(configParams) { + let res = []; + if (utils.isArray(configParams.eids)) { + res = res.concat(configParams.eids); + } + if (typeof configParams.eidsFunction === 'function') { + res = res.concat(configParams.eidsFunction()); + } + return {id: res}; + } +}; + +// Register submodule for userId +submodule('userId', pubProvidedIdSubmodule); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 9d4b6203a86..18d7973d9fa 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -559,6 +559,17 @@ export const spec = { const configUserId = config.getConfig('user.id'); if (configUserId) { data['ppuid'] = configUserId; + } else { + // if config.getConfig('user.id') doesn't return anything, then look for the first eid.uids[*].ext.stype === 'ppuid' + for (let i = 0; bidRequest.userIdAsEids && i < bidRequest.userIdAsEids.length; i++) { + if (bidRequest.userIdAsEids[i].uids) { + const pubProvidedId = find(bidRequest.userIdAsEids[i].uids, uid => uid.ext && uid.ext.stype === 'ppuid'); + if (pubProvidedId && pubProvidedId.id) { + data['ppuid'] = pubProvidedId.id; + break; + } + } + } } if (bidderRequest.gdprConsent) { diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 9e39b548a57..4e4576dbb14 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -190,9 +190,13 @@ export function createEidsArray(bidRequestUserId) { let eids = []; for (const subModuleKey in bidRequestUserId) { if (bidRequestUserId.hasOwnProperty(subModuleKey)) { - const eid = createEidObject(bidRequestUserId[subModuleKey], subModuleKey); - if (eid) { - eids.push(eid); + if (subModuleKey === 'pubProvidedId') { + eids = eids.concat(bidRequestUserId['pubProvidedId']); + } else { + const eid = createEidObject(bidRequestUserId[subModuleKey], subModuleKey); + if (eid) { + eids.push(eid); + } } } } diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 830f542ef40..26dbad2f153 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -261,8 +261,42 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + it('pubProvidedId', function() { + const userId = { + pubProvidedId: [{ + source: 'example.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'ppuid' + } + }] + }, { + source: 'id-partner.com', + uids: [{ + id: 'value read from cookie or local storage' + }] + }] + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(2); + expect(newEids[0]).to.deep.equal({ + source: 'example.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'ppuid' + } + }] + }); + expect(newEids[1]).to.deep.equal({ + source: 'id-partner.com', + uids: [{ + id: 'value read from cookie or local storage' + }] + }); + }); }); - describe('Negative case', function() { it('eids array generation for UN-known sub-module', function() { // UnknownCommonId diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index f4e35517636..9417303da0e 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -4,7 +4,7 @@ import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import find from 'core-js-pure/features/array/find.js'; -import { createEidsArray } from 'modules/userId/eids.js'; +import {createEidsArray} from 'modules/userId/eids.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid const PBS_INTEGRATION = 'pbjs'; @@ -137,7 +137,7 @@ describe('the rubicon adapter', function () { 'targeting': [ { 'key': getProp('targeting_key', `rpfl_${i}`), - 'values': [ '43_tier_all_test' ] + 'values': ['43_tier_all_test'] } ] }; @@ -220,11 +220,25 @@ describe('the rubicon adapter', function () { 'size_id': 201, }; bid.userId = { - lipb: { lipbid: '0000-1111-2222-3333', segments: ['segA', 'segB'] }, + lipb: {lipbid: '0000-1111-2222-3333', segments: ['segA', 'segB']}, idl_env: '1111-2222-3333-4444', - sharedid: { id: '1111', third: '2222' }, + sharedid: {id: '1111', third: '2222'}, tdid: '3000', - pubcid: '4000' + pubcid: '4000', + pubProvidedId: [{ + source: 'example.com', + uids: [{ + id: '333333', + ext: { + stype: 'ppuid' + } + }] + }, { + source: 'id-partner.com', + uids: [{ + id: '4444444' + }] + }] }; bid.userIdAsEids = createEidsArray(bid.userId); bid.storedAuctionResponse = 11111; @@ -461,29 +475,29 @@ describe('the rubicon adapter', function () { data = parseQuery(request.data); expect(data.rp_hard_floor).to.equal('1.23'); }); - it('should not send p_pos to AE if not params.position specified', function() { - var noposRequest = utils.deepClone(bidderRequest); - delete noposRequest.bids[0].params.position; + it('should not send p_pos to AE if not params.position specified', function () { + var noposRequest = utils.deepClone(bidderRequest); + delete noposRequest.bids[0].params.position; - let [request] = spec.buildRequests(noposRequest.bids, noposRequest); - let data = parseQuery(request.data); + let [request] = spec.buildRequests(noposRequest.bids, noposRequest); + let data = parseQuery(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal(undefined); + expect(data['site_id']).to.equal('70608'); + expect(data['p_pos']).to.equal(undefined); }); - it('should not send p_pos to AE if not params.position is invalid', function() { - var badposRequest = utils.deepClone(bidderRequest); - badposRequest.bids[0].params.position = 'bad'; + it('should not send p_pos to AE if not params.position is invalid', function () { + var badposRequest = utils.deepClone(bidderRequest); + badposRequest.bids[0].params.position = 'bad'; - let [request] = spec.buildRequests(badposRequest.bids, badposRequest); - let data = parseQuery(request.data); + let [request] = spec.buildRequests(badposRequest.bids, badposRequest); + let data = parseQuery(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal(undefined); + expect(data['site_id']).to.equal('70608'); + expect(data['p_pos']).to.equal(undefined); }); - it('should correctly send p_pos in sra fashion', function() { + it('should correctly send p_pos in sra fashion', function () { sandbox.stub(config, 'getConfig').callsFake((key) => { const config = { 'rubicon.singleRequest': true @@ -519,11 +533,11 @@ describe('the rubicon adapter', function () { expect(data['p_pos']).to.equal('atf;;btf;;'); }); - it('should not send x_source.pchain to AE if params.pchain is not specified', function() { - var noPchainRequest = utils.deepClone(bidderRequest); - delete noPchainRequest.bids[0].params.pchain; + it('should not send x_source.pchain to AE if params.pchain is not specified', function () { + var noPchainRequest = utils.deepClone(bidderRequest); + delete noPchainRequest.bids[0].params.pchain; - let [request] = spec.buildRequests(noPchainRequest.bids, noPchainRequest); + let [request] = spec.buildRequests(noPchainRequest.bids, noPchainRequest); expect(request.data).to.contain('&site_id=70608&'); expect(request.data).to.not.contain('x_source.pchain'); }); @@ -624,7 +638,7 @@ describe('the rubicon adapter', function () { expect(parseQuery(request.data).rf).to.equal('localhost'); delete bidderRequest.bids[0].params.referrer; - let refererInfo = { referer: 'https://www.prebid.org' }; + let refererInfo = {referer: 'https://www.prebid.org'}; bidderRequest = Object.assign({refererInfo}, bidderRequest); [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(parseQuery(request.data).rf).to.equal('https://www.prebid.org'); @@ -1041,7 +1055,7 @@ describe('the rubicon adapter', function () { keywords: ['d'], gender: 'M', yob: '1984', - geo: { country: 'ca' } + geo: {country: 'ca'} }; sandbox.stub(config, 'getConfig').callsFake(key => { @@ -1315,7 +1329,7 @@ describe('the rubicon adapter', function () { }); }); - describe('user id config', function() { + describe('user id config', function () { it('should send tpid_tdid when userIdAsEids contains unifiedId', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { @@ -1391,9 +1405,36 @@ describe('the rubicon adapter', function () { }); }); + describe('pubProvidedId support', function () { + it('should send pubProvidedId when userIdAsEids contains pubProvidedId ids', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + pubProvidedId: [{ + source: 'example.com', + uids: [{ + id: '11111', + ext: { + stype: 'ppuid' + } + }] + }, { + source: 'id-partner.com', + uids: [{ + id: '222222' + }] + }] + }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['ppuid']).to.equal('11111'); + }); + }); + describe('Config user.id support', function () { it('should send ppuid when config defines user.id', function () { - config.setConfig({ user: { id: '123' } }); + config.setConfig({user: {id: '123'}}); const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { sharedid: { @@ -1710,7 +1751,7 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].bidfloor).to.be.undefined; }); - it('should add alias name to PBS Request', function() { + it('should add alias name to PBS Request', function () { createVideoBidderRequest(); bidderRequest.bidderCode = 'superRubicon'; @@ -1726,7 +1767,7 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); }); - it('should send correct bidfloor to PBS', function() { + it('should send correct bidfloor to PBS', function () { createVideoBidderRequest(); bidderRequest.bids[0].params.floor = 0.1; @@ -1974,7 +2015,7 @@ describe('the rubicon adapter', function () { keywords: ['d'], gender: 'M', yob: '1984', - geo: { country: 'ca' } + geo: {country: 'ca'} }; sandbox.stub(config, 'getConfig').callsFake(key => { @@ -1988,7 +2029,7 @@ describe('the rubicon adapter', function () { }); const expected = [{ - bidders: [ 'rubicon' ], + bidders: ['rubicon'], config: { fpd: { site: Object.assign({}, bidderRequest.bids[0].params.inventory, context), @@ -2066,7 +2107,7 @@ describe('the rubicon adapter', function () { }); it('should pass the user.id provided in the config', function () { - config.setConfig({ user: { id: '123' } }); + config.setConfig({user: {id: '123'}}); createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => @@ -2120,7 +2161,11 @@ describe('the rubicon adapter', function () { it('should combine an array of slot url params', function () { expect(spec.combineSlotUrlParams([])).to.deep.equal({}); - expect(spec.combineSlotUrlParams([{p1: 'foo', p2: 'test', p3: ''}])).to.deep.equal({p1: 'foo', p2: 'test', p3: ''}); + expect(spec.combineSlotUrlParams([{p1: 'foo', p2: 'test', p3: ''}])).to.deep.equal({ + p1: 'foo', + p2: 'test', + p3: '' + }); expect(spec.combineSlotUrlParams([{}, {p1: 'foo', p2: 'test'}])).to.deep.equal({p1: ';foo', p2: ';test'}); @@ -2676,7 +2721,7 @@ describe('the rubicon adapter', function () { }] }; - let bids = spec.interpretResponse({ body: response }, { + let bids = spec.interpretResponse({body: response}, { bidRequest: [utils.deepClone(bidderRequest.bids[0])] }); @@ -2687,7 +2732,7 @@ describe('the rubicon adapter', function () { describe('singleRequest enabled', function () { it('handles bidRequest of type Array and returns associated adUnits', function () { const overrideMap = []; - overrideMap[0] = { impression_id: '1' }; + overrideMap[0] = {impression_id: '1'}; const stubAds = []; for (let i = 0; i < 10; i++) { @@ -2709,7 +2754,8 @@ describe('the rubicon adapter', function () { 'tracking': '', 'inventory': {}, 'ads': stubAds - }}, { bidRequest: stubBids }); + } + }, {bidRequest: stubBids}); expect(bids).to.be.a('array').with.lengthOf(10); bids.forEach((bid) => { @@ -2742,7 +2788,7 @@ describe('the rubicon adapter', function () { it('handles incorrect adUnits length by returning all bids with matching ads', function () { const overrideMap = []; - overrideMap[0] = { impression_id: '1' }; + overrideMap[0] = {impression_id: '1'}; const stubAds = []; for (let i = 0; i < 6; i++) { @@ -2764,7 +2810,8 @@ describe('the rubicon adapter', function () { 'tracking': '', 'inventory': {}, 'ads': stubAds - }}, { bidRequest: stubBids }); + } + }, {bidRequest: stubBids}); // no bids expected because response didn't match requested bid number expect(bids).to.be.a('array').with.lengthOf(6); @@ -2775,11 +2822,11 @@ describe('the rubicon adapter', function () { // Create overrides to break associations between bids and ads // Each override should cause one less bid to be returned by interpretResponse const overrideMap = []; - overrideMap[0] = { impression_id: '1' }; - overrideMap[2] = { status: 'error' }; - overrideMap[4] = { status: 'error' }; - overrideMap[7] = { status: 'error' }; - overrideMap[8] = { status: 'error' }; + overrideMap[0] = {impression_id: '1'}; + overrideMap[2] = {status: 'error'}; + overrideMap[4] = {status: 'error'}; + overrideMap[7] = {status: 'error'}; + overrideMap[8] = {status: 'error'}; for (let i = 0; i < 10; i++) { stubAds.push(createResponseAdByIndex(i, sizeMap[i].sizeId, overrideMap)); @@ -2800,7 +2847,8 @@ describe('the rubicon adapter', function () { 'tracking': '', 'inventory': {}, 'ads': stubAds - }}, { bidRequest: stubBids }); + } + }, {bidRequest: stubBids}); expect(bids).to.be.a('array').with.lengthOf(6); bids.forEach((bid) => { @@ -2931,7 +2979,7 @@ describe('the rubicon adapter', function () { }); it('should pass gdpr params if consent is true', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { gdprApplies: true, consentString: 'foo' })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?gdpr=1&gdpr_consent=foo` @@ -2939,7 +2987,7 @@ describe('the rubicon adapter', function () { }); it('should pass gdpr params if consent is false', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { gdprApplies: false, consentString: 'foo' })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?gdpr=0&gdpr_consent=foo` @@ -2947,7 +2995,7 @@ describe('the rubicon adapter', function () { }); it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: 'foo' })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?gdpr_consent=foo` @@ -2955,13 +3003,13 @@ describe('the rubicon adapter', function () { }); it('should pass no params if gdpr consentString is not defined', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {})).to.deep.equal({ + expect(spec.getUserSyncs({iframeEnabled: true}, {}, {})).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` }); }); it('should pass no params if gdpr consentString is a number', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: 0 })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` @@ -2969,7 +3017,7 @@ describe('the rubicon adapter', function () { }); it('should pass no params if gdpr consentString is null', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: null })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` @@ -2977,7 +3025,7 @@ describe('the rubicon adapter', function () { }); it('should pass no params if gdpr consentString is a object', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: {} })).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` @@ -2985,19 +3033,19 @@ describe('the rubicon adapter', function () { }); it('should pass no params if gdpr is not defined', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined)).to.deep.equal({ + expect(spec.getUserSyncs({iframeEnabled: true}, {}, undefined)).to.deep.equal({ type: 'iframe', url: `${emilyUrl}` }); }); it('should pass us_privacy if uspConsent is defined', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal({ + expect(spec.getUserSyncs({iframeEnabled: true}, {}, undefined, '1NYN')).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?us_privacy=1NYN` }); }); it('should pass us_privacy after gdpr if both are present', function () { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: 'foo' }, '1NYN')).to.deep.equal({ type: 'iframe', url: `${emilyUrl}?gdpr_consent=foo&us_privacy=1NYN` @@ -3005,8 +3053,8 @@ describe('the rubicon adapter', function () { }); }); - describe('get price granularity', function() { - it('should return correct buckets for all price granularity values', function() { + describe('get price granularity', function () { + it('should return correct buckets for all price granularity values', function () { const CUSTOM_PRICE_BUCKET_ITEM = {max: 5, increment: 0.5}; const mockConfig = { @@ -3039,7 +3087,7 @@ describe('the rubicon adapter', function () { }); }); - describe('Supply Chain Support', function() { + describe('Supply Chain Support', function () { const nodePropsOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; let bidRequests; let schainConfig; diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 749dd653865..c4e52d9a121 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1,13 +1,13 @@ import { attachIdSystem, auctionDelay, + coreStorage, init, requestBidsHook, - setSubmoduleRegistry, - syncDelay, - coreStorage, + setStoredConsentData, setStoredValue, - setStoredConsentData + setSubmoduleRegistry, + syncDelay } from 'modules/userId/index.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; @@ -15,8 +15,11 @@ import * as utils from 'src/utils.js'; import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import {getGlobal} from 'src/prebidGlobal.js'; -import {setConsentConfig, requestBidsHook as consentManagementRequestBidsHook, resetConsentData} from 'modules/consentManagement.js'; -import {gdprDataHandler} from 'src/adapterManager.js'; +import { + requestBidsHook as consentManagementRequestBidsHook, + resetConsentData, + setConsentConfig +} from 'modules/consentManagement.js'; import {unifiedIdSubmodule} from 'modules/unifiedIdSystem.js'; import {pubCommonIdSubmodule} from 'modules/pubCommonIdSystem.js'; import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; @@ -30,13 +33,14 @@ import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {haloIdSubmodule} from 'modules/haloIdSystem.js'; import {server} from 'test/mocks/xhr.js'; +import {pubProvidedIdSubmodule} from 'modules/pubProvidedSystem.js'; let assert = require('chai').assert; let expect = require('chai').expect; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; const CONSENT_LOCAL_STORAGE_NAME = '_pbjs_userid_consent_data'; -describe('User ID', function() { +describe('User ID', function () { function getConfigMock(configArr1, configArr2, configArr3, configArr4, configArr5, configArr6, configArr7, configArr8, configArr9, configArr10) { return { userSync: { @@ -90,35 +94,35 @@ describe('User ID', function() { return cfg; } - before(function() { + before(function () { coreStorage.setCookie('_pubcid_optout', '', EXPIRED_COOKIE_DATE); localStorage.removeItem('_pbjs_id_optout'); localStorage.removeItem('_pubcid_optout'); }); - beforeEach(function() { + beforeEach(function () { coreStorage.setCookie(CONSENT_LOCAL_STORAGE_NAME, '', EXPIRED_COOKIE_DATE); }); - describe('Decorate Ad Units', function() { - beforeEach(function() { + describe('Decorate Ad Units', function () { + beforeEach(function () { coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('pubcid_alt', 'altpubcid200000', (new Date(Date.now() + 5000).toUTCString())); sinon.spy(coreStorage, 'setCookie'); }); - afterEach(function() { + afterEach(function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); coreStorage.setCookie.restore(); }); - after(function() { + after(function () { coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('pubcid_alt', '', EXPIRED_COOKIE_DATE); }); - it('Check same cookie behavior', function() { + it('Check same cookie behavior', function () { let adUnits1 = [getAdUnitMock()]; let adUnits2 = [getAdUnitMock()]; let innerAdUnits1; @@ -153,7 +157,7 @@ describe('User ID', function() { assert.deepEqual(innerAdUnits1, innerAdUnits2); }); - it('Check different cookies', function() { + it('Check different cookies', function () { let adUnits1 = [getAdUnitMock()]; let adUnits2 = [getAdUnitMock()]; let innerAdUnits1; @@ -204,7 +208,7 @@ describe('User ID', function() { expect(pubcid1).to.not.equal(pubcid2); }); - it('Use existing cookie', function() { + it('Use existing cookie', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; @@ -229,7 +233,7 @@ describe('User ID', function() { expect(coreStorage.setCookie.callCount).to.equal(1); }); - it('Extend cookie', function() { + it('Extend cookie', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customConfig = getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie']); @@ -256,7 +260,7 @@ describe('User ID', function() { expect(coreStorage.setCookie.callCount).to.equal(2); }); - it('Disable auto create', function() { + it('Disable auto create', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customConfig = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); @@ -278,7 +282,7 @@ describe('User ID', function() { expect(coreStorage.setCookie.callCount).to.equal(1); }); - it('pbjs.getUserIds', function() { + it('pbjs.getUserIds', function () { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig({ @@ -293,7 +297,7 @@ describe('User ID', function() { expect((getGlobal()).getUserIds()).to.deep.equal({pubcid: '11111'}); }); - it('pbjs.getUserIdsAsEids', function() { + it('pbjs.getUserIdsAsEids', function () { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig({ @@ -309,16 +313,16 @@ describe('User ID', function() { }); }); - describe('Opt out', function() { - before(function() { + describe('Opt out', function () { + before(function () { coreStorage.setCookie('_pbjs_id_optout', '1', (new Date(Date.now() + 5000).toUTCString())); }); - beforeEach(function() { + beforeEach(function () { sinon.stub(utils, 'logInfo'); }); - afterEach(function() { + afterEach(function () { // removed cookie coreStorage.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); $$PREBID_GLOBAL$$.requestBids.removeAll(); @@ -326,18 +330,18 @@ describe('User ID', function() { config.resetConfig(); }); - after(function() { + after(function () { coreStorage.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); }); - it('fails initialization if opt out cookie exists', function() { + it('fails initialization if opt out cookie exists', function () { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - opt-out cookie found, exit module'); }); - it('initializes if no opt out cookie exists', function() { + it('initializes if no opt out cookie exists', function () { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); @@ -345,19 +349,19 @@ describe('User ID', function() { }); }); - describe('Handle variations of config values', function() { - beforeEach(function() { + describe('Handle variations of config values', function () { + beforeEach(function () { sinon.stub(utils, 'logInfo'); }); - afterEach(function() { + afterEach(function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); utils.logInfo.restore(); config.resetConfig(); }); - it('handles config with no usersync object', function() { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); + it('handles config with no usersync object', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -365,14 +369,14 @@ describe('User ID', function() { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule]); 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]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -383,7 +387,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]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -400,20 +404,22 @@ describe('User ID', function() { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); }); - it('config with 10 configurations should result in 11 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule]); + it('config with 11 configurations should result in 12 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule]); init(config); config.setConfig({ userSync: { syncDelay: 0, userIds: [{ + name: 'pubProvidedId' + }, { name: 'pubCommonId', value: {'pubcid': '11111'} }, { name: 'unifiedId', @@ -438,20 +444,20 @@ describe('User ID', function() { storage: {name: 'sharedid', type: 'cookie'} }, { name: 'intentIqId', - storage: { name: 'intentIqId', type: 'cookie' } + storage: {name: 'intentIqId', type: 'cookie'} }, { name: 'haloId', - storage: { name: 'haloId', type: 'cookie' } + storage: {name: 'haloId', type: 'cookie'} }, { name: 'zeotapIdPlus' }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 11 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 12 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -466,7 +472,7 @@ describe('User ID', function() { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -481,7 +487,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]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -496,13 +502,13 @@ describe('User ID', function() { }); }); - describe('auction and user sync delays', function() { + describe('auction and user sync delays', function () { let sandbox; let adUnits; let mockIdCallback; let auctionSpy; - beforeEach(function() { + beforeEach(function () { sandbox = sinon.createSandbox(); sandbox.stub(global, 'setTimeout').returns(2); sandbox.stub(events, 'on'); @@ -517,12 +523,12 @@ describe('User ID', function() { mockIdCallback = sandbox.stub(); const mockIdSystem = { name: 'mockId', - decode: function(value) { + decode: function (value) { return { 'mid': value['MOCKID'] }; }, - getId: function() { + getId: function () { const storedId = coreStorage.getCookie('MOCKID'); if (storedId) { return {id: {'MOCKID': storedId}}; @@ -536,13 +542,13 @@ describe('User ID', function() { attachIdSystem(mockIdSystem, true); }); - afterEach(function() { + afterEach(function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); sandbox.restore(); }); - it('delays auction if auctionDelay is set, timing out at auction delay', function() { + it('delays auction if auctionDelay is set, timing out at auction delay', function () { config.setConfig({ userSync: { auctionDelay: 33, @@ -575,7 +581,7 @@ describe('User ID', function() { events.on.called.should.equal(false); }); - it('delays auction if auctionDelay is set, continuing auction if ids are fetched before timing out', function(done) { + it('delays auction if auctionDelay is set, continuing auction if ids are fetched before timing out', function (done) { config.setConfig({ userSync: { auctionDelay: 33, @@ -614,7 +620,7 @@ describe('User ID', function() { events.on.called.should.equal(false); }); - it('does not delay auction if not set, delays id fetch after auction ends with syncDelay', function() { + it('does not delay auction if not set, delays id fetch after auction ends with syncDelay', function () { config.setConfig({ userSync: { syncDelay: 77, @@ -650,7 +656,7 @@ describe('User ID', function() { mockIdCallback.calledOnce.should.equal(true); }); - it('does not delay user id sync after auction ends if set to 0', function() { + it('does not delay user id sync after auction ends if set to 0', function () { config.setConfig({ userSync: { syncDelay: 0, @@ -679,7 +685,7 @@ describe('User ID', function() { mockIdCallback.calledOnce.should.equal(true); }); - it('does not delay auction if there are no ids to fetch', function() { + it('does not delay auction if there are no ids to fetch', function () { coreStorage.getCookie.withArgs('MOCKID').returns('123456778'); config.setConfig({ userSync: { @@ -702,21 +708,21 @@ describe('User ID', function() { }); }); - describe('Request bids hook appends userId to bid objs in adapters', function() { + describe('Request bids hook appends userId to bid objs in adapters', function () { let adUnits; - beforeEach(function() { + beforeEach(function () { adUnits = [getAdUnitMock()]; }); - it('test hook from pubcommonid cookie', function(done) { + it('test hook from pubcommonid cookie', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -732,12 +738,12 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from pubcommonid config value object', function(done) { + it('test hook from pubcommonid config value object', function (done) { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigValueMock('pubCommonId', {'pubcidvalue': 'testpubcidvalue'})); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcidvalue'); @@ -749,7 +755,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from pubcommonid html5', function(done) { + it('test hook from pubcommonid html5', function (done) { // simulate existing browser local storage values localStorage.setItem('unifiedid_alt', JSON.stringify({'TDID': 'testunifiedid_alt'})); localStorage.setItem('unifiedid_alt_exp', ''); @@ -758,7 +764,7 @@ describe('User ID', function() { init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid_alt', 'html5'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.tdid'); @@ -775,7 +781,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from identityLink html5', function(done) { + it('test hook from identityLink html5', function (done) { // simulate existing browser local storage values localStorage.setItem('idl_env', 'AiGNC8Z5ONyZKSpIPf'); localStorage.setItem('idl_env_exp', ''); @@ -783,7 +789,7 @@ describe('User ID', function() { setSubmoduleRegistry([identityLinkSubmodule]); init(config); config.setConfig(getConfigMock(['identityLink', 'idl_env', 'html5'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.idl_env'); @@ -800,14 +806,14 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from identityLink cookie', function(done) { + it('test hook from identityLink cookie', function (done) { coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 100000).toUTCString())); setSubmoduleRegistry([identityLinkSubmodule]); init(config); config.setConfig(getConfigMock(['identityLink', 'idl_env', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.idl_env'); @@ -823,7 +829,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from liveIntentId html5', function(done) { + it('test hook from liveIntentId html5', function (done) { // simulate existing browser local storage values localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier'})); localStorage.setItem('_li_pbid_exp', ''); @@ -831,7 +837,7 @@ describe('User ID', function() { setSubmoduleRegistry([liveIntentIdSubmodule]); init(config); config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.lipb'); @@ -848,14 +854,14 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from liveIntentId cookie', function(done) { + it('test hook from liveIntentId cookie', function (done) { coreStorage.setCookie('_li_pbid', JSON.stringify({'unifiedId': 'random-cookie-identifier'}), (new Date(Date.now() + 100000).toUTCString())); setSubmoduleRegistry([liveIntentIdSubmodule]); init(config); config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.lipb'); @@ -871,7 +877,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from sharedId html5', function(done) { + it('test hook from sharedId html5', function (done) { // simulate existing browser local storage values localStorage.setItem('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611})); localStorage.setItem('sharedid_exp', ''); @@ -879,7 +885,7 @@ describe('User ID', function() { setSubmoduleRegistry([sharedIdSubmodule]); init(config); config.setConfig(getConfigMock(['sharedId', 'sharedid', 'html5'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.sharedid'); @@ -907,7 +913,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from sharedId html5 (id not synced)', function(done) { + it('test hook from sharedId html5 (id not synced)', function (done) { // simulate existing browser local storage values localStorage.setItem('sharedid', JSON.stringify({'id': 'test_sharedId', 'ns': true, 'ts': 1590525289611})); localStorage.setItem('sharedid_exp', ''); @@ -915,7 +921,7 @@ describe('User ID', function() { setSubmoduleRegistry([sharedIdSubmodule]); init(config); config.setConfig(getConfigMock(['sharedId', 'sharedid', 'html5'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.sharedid'); @@ -937,14 +943,17 @@ describe('User ID', function() { done(); }, {adUnits}); }); - it('test hook from sharedId cookie', function(done) { - coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 100000).toUTCString())); + it('test hook from sharedId cookie', function (done) { + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test_sharedId', + 'ts': 1590525289611 + }), (new Date(Date.now() + 100000).toUTCString())); setSubmoduleRegistry([sharedIdSubmodule]); init(config); config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.sharedid'); @@ -970,14 +979,18 @@ describe('User ID', function() { done(); }, {adUnits}); }); - it('test hook from sharedId cookie (id not synced) ', function(done) { - coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ns': true, 'ts': 1590525289611}), (new Date(Date.now() + 100000).toUTCString())); + it('test hook from sharedId cookie (id not synced) ', function (done) { + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test_sharedId', + 'ns': true, + 'ts': 1590525289611 + }), (new Date(Date.now() + 100000).toUTCString())); setSubmoduleRegistry([sharedIdSubmodule]); init(config); config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.sharedid'); @@ -999,7 +1012,104 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from liveIntentId html5', function(done) { + it('test hook from pubProvidedId config params', function (done) { + setSubmoduleRegistry([pubProvidedIdSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'pubProvidedId', + params: { + eids: [{ + source: 'example.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'ppuid' + } + }] + }, { + source: 'id-partner.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'dmp' + } + }] + }], + eidsFunction: function () { + return [{ + source: 'provider.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'sha256email' + } + }] + }] + } + } + } + ] + } + }); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubProvidedId'); + expect(bid.userId.pubProvidedId).to.deep.equal([{ + source: 'example.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'ppuid' + } + }] + }, { + source: 'id-partner.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'dmp' + } + }] + }, { + source: 'provider.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'sha256email' + } + }] + }]); + + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'example.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'ppuid' + } + }] + }); + expect(bid.userIdAsEids[2]).to.deep.equal({ + source: 'provider.com', + uids: [{ + id: 'value read from cookie or local storage', + ext: { + stype: 'sha256email' + } + }] + }); + }); + }); + done(); + }, {adUnits}); + }); + + it('test hook from liveIntentId html5', function (done) { // simulate existing browser local storage values localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier', 'segments': ['123']})); localStorage.setItem('_li_pbid_exp', ''); @@ -1007,7 +1117,7 @@ describe('User ID', function() { setSubmoduleRegistry([liveIntentIdSubmodule]); init(config); config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.lipb'); @@ -1026,7 +1136,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from liveIntentId cookie', function(done) { + it('test hook from liveIntentId cookie', function (done) { coreStorage.setCookie('_li_pbid', JSON.stringify({ 'unifiedId': 'random-cookie-identifier', 'segments': ['123'] @@ -1036,7 +1146,7 @@ describe('User ID', function() { init(config); config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.lipb'); @@ -1054,7 +1164,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from britepoolid cookies', function(done) { + it('test hook from britepoolid cookies', function (done) { // simulate existing browser local storage values coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1062,7 +1172,7 @@ describe('User ID', function() { init(config); config.setConfig(getConfigMock(['britepoolId', 'britepoolid', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.britepoolid'); @@ -1078,7 +1188,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from netId cookies', function(done) { + it('test hook from netId cookies', function (done) { // simulate existing browser local storage values coreStorage.setCookie('netId', JSON.stringify({'netId': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1086,7 +1196,7 @@ describe('User ID', function() { init(config); config.setConfig(getConfigMock(['netId', 'netId', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.netId'); @@ -1102,7 +1212,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from intentIqId cookies', function(done) { + it('test hook from intentIqId cookies', function (done) { // simulate existing browser local storage values coreStorage.setCookie('intentIqId', 'abcdefghijk', (new Date(Date.now() + 5000).toUTCString())); @@ -1110,7 +1220,7 @@ describe('User ID', function() { init(config); config.setConfig(getConfigMock(['intentIqId', 'intentIqId', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.intentIqId'); @@ -1126,7 +1236,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from haloId html5', function(done) { + it('test hook from haloId html5', function (done) { // simulate existing browser local storage values localStorage.setItem('haloId', JSON.stringify({'haloId': 'random-ls-identifier'})); localStorage.setItem('haloId_exp', ''); @@ -1135,7 +1245,7 @@ describe('User ID', function() { init(config); config.setConfig(getConfigMock(['haloId', 'haloId', 'html5'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.haloId'); @@ -1152,7 +1262,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from merkleId cookies', function(done) { + it('test hook from merkleId cookies', function (done) { // simulate existing browser local storage values coreStorage.setCookie('merkleId', JSON.stringify({'ppid': {'id': 'testmerkleId'}}), (new Date(Date.now() + 5000).toUTCString())); @@ -1160,7 +1270,7 @@ describe('User ID', function() { init(config); config.setConfig(getConfigMock(['merkleId', 'merkleId', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.merkleId'); @@ -1176,7 +1286,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook from zeotapIdPlus cookies', function(done) { + it('test hook from zeotapIdPlus cookies', function (done) { // simulate existing browser local storage values coreStorage.setCookie('IDP', btoa(JSON.stringify('abcdefghijk')), (new Date(Date.now() + 5000).toUTCString())); @@ -1184,7 +1294,7 @@ describe('User ID', function() { init(config); config.setConfig(getConfigMock(['zeotapIdPlus', 'IDP', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.IDP'); @@ -1200,7 +1310,7 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId and haloId have data to pass', function(done) { + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId and haloId 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())); @@ -1209,7 +1319,10 @@ describe('User ID', function() { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test_sharedId', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule]); @@ -1225,7 +1338,7 @@ describe('User ID', function() { ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { // verify that the PubCommonId id data was copied to bid @@ -1277,14 +1390,17 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId and haloId have their modules added before and after init', function(done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId and haloId 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())); coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test_sharedId', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1318,7 +1434,7 @@ describe('User ID', function() { ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { // verify that the PubCommonId id data was copied to bid @@ -1371,8 +1487,11 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook when sharedId(opted out) have their modules added before and after init', function(done) { - coreStorage.setCookie('sharedid', JSON.stringify({'id': '00000000000000000000000000', 'ts': 1590525289611}), (new Date(Date.now() + 5000).toUTCString())); + it('test hook when sharedId(opted out) have their modules added before and after init', function (done) { + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': '00000000000000000000000000', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); init(config); @@ -1381,7 +1500,7 @@ describe('User ID', function() { config.setConfig(getConfigMock(['sharedId', 'sharedid', 'cookie'])); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid.userIdAsEids).to.be.undefined; @@ -1392,14 +1511,17 @@ describe('User ID', function() { }, {adUnits}); }); - it('should add new id system ', function(done) { + 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()); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('sharedid', JSON.stringify({'id': 'test_sharedId', 'ts': 1590525289611}), new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test_sharedId', + 'ts': 1590525289611 + }), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1426,11 +1548,11 @@ describe('User ID', function() { }, { name: 'sharedId', storage: {name: 'sharedid', type: 'cookie'} }, { - name: 'intentIqId', storage: { name: 'intentIqId', type: 'cookie' } + name: 'intentIqId', storage: {name: 'intentIqId', type: 'cookie'} }, { name: 'zeotapIdPlus' }, { - name: 'haloId', storage: { name: 'haloId', type: 'cookie' } + name: 'haloId', storage: {name: 'haloId', type: 'cookie'} }, { name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} }] @@ -1440,18 +1562,18 @@ describe('User ID', function() { // Add new submodule named 'mockId' attachIdSystem({ name: 'mockId', - decode: function(value) { + decode: function (value) { return { 'mid': value['MOCKID'] }; }, - getId: function(params, storedId) { + getId: function (params, storedId) { if (storedId) return {}; return {id: {'MOCKID': '1234'}}; } }); - requestBidsHook(function() { + requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { // check PubCommonId id data was copied to bid @@ -1509,8 +1631,8 @@ describe('User ID', function() { }); }); - describe('callbacks at the end of auction', function() { - beforeEach(function() { + describe('callbacks at the end of auction', function () { + beforeEach(function () { sinon.stub(events, 'getEvents').returns([]); sinon.stub(utils, 'triggerPixel'); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1518,7 +1640,7 @@ describe('User ID', function() { coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); }); - afterEach(function() { + afterEach(function () { events.getEvents.restore(); utils.triggerPixel.restore(); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1526,7 +1648,7 @@ describe('User ID', function() { coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); }); - it('pubcid callback with url', function() { + it('pubcid callback with url', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customCfg = getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie']); @@ -1544,7 +1666,7 @@ describe('User ID', function() { expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); }); - it('unifiedid callback with url', function() { + it('unifiedid callback with url', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); @@ -1562,7 +1684,7 @@ describe('User ID', function() { expect(server.requests[0].url).to.equal('/any/unifiedid/url'); }); - it('unifiedid callback with partner', function() { + it('unifiedid callback with partner', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); @@ -1581,7 +1703,7 @@ describe('User ID', function() { }); }); - describe('Set cookie behavior', function() { + describe('Set cookie behavior', function () { let coreStorageSpy; beforeEach(function () { coreStorageSpy = sinon.spy(coreStorage, 'setCookie'); @@ -1620,7 +1742,7 @@ describe('User ID', function() { }); }); - describe('Consent changes determine getId refreshes', function() { + describe('Consent changes determine getId refreshes', function () { let expStr; let adUnits; @@ -1656,7 +1778,7 @@ describe('User ID', function() { allowAuctionWithoutConsent: false }; - const sharedBeforeFunction = function() { + const sharedBeforeFunction = function () { // clear cookies expStr = (new Date(Date.now() + 25000).toUTCString()); coreStorage.setCookie(mockIdCookieName, '', EXPIRED_COOKIE_DATE); @@ -1682,7 +1804,7 @@ describe('User ID', function() { delete window.__tcfapi; }; - describe('TCF v1', function() { + describe('TCF v1', function () { testConsentData = { gdprApplies: true, consentData: 'xyz', @@ -1693,7 +1815,8 @@ describe('User ID', function() { sharedBeforeFunction(); // init v1 consent management - window.__cmp = function () { }; + window.__cmp = function () { + }; delete window.__tcfapi; cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { args[2](testConsentData); @@ -1701,17 +1824,20 @@ describe('User ID', function() { setConsentConfig(consentConfig); }); - afterEach(function() { + afterEach(function () { sharedAfterFunction(); }); it('does not call getId if no stored consent data and refresh is not needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); let innerAdUnits; - consentManagementRequestBidsHook(() => { }, {}); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); sinon.assert.notCalled(mockGetId); sinon.assert.calledOnce(mockDecode); @@ -1719,12 +1845,15 @@ describe('User ID', function() { }); it('calls getId if no stored consent data but refresh is needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 60 * 1000).toUTCString()), expStr); let innerAdUnits; - consentManagementRequestBidsHook(() => { }, {}); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); sinon.assert.calledOnce(mockGetId); sinon.assert.calledOnce(mockDecode); @@ -1732,14 +1861,17 @@ describe('User ID', function() { }); it('calls getId if empty stored consent and refresh not needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); setStoredConsentData(); let innerAdUnits; - consentManagementRequestBidsHook(() => { }, {}); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); sinon.assert.calledOnce(mockGetId); sinon.assert.calledOnce(mockDecode); @@ -1747,7 +1879,7 @@ describe('User ID', function() { }); it('calls getId if stored consent does not match current consent and refresh not needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); setStoredConsentData({ @@ -1757,8 +1889,11 @@ describe('User ID', function() { }); let innerAdUnits; - consentManagementRequestBidsHook(() => { }, {}); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); sinon.assert.calledOnce(mockGetId); sinon.assert.calledOnce(mockDecode); @@ -1766,7 +1901,7 @@ describe('User ID', function() { }); it('does not call getId if stored consent matches current consent and refresh not needed', function () { - coreStorage.setCookie(mockIdCookieName, JSON.stringify({ id: '1234' }), expStr); + coreStorage.setCookie(mockIdCookieName, JSON.stringify({id: '1234'}), expStr); coreStorage.setCookie(`${mockIdCookieName}_last`, (new Date(Date.now() - 1 * 1000).toUTCString()), expStr); setStoredConsentData({ @@ -1776,8 +1911,11 @@ describe('User ID', function() { }); let innerAdUnits; - consentManagementRequestBidsHook(() => { }, {}); - requestBidsHook((config) => { innerAdUnits = config.adUnits }, { adUnits }); + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); sinon.assert.notCalled(mockGetId); sinon.assert.calledOnce(mockDecode); From 92e234c4f266ac21003ba7adb3652eae412e4ac4 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 29 Sep 2020 10:54:30 -0700 Subject: [PATCH 028/190] standardize rubicon get config calls (#5780) --- modules/rubiconAnalyticsAdapter.js | 46 +++----- modules/rubiconBidAdapter.js | 48 +++------ .../modules/rubiconAnalyticsAdapter_spec.js | 101 +++++++++++++----- test/spec/modules/rubiconBidAdapter_spec.js | 69 ++++-------- 4 files changed, 132 insertions(+), 132 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 72648f8feb5..f6d30e06e9a 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -51,27 +51,16 @@ const cache = { const BID_REJECTED_IPF = 'rejected-ipf'; -let fpkvs = {}; -function updateFpkvs(fpkvs, newKvs) { - const isValid = typeof newKvs === 'object' && Object.keys(newKvs).every(key => typeof newKvs[key] === 'string'); - if (!isValid) { - utils.logError('Rubicon Analytics: fpkvs must be object with string keys and values'); - return fpkvs; - } else { - return {...fpkvs, ...newKvs}; - } -} - -let integration, ruleId, wrapperName; -// listen for any rubicon setConfig events and save them to appropriate fields! +export let rubiConf = { + pvid: utils.generateUUID().slice(0, 8) +}; // we are saving these as global to this module so that if a pub accidentally overwrites the entire // rubicon object, then we do not lose other data config.getConfig('rubicon', config => { - let rubiConf = config.rubicon; - integration = rubiConf.int_type || integration || DEFAULT_INTEGRATION; - ruleId = rubiConf.rule_name || ruleId; - wrapperName = rubiConf.wrapperName || wrapperName; - fpkvs = rubiConf.fpkvs ? updateFpkvs(fpkvs, rubiConf.fpkvs) : fpkvs + utils.mergeDeep(rubiConf, config.rubicon); + if (utils.deepAccess(config, 'rubicon.updatePageView') === true) { + rubiConf.pvid = utils.generateUUID().slice(0, 8) + } }); export function getHostNameFromReferer(referer) { @@ -163,13 +152,13 @@ function sendMessage(auctionId, bidWonId) { let referrer = config.getConfig('pageUrl') || (auctionCache && auctionCache.referrer); let message = { eventTimeMillis: Date.now(), - integration, - ruleId, + integration: rubiConf.int_type || DEFAULT_INTEGRATION, + ruleId: rubiConf.rule_name, version: '$prebid.version$', referrerUri: referrer, referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer), channel: 'web', - wrapperName + wrapperName: rubiConf.wrapperName }; if (auctionCache && !auctionCache.sent) { let adUnitMap = Object.keys(auctionCache.bids).reduce((adUnits, bidId) => { @@ -359,10 +348,9 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { ]); } -function getPageViewId() { - if (prebidGlobal.rp && typeof prebidGlobal.rp.generatePageViewId === 'function') { - return prebidGlobal.rp.generatePageViewId(false); - } +function getFpkvs() { + const isValid = rubiConf.fpkvs && typeof rubiConf.fpkvs === 'object' && Object.keys(rubiConf.fpkvs).every(key => typeof rubiConf.fpkvs[key] === 'string'); + return isValid ? rubiConf.fpkvs : {}; } let samplingFactor = 1; @@ -420,8 +408,8 @@ function updateRpaCookie() { // possible that decodedRpaCookie is undefined, and if it is, we probably are blocked by storage or some other exception if (Object.keys(decodedRpaCookie).length) { decodedRpaCookie.lastSeen = currentTime; - decodedRpaCookie.fpkvs = {...decodedRpaCookie.fpkvs, ...fpkvs}; - decodedRpaCookie.pvid = getPageViewId(); + decodedRpaCookie.fpkvs = {...decodedRpaCookie.fpkvs, ...getFpkvs()}; + decodedRpaCookie.pvid = rubiConf.pvid; setRpaCookie(decodedRpaCookie) } return decodedRpaCookie; @@ -495,8 +483,8 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { }, disableAnalytics() { this.getUrl = baseAdapter.getUrl; - accountId = integration = ruleId = wrapperName = undefined; - fpkvs = {}; + accountId = undefined; + rubiConf = {}; cache.gpt.registered = false; baseAdapter.disableAnalytics.apply(this, arguments); }, diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 18d7973d9fa..829fd208887 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -7,24 +7,11 @@ import find from 'core-js-pure/features/array/find.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; -// always use https, regardless of whether or not current page is secure -export let fastlaneEndpoint = `https://fastlane.rubiconproject.com/a/api/fastlane.json`; -export let videoEndpoint = `https://prebid-server.rubiconproject.com/openrtb2/auction`; -export let syncEndpoint = `https://eus.rubiconproject.com/usync.html`; -let returnVast = false; - -let bannerHost = 'fastlane'; -let videoHost = 'prebid-server'; -let syncHost = 'eus'; +let rubiConf = {}; +// we are saving these as global to this module so that if a pub accidentally overwrites the entire +// rubicon object, then we do not lose other data config.getConfig('rubicon', config => { - let rubiConf = config.rubicon; - bannerHost = rubiConf.bannerHost || bannerHost; - fastlaneEndpoint = `https://${bannerHost}.rubiconproject.com/a/api/fastlane.json`; - videoHost = rubiConf.videoHost || videoHost; - videoEndpoint = `https://${videoHost}.rubiconproject.com/openrtb2/auction`; - syncHost = rubiConf.syncHost || syncHost; - syncEndpoint = `https://${syncHost}.rubiconproject.com/usync.html`; - returnVast = rubiConf.returnVast === true; // anything other than true is false + utils.mergeDeep(rubiConf, config.rubicon); }); const GVLID = 52; @@ -192,7 +179,7 @@ export const spec = { prebid: { cache: { vastxml: { - returnCreative: returnVast + returnCreative: rubiConf.returnVast === true } }, targeting: { @@ -203,7 +190,7 @@ export const spec = { }, bidders: { rubicon: { - integration: config.getConfig('rubicon.int_type') || DEFAULT_PBS_INTEGRATION + integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION } } } @@ -218,7 +205,7 @@ export const spec = { } let bidFloor; - if (typeof bidRequest.getFloor === 'function' && !config.getConfig('rubicon.disableFloors')) { + if (typeof bidRequest.getFloor === 'function' && !rubiConf.disableFloors) { let floorInfo; try { floorInfo = bidRequest.getFloor({ @@ -343,19 +330,19 @@ export const spec = { return { method: 'POST', - url: videoEndpoint, + url: `https://${rubiConf.videoHost || 'prebid-server'}.rubiconproject.com/openrtb2/auction`, data, bidRequest } }); - if (config.getConfig('rubicon.singleRequest') !== true) { + if (rubiConf.singleRequest !== true) { // bids are not grouped if single request mode is not enabled requests = videoRequests.concat(bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner').map(bidRequest => { const bidParams = spec.createSlotParams(bidRequest, bidderRequest); return { method: 'GET', - url: fastlaneEndpoint, + url: `https://${rubiConf.bannerHost || 'fastlane'}.rubiconproject.com/a/api/fastlane.json`, data: spec.getOrderedParams(bidParams).reduce((paramString, key) => { const propValue = bidParams[key]; return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString; @@ -386,7 +373,7 @@ export const spec = { // SRA request returns grouped bidRequest arrays not a plain bidRequest aggregate.push({ method: 'GET', - url: fastlaneEndpoint, + url: `https://${rubiConf.bannerHost || 'fastlane'}.rubiconproject.com/a/api/fastlane.json`, data: spec.getOrderedParams(combinedSlotParams).reduce((paramString, key) => { const propValue = combinedSlotParams[key]; return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString; @@ -493,8 +480,6 @@ export const spec = { const [latitude, longitude] = params.latLong || []; - const configIntType = config.getConfig('rubicon.int_type'); - const data = { 'account_id': params.accountId, 'site_id': params.siteId, @@ -503,7 +488,7 @@ export const spec = { 'alt_size_ids': parsedSizes.slice(1).join(',') || undefined, 'rp_floor': (params.floor = parseFloat(params.floor)) > 0.01 ? params.floor : 0.01, 'rp_secure': '1', - 'tk_flint': `${configIntType || DEFAULT_INTEGRATION}_v$prebid.version$`, + 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, 'x_source.tid': bidRequest.transactionId, 'x_source.pchain': params.pchain, 'p_screen_res': _getScreenResolution(), @@ -515,7 +500,7 @@ export const spec = { }; // If floors module is enabled and we get USD floor back, send it in rp_hard_floor else undfined - if (typeof bidRequest.getFloor === 'function' && !config.getConfig('rubicon.disableFloors')) { + if (typeof bidRequest.getFloor === 'function' && !rubiConf.disableFloors) { let floorInfo; try { floorInfo = bidRequest.getFloor({ @@ -698,7 +683,7 @@ export const spec = { cpm: bid.price || 0, bidderCode: seatbid.seat, ttl: 300, - netRevenue: config.getConfig('rubicon.netRevenue') !== false, // If anything other than false, netRev is true + netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true width: bid.w || utils.deepAccess(bidRequest, 'mediaTypes.video.w') || utils.deepAccess(bidRequest, 'params.video.playerWidth'), height: bid.h || utils.deepAccess(bidRequest, 'mediaTypes.video.h') || utils.deepAccess(bidRequest, 'params.video.playerHeight'), }; @@ -778,7 +763,7 @@ export const spec = { cpm: ad.cpm || 0, dealId: ad.deal, ttl: 300, // 5 minutes - netRevenue: config.getConfig('rubicon.netRevenue') !== false, // If anything other than false, netRev is true + netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true rubicon: { advertiserId: ad.advertiser, networkId: ad.network }, @@ -840,7 +825,7 @@ export const spec = { hasSynced = true; return { type: 'iframe', - url: syncEndpoint + params + url: `https://${rubiConf.syncHost || 'eus'}.rubiconproject.com/usync.html` + params }; } }, @@ -1086,6 +1071,7 @@ function bidType(bid, log = false) { } } +export const resetRubiConf = () => rubiConf = {}; export function masSizeOrdering(sizes) { const MAS_SIZE_PRIORITY = [15, 2, 9]; diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 60b28b02361..2bbab506b34 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -3,6 +3,7 @@ import rubiconAnalyticsAdapter, { parseBidResponse, getHostNameFromReferer, storage, + rubiConf, } from 'modules/rubiconAnalyticsAdapter.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; @@ -12,9 +13,6 @@ import { setConfig, addBidResponseHook, } from 'modules/currency.js'; -import { getGlobal } from 'src/prebidGlobal.js'; - -let prebidGlobal = getGlobal(); let Ajv = require('ajv'); let schema = require('./rubiconAnalyticsSchema.json'); let ajv = new Ajv({ @@ -559,6 +557,73 @@ describe('rubicon analytics adapter', function () { expect(utils.logError.called).to.equal(true); }); + describe('config subscribe', function() { + it('should update the pvid if user asks', function () { + expect(utils.generateUUID.called).to.equal(false); + config.setConfig({rubicon: {updatePageView: true}}); + expect(utils.generateUUID.called).to.equal(true); + }); + it('should merge in and preserve older set configs', function () { + config.setConfig({ + rubicon: { + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + source: 'fb' + } + } + }); + expect(rubiConf).to.deep.equal({ + pvid: '12345678', + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + source: 'fb' + }, + updatePageView: true + }); + + // update it with stuff + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' + } + } + }); + expect(rubiConf).to.deep.equal({ + pvid: '12345678', + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + source: 'fb', + link: 'email' + }, + updatePageView: true + }); + + // overwriting specific edge keys should update them + config.setConfig({ + rubicon: { + fpkvs: { + link: 'iMessage', + source: 'twitter' + } + } + }); + expect(rubiConf).to.deep.equal({ + pvid: '12345678', + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + link: 'iMessage', + source: 'twitter' + }, + updatePageView: true + }); + }); + }); + describe('sampling', function () { beforeEach(function () { sandbox.stub(Math, 'random').returns(0.08); @@ -865,17 +930,9 @@ describe('rubicon analytics adapter', function () { }); describe('with session handling', function () { - let pvid, kvps; + const expectedPvid = STUBBED_UUID.slice(0, 8); beforeEach(function () { - // custom dm stuff - prebidGlobal.rp = { - getCustomTargeting: () => kvps, - generatePageViewId: () => pvid - } - }); - - afterEach(function () { - prebidGlobal.rp = pvid = kvps = undefined; + config.setConfig({rubicon: {updatePageView: true}}); }); it('should not log any session data if local storage is not enabled', function () { @@ -899,7 +956,6 @@ describe('rubicon analytics adapter', function () { }); it('should should pass along custom rubicon kv and pvid when defined', function () { - pvid = '1a2b3c'; config.setConfig({rubicon: { fpkvs: { source: 'fb', @@ -913,7 +969,7 @@ describe('rubicon analytics adapter', function () { validate(message); let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = '1a2b3c'; + expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); expectedMessage.fpkvs = [ {key: 'source', value: 'fb'}, {key: 'link', value: 'email'} @@ -932,7 +988,6 @@ describe('rubicon analytics adapter', function () { }; getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - pvid = '1a2b3c'; config.setConfig({rubicon: { fpkvs: { link: 'email' // should merge this with what is in the localStorage! @@ -949,7 +1004,7 @@ describe('rubicon analytics adapter', function () { id: '987654', start: 1519766113781, expires: 1519787713781, - pvid: '1a2b3c' + pvid: expectedPvid } expectedMessage.fpkvs = [ {key: 'source', value: 'tw'}, @@ -970,7 +1025,7 @@ describe('rubicon analytics adapter', function () { expires: 1519787713781, // should have stayed same lastSeen: 1519767013781, // lastSeen updated to our "now" fpkvs: { source: 'tw', link: 'email' }, // link merged in - pvid: '1a2b3c' // new pvid stored + pvid: expectedPvid // new pvid stored }); }); @@ -985,7 +1040,6 @@ describe('rubicon analytics adapter', function () { }; getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - pvid = '1a2b3c'; config.setConfig({rubicon: { fpkvs: { link: 'email' // should merge this with what is in the localStorage! @@ -1000,7 +1054,7 @@ describe('rubicon analytics adapter', function () { let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid - expectedMessage.session.pvid = '1a2b3c'; + expectedMessage.session.pvid = expectedPvid; // the saved fpkvs should have been thrown out since session expired expectedMessage.fpkvs = [ @@ -1021,7 +1075,7 @@ describe('rubicon analytics adapter', function () { expires: 1519788613781, // should have stayed same lastSeen: 1519767013781, // lastSeen updated to our "now" fpkvs: { link: 'email' }, // link merged in - pvid: '1a2b3c' // new pvid stored + pvid: expectedPvid // new pvid stored }); }); @@ -1036,7 +1090,6 @@ describe('rubicon analytics adapter', function () { }; getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - pvid = '1a2b3c'; config.setConfig({rubicon: { fpkvs: { link: 'email' // should merge this with what is in the localStorage! @@ -1051,7 +1104,7 @@ describe('rubicon analytics adapter', function () { let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid - expectedMessage.session.pvid = '1a2b3c'; + expectedMessage.session.pvid = expectedPvid; // the saved fpkvs should have been thrown out since session expired expectedMessage.fpkvs = [ @@ -1072,7 +1125,7 @@ describe('rubicon analytics adapter', function () { expires: 1519788613781, // should have stayed same lastSeen: 1519767013781, // lastSeen updated to our "now" fpkvs: { link: 'email' }, // link merged in - pvid: '1a2b3c' // new pvid stored + pvid: expectedPvid // new pvid stored }); }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 9417303da0e..dd25fafb149 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1,5 +1,12 @@ import {expect} from 'chai'; -import {spec, getPriceGranularity, masSizeOrdering, resetUserSync, hasVideoMediaType, fastlaneEndpoint} from 'modules/rubiconBidAdapter.js'; +import { + spec, + getPriceGranularity, + masSizeOrdering, + resetUserSync, + hasVideoMediaType, + resetRubiConf +} from 'modules/rubiconBidAdapter.js'; import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -361,6 +368,8 @@ describe('the rubicon adapter', function () { afterEach(function () { sandbox.restore(); utils.logError.restore(); + config.resetConfig(); + resetRubiConf(); }); describe('MAS mapping / ordering', function () { @@ -497,13 +506,8 @@ describe('the rubicon adapter', function () { expect(data['p_pos']).to.equal(undefined); }); - it('should correctly send p_pos in sra fashion', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': true - }; - return config[key]; - }); + it('should correctly send p_pos in sra fashion', function() { + config.setConfig({rubicon: {singleRequest: true}}); // first one is atf var sraPosRequest = utils.deepClone(bidderRequest); @@ -1096,12 +1100,7 @@ describe('the rubicon adapter', function () { it('should group all bid requests with the same site id', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': true - }; - return config[key]; - }); + config.setConfig({rubicon: {singleRequest: true}}); const expectedQuery = { 'account_id': '14062', @@ -1209,13 +1208,7 @@ describe('the rubicon adapter', function () { }); it('should not send more than 10 bids in a request (split into separate requests with <= 10 bids each)', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': true - }; - return config[key]; - }); - + config.setConfig({rubicon: {singleRequest: true}}); let serverRequests; let data; @@ -1257,12 +1250,7 @@ describe('the rubicon adapter', function () { }); it('should not group bid requests if singleRequest does not equal true', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': false - }; - return config[key]; - }); + config.setConfig({rubicon: {singleRequest: false}}); const bidCopy = utils.deepClone(bidderRequest.bids[0]); bidderRequest.bids.push(bidCopy); @@ -1280,12 +1268,7 @@ describe('the rubicon adapter', function () { }); it('should not group video bid requests', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'rubicon.singleRequest': true - }; - return config[key]; - }); + config.setConfig({rubicon: {singleRequest: true}}); const bidCopy = utils.deepClone(bidderRequest.bids[0]); bidderRequest.bids.push(bidCopy); @@ -1957,14 +1940,14 @@ describe('the rubicon adapter', function () { let requests = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal(fastlaneEndpoint); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); bidderRequest.mediaTypes.video.context = 'instream'; requests = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal(fastlaneEndpoint); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); }); it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { @@ -1983,7 +1966,7 @@ describe('the rubicon adapter', function () { let requests = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal(fastlaneEndpoint); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); }); it('should include coppa flag in video bid request', () => { @@ -2096,12 +2079,7 @@ describe('the rubicon adapter', function () { it('should use the integration type provided in the config instead of the default', () => { createVideoBidderRequest(); - sandbox.stub(config, 'getConfig').callsFake(function (key) { - const config = { - 'rubicon.int_type': 'testType' - }; - return config[key]; - }); + config.setConfig({rubicon: {int_type: 'testType'}}); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); }); @@ -2939,12 +2917,7 @@ describe('the rubicon adapter', function () { describe('config with integration type', () => { it('should use the integration type provided in the config instead of the default', () => { - sandbox.stub(config, 'getConfig').callsFake(function (key) { - const config = { - 'rubicon.int_type': 'testType' - }; - return config[key]; - }); + config.setConfig({rubicon: {int_type: 'testType'}}); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(parseQuery(request.data).tk_flint).to.equal('testType_v$prebid.version$'); }); From 242efdcd81abce2083ea279d46cc84cccce02d94 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Tue, 29 Sep 2020 16:29:18 -0400 Subject: [PATCH 029/190] Prebid 4.10.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fc5a0441ad1..20e1cd6be19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.10.0-pre", + "version": "4.10.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 08e2bf2bfae40ea8583794e3ced6caf07f57aec5 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Tue, 29 Sep 2020 17:01:39 -0400 Subject: [PATCH 030/190] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 20e1cd6be19..35b0f6925e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.10.0", + "version": "4.11.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 61e1485e269560779e4bd89cb34cfd9b8b9fcba2 Mon Sep 17 00:00:00 2001 From: Zak Andree Date: Tue, 29 Sep 2020 18:33:53 -0700 Subject: [PATCH 031/190] Add Inmar bidder adapter (#5674) * Add Inmar bidder adapter * Update Inmar adapter * Small fix * Update Inmar params * Remove domain and bidFloor, add meta * Remove unused data * Fix unit tests --- modules/inmarBidAdapter.js | 113 ++++++++++ modules/inmarBidAdapter.md | 46 ++++ test/spec/modules/inmarBidAdapter_spec.js | 251 ++++++++++++++++++++++ 3 files changed, 410 insertions(+) create mode 100755 modules/inmarBidAdapter.js create mode 100644 modules/inmarBidAdapter.md create mode 100644 test/spec/modules/inmarBidAdapter_spec.js diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js new file mode 100755 index 00000000000..b5ab72266fc --- /dev/null +++ b/modules/inmarBidAdapter.js @@ -0,0 +1,113 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'inmar'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['inm'], + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid + * + * @param {bidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise + */ + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.partnerId && bid.params.adnetId); + }, + + /** + * 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) { + var payload = { + bidderCode: bidderRequest.bidderCode, + auctionId: bidderRequest.auctionId, + bidderRequestId: bidderRequest.bidderRequestId, + bidRequests: validBidRequests, + auctionStart: bidderRequest.auctionStart, + timeout: bidderRequest.timeout, + refererInfo: bidderRequest.refererInfo, + start: bidderRequest.start, + gdprConsent: bidderRequest.gdprConsent, + uspConsent: bidderRequest.uspConsent, + currencyCode: config.getConfig('currency.adServerCurrency'), + coppa: config.getConfig('coppa'), + firstPartyData: config.getConfig('fpd'), + prebidVersion: '$prebid.version$' + }; + + var payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: 'https://prebid.owneriq.net:8443/bidder/pb/bid', + options: { + withCredentials: false + }, + data: payloadString, + }; + }, + + /** + * 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 = []; + var response = serverResponse.body; + + try { + if (response) { + var bidResponse = { + requestId: response.requestId, + cpm: response.cpm, + currency: response.currency, + width: response.width, + height: response.height, + ad: response.ad, + ttl: response.ttl, + creativeId: response.creativeId, + netRevenue: response.netRevenue, + vastUrl: response.vastUrl, + dealId: response.dealId, + meta: response.meta + }; + + bidResponses.push(bidResponse); + } + } catch (error) { + utils.logError('Error while parsing inmar response', error); + } + return bidResponses; + }, + + /** + * User Syncs + * + * @param {syncOptions} Publisher prebid configuration + * @param {serverResponses} Response from the server + * @returns {Array} + */ + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: 'https://px.owneriq.net/eucm/p/pb' + }); + } + return syncs; + } +}; + +registerBidder(spec); diff --git a/modules/inmarBidAdapter.md b/modules/inmarBidAdapter.md new file mode 100644 index 00000000000..1bacb30f2dd --- /dev/null +++ b/modules/inmarBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: Inmar Bidder Adapter +Module Type: Bidder Adapter +Maintainer: oiq_rtb@inmar.com +``` + +# Description + +Connects to Inmar for bids. This adapter supports Display and Video. + +The Inmar adapter requires setup and approval from the Inmar team. +Please reach out to your account manager for more information. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div1', + sizes: [[300, 250],[300, 600]], + bids: [{ + bidder: 'inmar', + params: { + partnerId: 12345, + adnetId: 'ADb1f40rmi', + position: 1 + } + }] + }, + { + code: 'test-div2', + sizes: [[728, 90],[970, 250]], + bids: [{ + bidder: 'inmar', + params: { + partnerId: 12345, + adnetId: 'ADb1f40rmo', + position: 0 + } + }] + } + ]; +``` diff --git a/test/spec/modules/inmarBidAdapter_spec.js b/test/spec/modules/inmarBidAdapter_spec.js new file mode 100644 index 00000000000..86b7ab3a8af --- /dev/null +++ b/test/spec/modules/inmarBidAdapter_spec.js @@ -0,0 +1,251 @@ +// import or require modules necessary for the test, e.g.: +import {expect} from 'chai'; // may prefer 'assert' in place of 'expect' +import { + spec +} from 'modules/inmarBidAdapter.js'; +import {config} from 'src/config.js'; + +describe('Inmar adapter tests', function () { + var DEFAULT_PARAMS_NEW_SIZES = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'inmar', + params: { + adnetId: 'ADb1f40rmi', + partnerId: 12345 + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_VIDEO = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'instream', // or 'outstream' + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'inmar', + params: { + adnetId: 'ADb1f40rmi', + partnerId: 12345 + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_WO_OPTIONAL = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [970, 250] + ], + bidder: 'inmar', + params: { + adnetId: 'ADb1f40rmi', + partnerId: 12345, + }, + auctionId: '851adee7-d843-48f9-a7e9-9ff00573fcbf', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + }]; + + var BID_RESPONSE = { + body: { + cpm: 1.50, + ad: '', + meta: { + mediaType: 'banner', + }, + width: 300, + height: 250, + creativeId: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + dealId: 'dealId' + + } + }; + + var BID_RESPONSE_VIDEO = { + body: { + cpm: 1.50, + meta: { + mediaType: 'video', + }, + width: 1, + height: 1, + creativeId: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + vastUrl: 'https://vast.com/vast.xml', + dealId: 'dealId' + } + }; + + it('Verify build request to prebid 3.0 display test', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request.data); + expect(requestContent.bidRequests[0].params).to.have.property('adnetId').and.to.equal('ADb1f40rmi'); + expect(requestContent.bidRequests[0].params).to.have.property('partnerId').and.to.equal(12345); + expect(requestContent.bidRequests[0]).to.have.property('auctionId').and.to.equal('0cb3144c-d084-4686-b0d6-f5dbe917c563'); + expect(requestContent.bidRequests[0]).to.have.property('bidId').and.to.equal('2c7c8e9c900244'); + expect(requestContent.bidRequests[0]).to.have.property('bidRequestsCount').and.to.equal(1); + expect(requestContent.bidRequests[0]).to.have.property('bidder').and.to.equal('inmar'); + expect(requestContent.bidRequests[0]).to.have.property('bidderRequestId').and.to.equal('1858b7382993ca'); + expect(requestContent.bidRequests[0]).to.have.property('adUnitCode').and.to.equal('test-div'); + expect(requestContent.refererInfo).to.have.property('referer').and.to.equal('https://domain.com'); + expect(requestContent.bidRequests[0].mediaTypes.banner).to.have.property('sizes'); + expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[0]).to.have.ordered.members([300, 250]); + expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[1]).to.have.ordered.members([300, 600]); + expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[2]).to.have.ordered.members([728, 90]); + expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[3]).to.have.ordered.members([970, 250]); + expect(requestContent.bidRequests[0]).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); + expect(requestContent.refererInfo).to.have.property('numIframes').and.to.equal(0); + }) + + it('Verify interprete response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE, request); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.ad).to.equal(''); + expect(bid.meta.mediaType).to.equal('banner'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + 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', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request); + const bid = bids[0]; + expect(bid.vastUrl).to.equal('https://vast.com/vast.xml'); + }); + + it('Verifies bidder_code', function () { + expect(spec.code).to.equal('inmar'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('inm'); + }); + + 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); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + adnetId: 'ADb1f40rmi' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + partnerId: 12345 + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + adnetId: 'ADb1f40rmi', + partnerId: 12345 + } + })).to.equal(true); + }); + + it('Verifies user syncs image', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); +}); From 083d76e46a2fe2bf36b1add0170bf03e88c4225c Mon Sep 17 00:00:00 2001 From: Ignat Khaylov Date: Wed, 30 Sep 2020 04:37:37 +0300 Subject: [PATCH 032/190] added detect referer (#5759) Co-authored-by: Ignat Khaylov --- modules/betweenBidAdapter.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index c435a5a993e..fb3fcdb8d89 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -1,5 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getAdUnitSizes, parseSizesInput } from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; + const BIDDER_CODE = 'between'; export const spec = { @@ -24,6 +26,7 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { let requests = []; const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const refInfo = getRefererInfo(); validBidRequests.forEach(i => { let params = { @@ -56,6 +59,8 @@ export const spec = { } } + if (refInfo && refInfo.referer) params.ref = refInfo.referer; + if (gdprConsent) { if (typeof gdprConsent.gdprApplies !== 'undefined') { params.gdprApplies = !!gdprConsent.gdprApplies; From 0c9bbf9ad9543ee27f4d817a11f44e08b1e44fa0 Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Wed, 30 Sep 2020 18:03:22 +0700 Subject: [PATCH 033/190] Qwarry bid adapter (#5662) * 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 Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 75 +++++++++++++ modules/qwarryBidAdapter.md | 28 +++++ test/spec/modules/qwarryBidAdapter_spec.js | 122 +++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 modules/qwarryBidAdapter.js create mode 100644 modules/qwarryBidAdapter.md create mode 100644 test/spec/modules/qwarryBidAdapter_spec.js diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js new file mode 100644 index 00000000000..36c1562324a --- /dev/null +++ b/modules/qwarryBidAdapter.js @@ -0,0 +1,75 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepClone } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'qwarry'; +export const ENDPOINT = 'https://ui-bidder.kantics.co/bid/adtag?prebid=true' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.zoneToken); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + let bids = []; + validBidRequests.forEach(bidRequest => { + bids.push({ + bidId: bidRequest.bidId, + zoneToken: bidRequest.params.zoneToken + }) + }) + + return { + method: 'POST', + url: ENDPOINT, + data: { requestId: bidderRequest.bidderRequestId, bids }, + options: { + contentType: 'application/json', + customHeaders: { + 'Rtb-Direct': true + } + } + }; + }, + + interpretResponse: function (serverResponse, request) { + const serverBody = serverResponse.body; + if (!serverBody || typeof serverBody !== 'object') { + return []; + } + + const { prebidResponse } = serverBody; + if (!prebidResponse || typeof prebidResponse !== 'object') { + return []; + } + + let bids = []; + prebidResponse.forEach(bidResponse => { + let bid = deepClone(bidResponse); + bid.cpm = parseFloat(bidResponse.cpm); + + // banner or video + if (VIDEO === bid.format) { + bid.vastXml = bid.ad; + } + + bids.push(bid); + }) + + return bids; + }, + + onBidWon: function (bid) { + if (bid.winUrl) { + ajax(bid.winUrl, null); + return true; + } + return false; + } +} + +registerBidder(spec); diff --git a/modules/qwarryBidAdapter.md b/modules/qwarryBidAdapter.md new file mode 100644 index 00000000000..056ccb51293 --- /dev/null +++ b/modules/qwarryBidAdapter.md @@ -0,0 +1,28 @@ +# Overview + +``` +Module Name: Qwarry Bidder Adapter +Module Type: Bidder Adapter +Maintainer: akascheev@asteriosoft.com +``` + +# Description + +Connects to Qwarry Bidder for bids. +Qwarry bid adapter supports Banner and Video ads. + +# Test Parameters +``` +const adUnits = [ + { + bids: [ + { + bidder: 'qwarry', + params: { + zoneToken: '?????????????????????', // zoneToken provided by Qwarry + } + } + ] + } +]; +``` diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js new file mode 100644 index 00000000000..a5bb438f384 --- /dev/null +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -0,0 +1,122 @@ +import { expect } from 'chai' +import { ENDPOINT, spec } from 'modules/qwarryBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' + +const REQUEST = { + 'bidId': '456', + 'bidder': 'qwarry', + 'params': { + zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f' + } +} + +const BIDDER_BANNER_RESPONSE = {'prebidResponse': [{ + 'ad': '
test
', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46d', + 'cpm': 900.5, + 'currency': 'USD', + 'width': 640, + 'height': 480, + 'ttl': 300, + 'creativeId': 1, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'format': 'banner' +}]} + +const BIDDER_VIDEO_RESPONSE = {'prebidResponse': [{ + 'ad': 'vast', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46z', + 'cpm': 800.4, + 'currency': 'USD', + 'width': 1024, + 'height': 768, + 'ttl': 200, + 'creativeId': 2, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'format': 'video' +}]} + +const BIDDER_NO_BID_RESPONSE = '' + +describe('qwarryBidAdapter', function () { + const adapter = newBidder(spec) + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(REQUEST)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, REQUEST) + delete bid.params.zoneToken + expect(spec.isBidRequestValid(bid)).to.equal(false) + delete bid.params + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests = [REQUEST] + const bidderRequest = spec.buildRequests(bidRequests, { bidderRequestId: '123' }) + + 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.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f' }) + expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) + expect(bidderRequest.options.contentType).to.equal('application/json') + expect(bidderRequest.url).to.equal(ENDPOINT) + }) + }) + + describe('interpretResponse', function () { + it('handles banner request : should get correct bid response', function () { + const result = spec.interpretResponse({ body: BIDDER_BANNER_RESPONSE }, {}) + + expect(result[0]).to.have.property('ad').equal('
test
') + expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46d') + expect(result[0]).to.have.property('cpm').equal(900.5) + expect(result[0]).to.have.property('currency').equal('USD') + expect(result[0]).to.have.property('width').equal(640) + expect(result[0]).to.have.property('height').equal(480) + expect(result[0]).to.have.property('ttl').equal(300) + expect(result[0]).to.have.property('creativeId').equal(1) + expect(result[0]).to.have.property('netRevenue').equal(true) + expect(result[0]).to.have.property('winUrl').equal('http://test.com') + expect(result[0]).to.have.property('format').equal('banner') + }) + + it('handles video request : should get correct bid response', function () { + const result = spec.interpretResponse({ body: BIDDER_VIDEO_RESPONSE }, {}) + + expect(result[0]).to.have.property('ad').equal('vast') + expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46z') + expect(result[0]).to.have.property('cpm').equal(800.4) + expect(result[0]).to.have.property('currency').equal('USD') + expect(result[0]).to.have.property('width').equal(1024) + expect(result[0]).to.have.property('height').equal(768) + expect(result[0]).to.have.property('ttl').equal(200) + expect(result[0]).to.have.property('creativeId').equal(2) + expect(result[0]).to.have.property('netRevenue').equal(true) + expect(result[0]).to.have.property('winUrl').equal('http://test.com') + expect(result[0]).to.have.property('format').equal('video') + expect(result[0]).to.have.property('vastXml').equal('vast') + }) + + it('handles no bid response : should get empty array', function () { + let result = spec.interpretResponse({ body: undefined }, {}) + expect(result).to.deep.equal([]) + + result = spec.interpretResponse({ body: BIDDER_NO_BID_RESPONSE }, {}) + expect(result).to.deep.equal([]) + }) + }) +}) From 9573a428dee71f7aa1ba6d7c9a3605b3b9350d32 Mon Sep 17 00:00:00 2001 From: mimenet <64042452+mimenet@users.noreply.github.com> Date: Wed, 30 Sep 2020 08:59:25 -0700 Subject: [PATCH 034/190] Allow selection of supported default targeting keys at configuration time. (#5763) * initial check-in: add ability to selectively allow default keys into GAM KV targeting. * add more descriptive test documentation to explain that the default targeting keys is checking against the key prefix to accomodate bid landscape. collate and remove targeting surrounding the key removal process. --- src/targeting.js | 47 +++++++++++++++++++++++++++ test/spec/unit/core/targeting_spec.js | 40 +++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/targeting.js b/src/targeting.js index 1b1e14fd4a6..8176bc9caff 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -181,6 +181,48 @@ export function newTargeting(auctionManager) { return []; }; + /** + * Returns filtered ad server targeting for custom and allowed keys. + * @param {targetingArray} targeting + * @param {string[]} allowedKeys + * @return {targetingArray} filtered targeting + */ + function getAllowedTargetingKeyValues(targeting, allowedKeys) { + const defaultKeyring = Object.assign({}, CONSTANTS.TARGETING_KEYS, CONSTANTS.NATIVE_KEYS); + const defaultKeys = Object.keys(defaultKeyring); + const keyDispositions = {}; + logInfo(`allowTargetingKeys - allowed keys [ ${allowedKeys.map(k => defaultKeyring[k]).join(', ')} ]`); + targeting.map(adUnit => { + const adUnitCode = Object.keys(adUnit)[0]; + const keyring = adUnit[adUnitCode]; + const keys = keyring.filter(kvPair => { + const key = Object.keys(kvPair)[0]; + // check if key is in default keys, if not, it's custom, we won't remove it. + const isCustom = defaultKeys.filter(defaultKey => key.indexOf(defaultKeyring[defaultKey]) === 0).length === 0; + // check if key explicitly allowed, if not, we'll remove it. + const found = isCustom || allowedKeys.find(allowedKey => { + const allowedKeyName = defaultKeyring[allowedKey]; + // we're looking to see if the key exactly starts with one of our default keys. + // (which hopefully means it's not custom) + const found = key.indexOf(allowedKeyName) === 0; + return found; + }); + keyDispositions[key] = !found; + return found; + }); + adUnit[adUnitCode] = keys; + }); + const removedKeys = Object.keys(keyDispositions).filter(d => keyDispositions[d]); + logInfo(`allowTargetingKeys - removed keys [ ${removedKeys.join(', ')} ]`); + // remove any empty targeting objects, as they're unnecessary. + const filteredTargeting = targeting.filter(adUnit => { + const adUnitCode = Object.keys(adUnit)[0]; + const keyring = adUnit[adUnitCode]; + return keyring.length > 0; + }); + return filteredTargeting + } + /** * Returns all ad server targeting for all ad units. * @param {string=} adUnitCode @@ -206,6 +248,11 @@ export function newTargeting(auctionManager) { }); }); + const allowedKeys = config.getConfig('targetingControls.allowTargetingKeys'); + if (Array.isArray(allowedKeys) && allowedKeys.length > 0) { + targeting = getAllowedTargetingKeyValues(targeting, allowedKeys); + } + targeting = flattenTargeting(targeting); const auctionKeysThreshold = config.getConfig('targetingControls.auctionKeyMaxChars'); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 3aae6e3c33a..5d43ed48266 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -416,6 +416,46 @@ describe('targeting tests', function () { }); }); + describe('targetingControls.allowTargetingKeys', function () { + let bid4; + + beforeEach(function() { + bid4 = utils.deepClone(bid1); + bid4.adserverTargeting = { + hb_deal: '4321', + hb_pb: '0.1', + hb_adid: '567891011', + hb_bidder: 'appnexus', + }; + bid4.bidder = bid4.bidderCode = 'appnexus'; + bid4.cpm = 0.1; // losing bid so not included if enableSendAllBids === false + bid4.dealId = '4321'; + enableSendAllBids = true; + config.setConfig({ + targetingControls: { + allowTargetingKeys: ['BIDDER', 'AD_ID', 'PRICE_BUCKET'] + } + }); + bidsReceived.push(bid4); + }); + + it('targeting should include custom keys', function () { + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('foobar'); + }); + + it('targeting should include keys prefixed by allowed default targeting keys', function () { + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('hb_bidder_rubicon', 'hb_adid_rubicon', 'hb_pb_rubicon'); + expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('hb_bidder_appnexus', 'hb_adid_appnexus', 'hb_pb_appnexus'); + }); + + it('targeting should not include keys prefixed by disallowed default targeting keys', function () { + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.not.have.all.keys(['hb_deal_appnexus', 'hb_deal_rubicon']); + }); + }); + describe('targetingControls.alwaysIncludeDeals', function () { let bid4; From 1df6a22650037fdfb4606e8296dc86e56441b5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandr=20=C5=A0t=C5=A1epelin?= Date: Thu, 1 Oct 2020 17:59:57 +0300 Subject: [PATCH 035/190] cointrafficBidAdapter: added support responding in different currencies (#5800) * New adapter "Cointraffic" added * removed mobile detection * The sizes property has been updated, added supportedMediaTypes. * feat: added support responding in different currencies * change: module description --- modules/cointrafficBidAdapter.js | 20 ++- modules/cointrafficBidAdapter.md | 5 +- .../modules/cointrafficBidAdapter_spec.js | 138 ++++++++++++++++-- 3 files changed, 149 insertions(+), 14 deletions(-) diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index aa6860d1fc6..43f5cc01ccf 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -1,9 +1,15 @@ import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js' +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js' +import { config } from '../src/config.js' const BIDDER_CODE = 'cointraffic'; const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; +const DEFAULT_CURRENCY = 'EUR'; +const ALLOWED_CURRENCIES = [ + 'EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK', 'RUB', 'TRY', + 'AUD', 'BRL', 'CAD', 'CNY', 'HKD', 'IDR', 'ILS', 'INR', 'KRW', 'MXN', 'MYR', 'NZD', 'PHP', 'SGD', 'THB', 'ZAR', +]; export const spec = { code: BIDDER_CODE, @@ -29,9 +35,19 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { const sizes = utils.parseSizesInput(bidRequest.params.size || bidRequest.sizes); + const currency = + config.getConfig(`currency.bidderCurrencyDefault.${BIDDER_CODE}`) || + config.getConfig('currency.adServerCurrency') || + DEFAULT_CURRENCY; + + if (ALLOWED_CURRENCIES.indexOf(currency) === -1) { + utils.logError('Currency is not supported - ' + currency); + return; + } const payload = { placementId: bidRequest.params.placementId, + currency: currency, sizes: sizes, bidId: bidRequest.bidId, referer: bidderRequest.refererInfo.referer, diff --git a/modules/cointrafficBidAdapter.md b/modules/cointrafficBidAdapter.md index ad608a1319e..fcbcad1cad7 100644 --- a/modules/cointrafficBidAdapter.md +++ b/modules/cointrafficBidAdapter.md @@ -7,7 +7,10 @@ Maintainer: tech@cointraffic.io ``` # Description -The Cointraffic client module makes it easy to implement Cointraffic directly into your website. To get started, simply replace the ``placementId`` with your assigned tracker key. This is dependent on the size required by your account dashboard. For additional information on this module, please contact us at ``support@cointraffic.io``. +The Cointraffic client module makes it easy to implement Cointraffic directly into your website. To get started, simply replace the ``placementId`` with your assigned tracker key. This is dependent on the size required by your account dashboard. +We support response in different currencies. Supported currencies listed [here](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html). + +For additional information on this module, please contact us at ``support@cointraffic.io``. # Test Parameters ``` diff --git a/test/spec/modules/cointrafficBidAdapter_spec.js b/test/spec/modules/cointrafficBidAdapter_spec.js index 6d948e36cb9..a2ce4cedea0 100644 --- a/test/spec/modules/cointrafficBidAdapter_spec.js +++ b/test/spec/modules/cointrafficBidAdapter_spec.js @@ -1,5 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/cointrafficBidAdapter.js'; +import { config } from 'src/config.js' +import * as utils from 'src/utils.js' const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; @@ -37,7 +39,7 @@ describe('cointrafficBidAdapter', function () { ], bidId: 'bidId12345', bidderRequestId: 'bidderRequestId12345', - auctionId: 'auctionId12345', + auctionId: 'auctionId12345' }, { bidder: 'cointraffic', @@ -50,7 +52,7 @@ describe('cointrafficBidAdapter', function () { ], bidId: 'bidId67890"', bidderRequestId: 'bidderRequestId67890', - auctionId: 'auctionId12345', + auctionId: 'auctionId12345' } ]; @@ -65,34 +67,71 @@ describe('cointrafficBidAdapter', function () { } }; - const request = spec.buildRequests(bidRequests, bidderRequests); + it('replaces currency with EUR if there is no currency provided', function () { + const request = spec.buildRequests(bidRequests, bidderRequests); + + expect(request[0].data.currency).to.equal('EUR'); + expect(request[1].data.currency).to.equal('EUR'); + }); + + it('replaces currency with EUR if there is no currency provided', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'currency.bidderCurrencyDefault.cointraffic' ? 'USD' : 'EUR' + ); + + const request = spec.buildRequests(bidRequests, bidderRequests); + + expect(request[0].data.currency).to.equal('USD'); + expect(request[1].data.currency).to.equal('USD'); + + getConfigStub.restore(); + }); + + it('throws an error if currency provided in params is not allowed', function () { + const utilsMock = sinon.mock(utils).expects('logError').twice() + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'currency.bidderCurrencyDefault.cointraffic' ? 'BTC' : 'EUR' + ); + + const request = spec.buildRequests(bidRequests, bidderRequests); + + expect(request[0]).to.undefined; + expect(request[1]).to.undefined; + + utilsMock.restore() + getConfigStub.restore(); + }); it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequests); + expect(request[0].method).to.equal('POST'); expect(request[1].method).to.equal('POST'); }); + it('attaches source and version to endpoint URL as query params', function () { + const request = spec.buildRequests(bidRequests, bidderRequests); + expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[1].url).to.equal(ENDPOINT_URL); }); }); describe('interpretResponse', function () { - let bidRequest = [ - { + it('should get the correct bid response', function () { + let bidRequest = [{ method: 'POST', url: ENDPOINT_URL, data: { placementId: 'testPlacementId', device: 'desktop', + currency: 'EUR', sizes: ['300x250'], bidId: 'bidId12345', referer: 'www.example.com' } - } - ]; + }]; - it('should get the correct bid response', function () { let serverResponse = { body: { requestId: 'bidId12345', @@ -103,7 +142,7 @@ describe('cointrafficBidAdapter', function () { height: 250, creativeId: 'creativeId12345', ttl: 90, - ad: '

I am an ad

', + ad: '

I am an ad

' } }; @@ -118,22 +157,99 @@ describe('cointrafficBidAdapter', function () { ttl: 90, ad: '

I am an ad

' }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); }); - it('should get empty bid response if server response body is empty', function () { + it('should get the correct bid response with different currency', function () { + let bidRequest = [{ + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + device: 'desktop', + currency: 'USD', + sizes: ['300x250'], + bidId: 'bidId12345', + referer: 'www.example.com' + } + }]; + let serverResponse = { - body: {} + body: { + requestId: 'bidId12345', + cpm: 3.9, + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

' + } }; + let expectedResponse = [{ + requestId: 'bidId12345', + cpm: 3.9, + currency: 'USD', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

' + }]; + + const getConfigStub = sinon.stub(config, 'getConfig').returns('USD'); + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + + getConfigStub.restore(); + }); + + it('should get empty bid response requested currency is not available', function () { + let bidRequest = [{ + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + device: 'desktop', + currency: 'BTC', + sizes: ['300x250'], + bidId: 'bidId12345', + referer: 'www.example.com' + } + }]; + + let serverResponse = {}; + let expectedResponse = []; + const getConfigStub = sinon.stub(config, 'getConfig').returns('BTC'); + let result = spec.interpretResponse(serverResponse, bidRequest[0]); expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + + getConfigStub.restore(); }); it('should get empty bid response if no server response', function () { + let bidRequest = [{ + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + device: 'desktop', + currency: 'EUR', + sizes: ['300x250'], + bidId: 'bidId12345', + referer: 'www.example.com' + } + }]; + let serverResponse = {}; let expectedResponse = []; From 303ccaee3e190b3bea50a50b8eb75460e9922818 Mon Sep 17 00:00:00 2001 From: Monis Qadri Date: Thu, 1 Oct 2020 21:09:47 +0530 Subject: [PATCH 036/190] Send proper slot info in case of adUnitPath (#5810) - using `getGptSlotInfoForAdUnitCode` to get `divId` in case of `adUnitPath` - added test case for visibility via `adUnitPath` Co-authored-by: monis.q --- modules/medianetBidAdapter.js | 11 +++++++-- test/spec/modules/medianetBidAdapter_spec.js | 26 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 5decaa148e3..49e2bdc225f 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -113,8 +113,15 @@ function getWindowSize() { } } -function getCoordinates(id) { - const element = document.getElementById(id); +function getCoordinates(adUnitCode) { + let element = document.getElementById(adUnitCode); + if (!element && adUnitCode.indexOf('/') !== -1) { + // now it means that adUnitCode is GAM AdUnitPath + const {divId} = utils.getGptSlotInfoForAdUnitCode(adUnitCode); + if (utils.isStr(divId)) { + element = document.getElementById(divId); + } + } if (element && element.getBoundingClientRect) { const rect = element.getBoundingClientRect(); let coordinates = {}; diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 649929056fa..1b2207de842 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -1,7 +1,9 @@ import {expect} from 'chai'; import {spec} from 'modules/medianetBidAdapter.js'; +import { makeSlot } from '../integration/faker/googletag.js'; import { config } from 'src/config.js'; +$$PREBID_GLOBAL$$.version = $$PREBID_GLOBAL$$.version || 'version'; let VALID_BID_REQUEST = [{ 'bidder': 'medianet', 'params': { @@ -1306,6 +1308,30 @@ describe('Media.net bid adapter', function () { expect(data.imp[1].ext).to.not.have.ownPropertyDescriptor('viewability'); expect(data.imp[1].ext.visibility).to.equal(0); }); + it('slot visibility should be calculable even in case of adUnitPath', function () { + const code = '/19968336/header-bid-tag-0'; + const divId = 'div-gpt-ad-1460505748561-0'; + window.googletag.pubads().setSlots([makeSlot({ code, divId })]); + + let boundingRect = { + top: 1010, + left: 1010, + bottom: 1050, + right: 1050 + }; + documentStub.withArgs(divId).returns({ + getBoundingClientRect: () => boundingRect + }); + documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({ + getBoundingClientRect: () => boundingRect + }); + + const bidRequest = [{...VALID_BID_REQUEST[0], adUnitCode: code}] + const bidReq = spec.buildRequests(bidRequest, VALID_AUCTIONDATA); + const data = JSON.parse(bidReq.data); + expect(data.imp[0].ext.visibility).to.equal(2); + expect(data.imp[0].ext.viewability).to.equal(0); + }); }); describe('getUserSyncs', function () { From c1c6aaad95c51761aa00c21c0b6d733c31c76bb9 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Thu, 1 Oct 2020 12:02:47 -0400 Subject: [PATCH 037/190] Update to rubiconBidAdapter to include criteoId support (#5806) --- modules/rubiconBidAdapter.js | 6 +++++- test/spec/modules/rubiconBidAdapter_spec.js | 21 ++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 829fd208887..17e64a7b76c 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -253,7 +253,7 @@ export const spec = { const eids = utils.deepAccess(bidderRequest, 'bids.0.userIdAsEids'); if (eids && eids.length) { // filter out unsupported id systems - utils.deepSetValue(data, 'user.ext.eids', eids.filter(eid => ['adserver.org', 'pubcid.org', 'liveintent.com', 'liveramp.com', 'sharedid.org'].indexOf(eid.source) !== -1)); + utils.deepSetValue(data, 'user.ext.eids', eids.filter(eid => ['adserver.org', 'pubcid.org', 'liveintent.com', 'liveramp.com', 'sharedid.org', 'criteo.com'].indexOf(eid.source) !== -1)); // liveintent requires additional props to be set const liveIntentEid = find(data.user.ext.eids, eid => eid.source === 'liveintent.com'); @@ -538,6 +538,10 @@ export const spec = { if (sharedId) { data['eid_sharedid.org'] = `${sharedId.uids[0].id}^${sharedId.uids[0].atype}^${sharedId.uids[0].ext.third}`; } + const criteoId = find(bidRequest.userIdAsEids, eid => eid.source === 'criteo.com'); + if (criteoId) { + data['eid_criteo.com'] = `${criteoId.uids[0].id}^${criteoId.uids[0].atype}`; + } } // set ppuid value from config value diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index dd25fafb149..159632eee8a 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -245,7 +245,8 @@ describe('the rubicon adapter', function () { uids: [{ id: '4444444' }] - }] + }], + criteoId: '1111' }; bid.userIdAsEids = createEidsArray(bid.userId); bid.storedAuctionResponse = 11111; @@ -1371,6 +1372,20 @@ describe('the rubicon adapter', function () { }); }); + describe('Criteo support', function () { + it('should send eid_criteo.com when userIdAsEids contains criteo', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + criteoId: '1111' + }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['eid_criteo.com']).to.equal('1111^1'); + }); + }); + describe('SharedID support', function () { it('should send sharedid when userIdAsEids contains sharedId', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); @@ -1654,6 +1669,10 @@ describe('the rubicon adapter', function () { expect(post.user.ext.eids[4].source).to.equal('pubcid.org'); expect(post.user.ext.eids[4].uids[0].atype).to.equal(1); expect(post.user.ext.eids[4].uids[0].id).to.equal('4000'); + // CriteoId should exist + expect(post.user.ext.eids[5].source).to.equal('criteo.com'); + expect(post.user.ext.eids[5].uids[0].id).to.equal('1111'); + expect(post.user.ext.eids[5].uids[0].atype).to.equal(1); expect(post.regs.ext.gdpr).to.equal(1); expect(post.regs.ext.us_privacy).to.equal('1NYN'); From 804ff2fadff746d98e3484051852c65fb5769b7f Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Thu, 1 Oct 2020 15:07:40 -0400 Subject: [PATCH 038/190] appnexus bid adapter: criteo back to tpuids (#5808) --- modules/appnexusBidAdapter.js | 10 ++++++---- test/spec/modules/appnexusBidAdapter_spec.js | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 12bc6a8105c..f3ebf9bf4f6 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -220,15 +220,17 @@ export const spec = { }); } - let eids = []; const criteoId = utils.deepAccess(bidRequests[0], `userId.criteoId`); if (criteoId) { - eids.push({ - source: 'criteo.com', - id: criteoId + let tpuids = []; + tpuids.push({ + 'provider': 'criteo', + 'user_id': criteoId }); + payload.tpuids = tpuids; } + let eids = []; const tdid = utils.deepAccess(bidRequests[0], `userId.tdid`); if (tdid) { eids.push({ diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 7e985045c45..7b563c4ec1d 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -808,7 +808,7 @@ describe('AppNexusAdapter', function () { expect(request.options).to.deep.equal({withCredentials: false}); }); - it('should populate eids array when ttd id and criteo is available', function () { + it('should populate eids and tpuids when ttd id and criteo is available', function () { const bidRequest = Object.assign({}, bidRequests[0], { userId: { tdid: 'sample-userid', @@ -824,9 +824,9 @@ describe('AppNexusAdapter', function () { rti_partner: 'TDID' }); - expect(payload.eids).to.deep.include({ - source: 'criteo.com', - id: 'sample-criteo-userid', + expect(payload.tpuids).to.deep.include({ + provider: 'criteo', + user_id: 'sample-criteo-userid', }); }); }) From 907a9c7bac5f3f400d68ee7e980356d879f5d0d3 Mon Sep 17 00:00:00 2001 From: yuvalgg <57989766+yuvalgg@users.noreply.github.com> Date: Fri, 2 Oct 2020 00:39:35 +0300 Subject: [PATCH 039/190] Intentiq id add validation (#5797) * Add validity check to ignore not-available response * Added tests * Added error log --- modules/intentIqIdSystem.js | 36 ++++++++++++++++++++-- test/spec/modules/intentIqIdSystem_spec.js | 8 +++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 88da7d44481..bb74b1210cb 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -11,6 +11,32 @@ import {submodule} from '../src/hook.js' const MODULE_NAME = 'intentIqId'; +const NOT_AVAILABLE = 'NA'; + +/** + * Verify the id is valid - Id value or Not Found (ignore not available response) + * @param id + * @returns {boolean|*|boolean} + */ +function isValidId(id) { + return id && id != '' && id != NOT_AVAILABLE && isValidResponse(id); +} + +/** + * Ignore not available response JSON + * @param obj + * @returns {boolean} + */ +function isValidResponse(obj) { + try { + obj = JSON.parse(obj); + return obj && obj['RESULT'] != NOT_AVAILABLE; + } catch (error) { + utils.logError(error); + return true; + } +} + /** @type {Submodule} */ export const intentIqIdSubmodule = { /** @@ -22,10 +48,10 @@ export const intentIqIdSubmodule = { * decode the stored id value for passing to bid requests * @function * @param {{string}} value - * @returns {{intentIqId:string}} + * @returns {{intentIqId: {string}}|undefined} */ decode(value) { - return (value && value != '') ? { 'intentIqId': value } : undefined; + return isValidId(value) ? { 'intentIqId': value } : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -47,7 +73,11 @@ export const intentIqIdSubmodule = { const resp = function (callback) { const callbacks = { success: response => { - callback(response); + if (isValidId(response)) { + callback(response); + } else { + callback(); + } }, error: error => { utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 2dccde18855..951cfab5f50 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -55,6 +55,14 @@ describe('IntentIQ tests', function () { expect(callBackSpy.calledOnce).to.be.true; }); + it('should ignore NA and invalid responses', function () { + let resp = JSON.stringify({'RESULT': 'NA'}); + expect(intentIqIdSubmodule.decode(resp)).to.equal(undefined); + expect(intentIqIdSubmodule.decode('NA')).to.equal(undefined); + expect(intentIqIdSubmodule.decode('')).to.equal(undefined); + expect(intentIqIdSubmodule.decode(undefined)).to.equal(undefined); + }); + it('should call the IntentIQ endpoint with only partner, pai', function () { let callBackSpy = sinon.spy(); let submoduleCallback = intentIqIdSubmodule.getId(paiConfigParams).callback; From b5cf481ec8ba56c43915b78b1a67c6717bf4c676 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Sun, 4 Oct 2020 21:01:25 -0700 Subject: [PATCH 040/190] remove digitrust from rubicon bid adapter (#5798) --- modules/rubiconBidAdapter.js | 55 ----- test/spec/modules/rubiconBidAdapter_spec.js | 227 -------------------- 2 files changed, 282 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 17e64a7b76c..36b3de8403d 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -15,17 +15,6 @@ config.getConfig('rubicon', config => { }); const GVLID = 52; -const DIGITRUST_PROP_NAMES = { - FASTLANE: { - id: 'dt.id', - keyv: 'dt.keyv', - pref: 'dt.pref' - }, - PREBID_SERVER: { - id: 'id', - keyv: 'keyv' - } -}; var sizeMap = { 1: '468x60', @@ -230,11 +219,6 @@ export const spec = { addVideoParameters(data, bidRequest); - const digiTrust = _getDigiTrustQueryParams(bidRequest, 'PREBID_SERVER'); - if (digiTrust) { - utils.deepSetValue(data, 'user.ext.digitrust', digiTrust); - } - if (bidderRequest.gdprConsent) { // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module let gdprApplies; @@ -405,9 +389,6 @@ export const spec = { 'tpid_tdid', 'tpid_liveintent.com', 'tg_v.LIseg', - 'dt.id', - 'dt.keyv', - 'dt.pref', 'rf', 'p_geo.latitude', 'p_geo.longitude', @@ -617,10 +598,6 @@ export const spec = { data['tg_i.dfp_ad_unit_code'] = gamAdUnit.replace(/^\/+/, ''); } - // digitrust properties - const digitrustParams = _getDigiTrustQueryParams(bidRequest, 'FASTLANE'); - Object.assign(data, digitrustParams); - if (config.getConfig('coppa') === true) { data['coppa'] = 1; } @@ -852,38 +829,6 @@ function _getScreenResolution() { return [window.screen.width, window.screen.height].join('x'); } -function _getDigiTrustQueryParams(bidRequest = {}, endpointName) { - if (!endpointName || !DIGITRUST_PROP_NAMES[endpointName]) { - return null; - } - const propNames = DIGITRUST_PROP_NAMES[endpointName]; - - function getDigiTrustId() { - const bidRequestDigitrust = utils.deepAccess(bidRequest, 'userId.digitrustid.data'); - if (bidRequestDigitrust) { - return bidRequestDigitrust; - } - - let digiTrustUser = (window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'}))); - return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; - } - - let digiTrustId = getDigiTrustId(); - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return null; - } - - const digiTrustQueryParams = { - [propNames.id]: digiTrustId.id, - [propNames.keyv]: digiTrustId.keyv - }; - if (propNames.pref) { - digiTrustQueryParams[propNames.pref] = 0; - } - return digiTrustQueryParams; -} - /** * @param {BidRequest} bidRequest * @param bidderRequest diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 159632eee8a..aee792714cb 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -702,233 +702,6 @@ describe('the rubicon adapter', function () { expect(data['rp_floor']).to.equal('2'); }); - it('should send digitrust params', function () { - window.DigiTrust = { - getUser: function () { - } - }; - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: true, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 'testKeyV' - } - }) - ); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let expectedQuery = { - 'dt.id': 'testId', - 'dt.keyv': 'testKeyV', - 'dt.pref': '0' - }; - - // test that all values above are both present and correct - Object.keys(expectedQuery).forEach(key => { - let value = expectedQuery[key]; - expect(data[key]).to.equal(value); - }); - - delete window.DigiTrust; - }); - - it('should not send digitrust params when DigiTrust not loaded', function () { - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - }); - - it('should not send digitrust params due to optout', function () { - window.DigiTrust = { - getUser: function () { - } - }; - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: true, - identity: { - privacy: {optout: true}, - id: 'testId', - keyv: 'testKeyV' - } - }) - ); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - delete window.DigiTrust; - }); - - it('should not send digitrust params due to failure', function () { - window.DigiTrust = { - getUser: function () { - } - }; - sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => - ({ - success: false, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 'testKeyV' - } - }) - ); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - delete window.DigiTrust; - }); - - describe('digiTrustId config', function () { - beforeEach(function () { - window.DigiTrust = { - getUser: sandbox.spy() - }; - }); - - afterEach(function () { - delete window.DigiTrust; - }); - - it('should send digiTrustId config params', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - digiTrustId: { - success: true, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 'testKeyV' - } - } - }; - return config[key]; - }); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let expectedQuery = { - 'dt.id': 'testId', - 'dt.keyv': 'testKeyV' - }; - - // test that all values above are both present and correct - Object.keys(expectedQuery).forEach(key => { - let value = expectedQuery[key]; - expect(data[key]).to.equal(value); - }); - - // should not have called DigiTrust.getUser() - expect(window.DigiTrust.getUser.notCalled).to.equal(true); - }); - - it('should not send digiTrustId config params due to optout', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - digiTrustId: { - success: true, - identity: { - privacy: {optout: true}, - id: 'testId', - keyv: 'testKeyV' - } - } - } - return config[key]; - }); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - // should not have called DigiTrust.getUser() - expect(window.DigiTrust.getUser.notCalled).to.equal(true); - }); - - it('should not send digiTrustId config params due to failure', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - digiTrustId: { - success: false, - identity: { - privacy: {optout: false}, - id: 'testId', - keyv: 'testKeyV' - } - } - } - return config[key]; - }); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - // should not have called DigiTrust.getUser() - expect(window.DigiTrust.getUser.notCalled).to.equal(true); - }); - - it('should not send digiTrustId config params if they do not exist', function () { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = {}; - return config[key]; - }); - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - - let undefinedKeys = ['dt.id', 'dt.keyv']; - - // Test that none of the DigiTrust keys are part of the query - undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); - }); - - // should have called DigiTrust.getUser() once - expect(window.DigiTrust.getUser.calledOnce).to.equal(true); - }); - }); - describe('GDPR consent config', function () { it('should send "gdpr" and "gdpr_consent", when gdprConsent defines consentString and gdprApplies', function () { createGdprBidderRequest(true); From ca22a45991077d4dc5fa87582568b3eb94236982 Mon Sep 17 00:00:00 2001 From: Maxime Lequain Date: Mon, 5 Oct 2020 11:14:38 +0200 Subject: [PATCH 041/190] add native preset handling and automatic price macro replacement (#5807) Co-authored-by: Maxime Lequain --- modules/adotBidAdapter.js | 97 ++++++++++++++++++------ modules/adotBidAdapter.md | 64 ++++++++-------- test/spec/modules/adotBidAdapter_spec.js | 74 +++++++++++------- 3 files changed, 149 insertions(+), 86 deletions(-) diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index 911da416cfe..3d0af864a31 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -1,14 +1,26 @@ import {Renderer} from '../src/Renderer.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {isStr, isArray, isNumber, isPlainObject, isBoolean, logError} from '../src/utils.js'; +import {isStr, isArray, isNumber, isPlainObject, isBoolean, logError, replaceAuctionPrice} from '../src/utils.js'; import find from 'core-js-pure/features/array/find.js'; const ADAPTER_VERSION = 'v1.0.0'; const BID_METHOD = 'POST'; const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding/bidrequest'; +const FIRST_PRICE = 1; +const NET_REVENUE = true; +// eslint-disable-next-line no-template-curly-in-string +const AUCTION_PRICE = '${AUCTION_PRICE}'; +const TTL = 10; + const SUPPORTED_VIDEO_CONTEXTS = ['instream', 'outstream']; const SUPPORTED_INSTREAM_CONTEXTS = ['pre-roll', 'mid-roll', 'post-roll']; +const SUPPORTED_VIDEO_MIMES = ['video/mp4']; +const BID_SUPPORTED_MEDIA_TYPES = ['banner', 'video', 'native']; + +const DOMAIN_REGEX = new RegExp('//([^/]*)'); +const OUTSTREAM_VIDEO_PLAYER_URL = 'https://adserver.adotmob.com/video/player.min.js'; + const NATIVE_PLACEMENTS = { title: {id: 1, name: 'title'}, icon: {id: 2, type: 1, name: 'img'}, @@ -18,15 +30,9 @@ const NATIVE_PLACEMENTS = { cta: {id: 6, type: 12, name: 'data'} }; const NATIVE_ID_MAPPING = {1: 'title', 2: 'icon', 3: 'image', 4: 'sponsoredBy', 5: 'body', 6: 'cta'}; -const SUPPORTED_VIDEO_MIMES = ['video/mp4']; -const DOMAIN_REGEX = new RegExp('//([^/]*)'); -const FIRST_PRICE = 1; -const BID_SUPPORTED_MEDIA_TYPES = ['banner', 'video', 'native']; -const TTL = 10; -const NET_REVENUE = true; -// eslint-disable-next-line no-template-curly-in-string -const AUCTION_PRICE = '${AUCTION_PRICE}'; -const OUTSTREAM_VIDEO_PLAYER_URL = 'https://adserver.adotmob.com/video/player.min.js'; +const NATIVE_PRESET_FORMATTERS = { + image: formatNativePresetImage +} function isNone(value) { return (value === null) || (value === undefined); @@ -183,6 +189,7 @@ function generateImpressionsFromAdUnit(acc, adUnit) { const {bidId, mediaTypes, params} = adUnit; const {placementId} = params; const pmp = {}; + const ext = {placementId}; if (placementId) pmp.deals = [{id: placementId}] @@ -193,8 +200,8 @@ function generateImpressionsFromAdUnit(acc, adUnit) { const impId = `${bidId}_${index}`; if (mediaType === 'banner') return acc.concat(generateBannerFromAdUnit(impId, data, params)); - if (mediaType === 'video') return acc.concat({id: impId, video: generateVideoFromAdUnit(data, params), pmp}); - if (mediaType === 'native') return acc.concat({id: impId, native: generateNativeFromAdUnit(data, params), pmp}); + if (mediaType === 'video') return acc.concat({id: impId, video: generateVideoFromAdUnit(data, params), pmp, ext}); + if (mediaType === 'native') return acc.concat({id: impId, native: generateNativeFromAdUnit(data, params), pmp, ext}); }, []); return acc.concat(imps); @@ -208,10 +215,11 @@ function generateBannerFromAdUnit(impId, data, params) { const {position, placementId} = params; const pos = position || 0; const pmp = {}; + const ext = {placementId}; if (placementId) pmp.deals = [{id: placementId}] - return data.sizes.map(([w, h], index) => ({id: `${impId}_${index}`, banner: {format: [{w, h}], w, h, pos}, pmp})); + return data.sizes.map(([w, h], index) => ({id: `${impId}_${index}`, banner: {format: [{w, h}], w, h, pos}, pmp, ext})); } function generateVideoFromAdUnit(data, params) { @@ -254,19 +262,31 @@ function computeStartDelay(data, params) { } function generateNativeFromAdUnit(data, params) { - const placements = NATIVE_PLACEMENTS; + const {type} = data; + const presetFormatter = type && NATIVE_PRESET_FORMATTERS[data.type]; + const nativeFields = presetFormatter ? presetFormatter(data) : data; + const assets = Object - .keys(data) + .keys(nativeFields) .reduce((acc, placement) => { - const placementData = data[placement]; - const assetInfo = placements[placement]; + const placementData = nativeFields[placement]; + const assetInfo = NATIVE_PLACEMENTS[placement]; if (!assetInfo) return acc; const {id, name, type} = assetInfo; - const {required, len, sizes} = placementData; - const wmin = sizes && sizes[0]; - const hmin = sizes && sizes[1]; + const {required, len, sizes = []} = placementData; + let wmin; + let hmin; + + if (isArray(sizes[0])) { + wmin = sizes[0][0]; + hmin = sizes[0][1]; + } else { + wmin = sizes[0]; + hmin = sizes[1]; + } + const content = {}; if (type) content.type = type; @@ -284,6 +304,32 @@ function generateNativeFromAdUnit(data, params) { }; } +function formatNativePresetImage(data) { + const sizes = data.sizes; + + return { + image: { + required: true, + sizes + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + body: { + required: false + }, + cta: { + required: false + }, + icon: { + required: false + } + }; +} + function generateSiteFromAdUnitContext(adUnitContext) { if (!adUnitContext || !adUnitContext.refererInfo) return null; @@ -448,7 +494,7 @@ function generateAdFromBid(bid, bidResponse, serverRequest) { mediaType: bid.ext.adot.media_type, }; - if (isBidANative(bid)) return {...base, native: formatNativeData(bid.adm)}; + if (isBidANative(bid)) return {...base, native: formatNativeData(bid)}; const size = getSizeFromBid(bid, impressionData); const creative = getCreativeFromBid(bid, impressionData); @@ -465,7 +511,7 @@ function generateAdFromBid(bid, bidResponse, serverRequest) { }; } -function formatNativeData(adm) { +function formatNativeData({adm, price}) { const parsedAdm = tryParse(adm); const {assets, link: {url, clicktrackers}, imptrackers, jstracker} = parsedAdm.native; const placements = NATIVE_PLACEMENTS; @@ -480,7 +526,7 @@ function formatNativeData(adm) { }, { clickUrl: url, clickTrackers: clicktrackers, - impressionTrackers: imptrackers, + impressionTrackers: imptrackers && imptrackers.map(impTracker => replaceAuctionPrice(impTracker, price)), javascriptTrackers: jstracker && [jstracker] }); } @@ -503,10 +549,11 @@ function getSizeFromBid(bid, impressionData) { function getCreativeFromBid(bid, impressionData) { const shouldUseAdMarkup = !!bid.adm; + const price = bid.price; return { - markup: shouldUseAdMarkup ? bid.adm : null, - markupUrl: !shouldUseAdMarkup ? bid.nurl : null, + markup: shouldUseAdMarkup ? replaceAuctionPrice(bid.adm, price) : null, + markupUrl: !shouldUseAdMarkup ? replaceAuctionPrice(bid.nurl, price) : null, renderer: getRendererFromBid(bid, impressionData) }; } diff --git a/modules/adotBidAdapter.md b/modules/adotBidAdapter.md index 88c8fb0b936..7fc1d84ee60 100644 --- a/modules/adotBidAdapter.md +++ b/modules/adotBidAdapter.md @@ -114,39 +114,37 @@ const adUnit = { code: 'test-div', mediaTypes: { native: { - native: { - image: { - // Field required status - required: false, - // Image dimensions supported by the native ad unit. - // Each ad unit size is formatted as follows: [width, height]. - sizes: [100, 50] - }, - title: { - // Field required status - required: false, - // Maximum length of the title - len: 140 - }, - sponsoredBy: { - // Field required status - required: false - }, - clickUrl: { - // Field required status - required: false - }, - body: { - // Field required status - required: false - }, - icon: { - // Field required status - required: false, - // Icon dimensions supported by the native ad unit. - // Each ad unit size is formatted as follows: [width, height]. - sizes: [50, 50] - } + image: { + // Field required status + required: false, + // Image dimensions supported by the native ad unit. + // Each ad unit size is formatted as follows: [width, height]. + sizes: [100, 50] + }, + title: { + // Field required status + required: false, + // Maximum length of the title + len: 140 + }, + sponsoredBy: { + // Field required status + required: false + }, + clickUrl: { + // Field required status + required: false + }, + body: { + // Field required status + required: false + }, + icon: { + // Field required status + required: false, + // Icon dimensions supported by the native ad unit. + // Each ad unit size is formatted as follows: [width, height]. + sizes: [50, 50] } } }, diff --git a/test/spec/modules/adotBidAdapter_spec.js b/test/spec/modules/adotBidAdapter_spec.js index 594fc4ac7b7..d580cd763a4 100644 --- a/test/spec/modules/adotBidAdapter_spec.js +++ b/test/spec/modules/adotBidAdapter_spec.js @@ -2062,10 +2062,11 @@ describe('Adot Adapter', function () { serverResponse.body.cur = 'USD'; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); expect(ads[0].vastXml).to.equal(null); expect(ads[0].vastUrl).to.equal(null); @@ -2087,10 +2088,13 @@ describe('Adot Adapter', function () { const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); + const adm2WithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[1].adm, serverResponse.body.seatbid[0].bid[1].price); + expect(ads).to.be.an('array').and.to.have.length(2); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); expect(ads[0].vastXml).to.equal(null); expect(ads[0].vastUrl).to.equal(null); @@ -2104,7 +2108,7 @@ describe('Adot Adapter', function () { expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('banner'); expect(ads[0].renderer).to.equal(null); expect(ads[1].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[1].bidId); - expect(ads[1].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[1].adm); + expect(ads[1].ad).to.exist.and.to.be.a('string').and.to.have.string(adm2WithAuctionPriceReplaced); expect(ads[1].adUrl).to.equal(null); expect(ads[1].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[1].crid); expect(ads[1].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].price); @@ -2592,10 +2596,11 @@ describe('Adot Adapter', function () { const serverResponse = examples.serverResponse_banner; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); expect(ads[0].vastXml).to.equal(null); expect(ads[0].vastUrl).to.equal(null); @@ -2617,10 +2622,11 @@ describe('Adot Adapter', function () { serverResponse.body.seatbid[0].bid[0].nurl = undefined; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.equal(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); expect(ads[0].vastXml).to.equal(null); expect(ads[0].vastUrl).to.equal(null); @@ -2642,11 +2648,12 @@ describe('Adot Adapter', function () { serverResponse.body.seatbid[0].bid[0].adm = undefined; const ads = spec.interpretResponse(serverResponse, serverRequest); + const nurlWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].nurl, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); expect(ads[0].ad).to.equal(null); - expect(ads[0].adUrl).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].nurl); + expect(ads[0].adUrl).to.exist.and.to.be.a('string').and.to.equal(nurlWithAuctionPriceReplaced); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); @@ -2721,12 +2728,13 @@ describe('Adot Adapter', function () { const serverResponse = examples.serverResponse_video_instream; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); expect(ads[0].vastUrl).to.equal(null); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); @@ -2745,12 +2753,13 @@ describe('Adot Adapter', function () { const serverResponse = examples.serverResponse_video_outstream; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); expect(ads[0].vastUrl).to.equal(null); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); @@ -2769,12 +2778,14 @@ describe('Adot Adapter', function () { const serverResponse = examples.serverResponse_video_instream_outstream; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); + const adm2WithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[1].adm, serverResponse.body.seatbid[0].bid[1].price); expect(ads).to.be.an('array').and.to.have.length(2); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); expect(ads[0].vastUrl).to.equal(null); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); @@ -2786,9 +2797,9 @@ describe('Adot Adapter', function () { expect(ads[0].mediaType).to.exist.and.to.be.a('string').and.to.equal('video'); expect(ads[0].renderer).to.equal(null); expect(ads[1].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[1].bidId); - expect(ads[1].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[1].adm); + expect(ads[1].ad).to.exist.and.to.be.a('string').and.to.have.string(adm2WithAuctionPriceReplaced); expect(ads[1].adUrl).to.equal(null); - expect(ads[1].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[1].vastXml).to.equal(adm2WithAuctionPriceReplaced); expect(ads[1].vastUrl).to.equal(null); expect(ads[1].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[1].crid); expect(ads[1].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[1].price); @@ -2808,12 +2819,13 @@ describe('Adot Adapter', function () { serverResponse.body.seatbid[0].bid[0].nurl = undefined; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); expect(ads[0].vastUrl).to.equal(null); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); @@ -2833,13 +2845,14 @@ describe('Adot Adapter', function () { serverResponse.body.seatbid[0].bid[0].adm = undefined; const ads = spec.interpretResponse(serverResponse, serverRequest); + const nurlWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].nurl, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); expect(ads[0].ad).to.equal(null); - expect(ads[0].adUrl).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].nurl); + expect(ads[0].adUrl).to.exist.and.to.be.a('string').and.to.have.string(nurlWithAuctionPriceReplaced); expect(ads[0].vastXml).to.equal(null); - expect(ads[0].vastUrl).to.equal(serverResponse.body.seatbid[0].bid[0].nurl); + expect(ads[0].vastUrl).to.equal(nurlWithAuctionPriceReplaced); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); expect(ads[0].currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.cur); @@ -2858,12 +2871,13 @@ describe('Adot Adapter', function () { serverResponse.body.seatbid[0].bid[0].h = 500; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); expect(ads[0].vastUrl).to.equal(null); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); @@ -2883,12 +2897,13 @@ describe('Adot Adapter', function () { serverResponse.body.seatbid[0].bid[0].w = 500; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); expect(ads[0].vastUrl).to.equal(null); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); @@ -2909,12 +2924,13 @@ describe('Adot Adapter', function () { serverResponse.body.seatbid[0].bid[0].h = 400; const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); expect(ads[0].vastUrl).to.equal(null); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); @@ -2934,12 +2950,13 @@ describe('Adot Adapter', function () { const serverResponse = utils.deepClone(examples.serverResponse_video_instream); const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); expect(ads[0].vastUrl).to.equal(null); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); @@ -2959,12 +2976,13 @@ describe('Adot Adapter', function () { const serverResponse = utils.deepClone(examples.serverResponse_video_instream); const ads = spec.interpretResponse(serverResponse, serverRequest); + const admWithAuctionPriceReplaced = utils.replaceAuctionPrice(serverResponse.body.seatbid[0].bid[0].adm, serverResponse.body.seatbid[0].bid[0].price); expect(ads).to.be.an('array').and.to.have.length(1); expect(ads[0].requestId).to.exist.and.to.be.a('string').and.to.equal(serverRequest._adot_internal.impressions[0].bidId); - expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].ad).to.exist.and.to.be.a('string').and.to.have.string(admWithAuctionPriceReplaced); expect(ads[0].adUrl).to.equal(null); - expect(ads[0].vastXml).to.equal(serverResponse.body.seatbid[0].bid[0].adm); + expect(ads[0].vastXml).to.equal(admWithAuctionPriceReplaced); expect(ads[0].vastUrl).to.equal(null); expect(ads[0].creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].crid); expect(ads[0].cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); From 664ec3bbc72d06d20e3f0cdc581cb386ef89ca23 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Mon, 5 Oct 2020 16:04:40 -0700 Subject: [PATCH 042/190] fix some video request params (#5799) --- modules/rubiconBidAdapter.js | 4 +-- test/spec/modules/rubiconBidAdapter_spec.js | 36 +++++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 36b3de8403d..a446cbc3ce0 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -154,9 +154,9 @@ export const spec = { source: { tid: bidRequest.transactionId }, - tmax: config.getConfig('TTL') || 1000, + tmax: bidderRequest.timeout, imp: [{ - exp: 300, + exp: config.getConfig('s2sConfig.defaultTtl'), id: bidRequest.adUnitCode, secure: 1, ext: { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index aee792714cb..b2adc1f39ff 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1381,11 +1381,11 @@ describe('the rubicon adapter', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let post = request.data; - expect(post).to.have.property('imp') + expect(post).to.have.property('imp'); // .with.length.of(1); let imp = post.imp[0]; expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(imp.exp).to.equal(300); + expect(imp.exp).to.equal(undefined); // now undefined expect(imp.video.w).to.equal(640); expect(imp.video.h).to.equal(480); expect(imp.video.pos).to.equal(1); @@ -1542,6 +1542,36 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); }); + it('should send video exp param correctly when set', function () { + createVideoBidderRequest(); + config.setConfig({s2sConfig: {defaultTtl: 600}}); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + + // should exp set to the right value according to config + let imp = post.imp[0]; + expect(imp.exp).to.equal(600); + }); + + it('should not send video exp at all if not set in s2sConfig config', function () { + createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + + // should exp set to the right value according to config + let imp = post.imp[0]; + // bidderFactory stringifies request body before sending so removes undefined attributes: + expect(imp.exp).to.equal(undefined); + }); + + it('should send tmax as the bidderRequest timeout value', function () { + createVideoBidderRequest(); + bidderRequest.timeout = 3333; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + expect(post.tmax).to.equal(3333); + }); + it('should send correct bidfloor to PBS', function () { createVideoBidderRequest(); @@ -1891,7 +1921,7 @@ describe('the rubicon adapter', function () { // .with.length.of(1); let imp = post.imp[0]; expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(imp.exp).to.equal(300); + expect(imp.exp).to.equal(undefined); expect(imp.video.w).to.equal(640); expect(imp.video.h).to.equal(480); expect(imp.video.pos).to.equal(1); From aa1e5e3b1f9025c97d18a633c147c91af15e9b6e Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 6 Oct 2020 16:17:55 +0200 Subject: [PATCH 043/190] expose full user id config (including storage) to user id modules (#5803) * expose full user id config (including storage) to user id modules, rather than just the params object * update docs to `SubmoduleConfig` * more doc fixes * missed one doc --- modules/britepoolIdSystem.js | 5 +- modules/criteoIdSystem.js | 4 +- modules/haloIdSystem.js | 4 +- modules/id5IdSystem.js | 12 +++-- modules/identityLinkIdSystem.js | 5 +- modules/intentIqIdSystem.js | 5 +- modules/liveIntentIdSystem.js | 10 ++-- modules/lotamePanoramaIdSystem.js | 7 +-- modules/merkleIdSystem.js | 5 +- modules/netIdSystem.js | 4 +- modules/parrableIdSystem.js | 5 +- modules/pubCommonIdSystem.js | 8 +-- modules/pubProvidedSystem.js | 5 +- modules/sharedIdSystem.js | 9 ++-- modules/unifiedIdSystem.js | 5 +- modules/userId/index.js | 19 +++---- modules/zeotapIdPlusIdSystem.js | 2 +- test/spec/modules/britepoolIdSystem_spec.js | 10 ++-- test/spec/modules/id5IdSystem_spec.js | 18 ++++--- .../spec/modules/identityLinkIdSystem_spec.js | 6 +-- test/spec/modules/intentIqIdSystem_spec.js | 14 ++--- test/spec/modules/liveIntentIdSystem_spec.js | 28 +++++----- test/spec/modules/parrableIdSystem_spec.js | 54 +++++++++---------- test/spec/modules/userId_spec.js | 2 +- 24 files changed, 131 insertions(+), 115 deletions(-) diff --git a/modules/britepoolIdSystem.js b/modules/britepoolIdSystem.js index 90fd159571f..3bf416957d2 100644 --- a/modules/britepoolIdSystem.js +++ b/modules/britepoolIdSystem.js @@ -29,11 +29,12 @@ export const britepoolIdSubmodule = { /** * Performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [submoduleConfigParams] + * @param {SubmoduleConfig} [submoduleConfig] * @param {ConsentData|undefined} consentData * @returns {function(callback:function)} */ - getId(submoduleConfigParams, consentData) { + getId(submoduleConfig, consentData) { + const submoduleConfigParams = (submoduleConfig && submoduleConfig.params) || {}; const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams(submoduleConfigParams, consentData); let getterResponse = null; if (typeof getter === 'function') { diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index 017194d0e86..64aaf61ef23 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -126,11 +126,11 @@ export const criteoIdSubmodule = { /** * get the Criteo Id from local storages and initiate a new user sync * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] * @returns {{id: {criteoId: string} | undefined}}} */ - getId(configParams, consentData) { + getId(config, consentData) { const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; const gdprConsentString = hasGdprData ? consentData.consentString : undefined; diff --git a/modules/haloIdSystem.js b/modules/haloIdSystem.js index 237b502f6a7..d0eb79d4ac2 100644 --- a/modules/haloIdSystem.js +++ b/modules/haloIdSystem.js @@ -30,10 +30,10 @@ export const haloIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId(configParams) { + getId(config) { const url = `https://id.halo.ad.gt/api/v1/pbhid`; const resp = function (callback) { diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 47064e0a1a9..f8ff50f52a3 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -65,12 +65,13 @@ export const id5IdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function getId - * @param {SubmoduleParams} [configParams] - * @param {ConsentData} [consentData] + * @param {SubmoduleConfig} config + * @param {ConsentData} consentData * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ - getId(configParams, consentData, cacheIdObj) { + getId(config, consentData, cacheIdObj) { + const configParams = (config && config.params) || {}; if (!hasRequiredParams(configParams)) { return undefined; } @@ -123,11 +124,12 @@ export const id5IdSubmodule = { * If IdResponse#callback is defined, then it'll called at the end of auction. * It's permissible to return neither, one, or both fields. * @function extendId - * @param {SubmoduleParams} configParams + * @param {SubmoduleConfig} config * @param {Object} cacheIdObj - existing id, if any * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ - extendId(configParams, cacheIdObj) { + extendId(config, cacheIdObj) { + const configParams = (config && config.params) || {}; incrementNb(configParams); return cacheIdObj; } diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index 14c33329b2d..73fc6408581 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -34,10 +34,11 @@ export const identityLinkSubmodule = { * performs action to obtain id and return a value in the callback's response argument * @function * @param {ConsentData} [consentData] - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId(configParams, consentData) { + getId(config, consentData) { + const configParams = (config && config.params) || {}; if (!configParams || typeof configParams.pid !== 'string') { utils.logError('identityLink submodule requires partner id to be defined'); return; diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index bb74b1210cb..c22a6dbc7aa 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -56,10 +56,11 @@ export const intentIqIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId(configParams) { + getId(config) { + const configParams = (config && config.params) || {}; if (!configParams || typeof configParams.partner !== 'number') { utils.logError('User ID - intentIqId submodule requires a valid partner to be defined'); return; diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index bd2638e5936..7981b62dc51 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -100,10 +100,11 @@ export const liveIntentIdSubmodule = { * `publisherId` params. * @function * @param {{unifiedId:string}} value - * @param {SubmoduleParams|undefined} [configParams] + * @param {SubmoduleConfig|undefined} config * @returns {{lipb:Object}} */ - decode(value, configParams) { + decode(value, config) { + const configParams = (config && config.params) || {}; function composeIdObject(value) { const base = { 'lipbid': value['unifiedId'] }; delete value.unifiedId; @@ -121,10 +122,11 @@ export const liveIntentIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId(configParams) { + getId(config) { + const configParams = (config && config.params) || {}; const liveConnect = initializeLiveConnect(configParams); if (!liveConnect) { return; diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index cdf9131dd68..5c3a9a16b3a 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -152,21 +152,22 @@ export const lotamePanoramaIdSubmodule = { * Decode the stored id value for passing to bid requests * @function decode * @param {(Object|string)} value + * @param {SubmoduleConfig|undefined} config * @returns {(Object|undefined)} */ - decode(value, configParams) { + decode(value, config) { return utils.isStr(value) ? { 'lotamePanoramaId': value } : undefined; }, /** * Retrieve the Lotame Panorama Id * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ - getId(configParams, consentData, cacheIdObj) { + getId(config, consentData, cacheIdObj) { let localCache = getLotameLocalCache(); let refreshNeeded = Date.now() > localCache.expiryTimestampMs; diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index d6bf96618df..c55233af6a0 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -31,11 +31,12 @@ export const merkleIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] * @returns {IdResponse|undefined} */ - getId(configParams, consentData) { + getId(config, consentData) { + const configParams = (config && config.params) || {}; if (!configParams || typeof configParams.pubid !== 'string') { utils.logError('User ID - merkleId submodule requires a valid pubid to be defined'); return; diff --git a/modules/netIdSystem.js b/modules/netIdSystem.js index cfe78d9f488..90c8735c993 100644 --- a/modules/netIdSystem.js +++ b/modules/netIdSystem.js @@ -26,12 +26,12 @@ export const netIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ - getId(configParams) { + getId(config) { /* currently not possible */ return {}; } diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 2d6a2d6d6e5..7587962c62b 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -261,11 +261,12 @@ export const parrableIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @param {ConsentData} [consentData] * @returns {function(callback:function), id:ParrableId} */ - getId(configParams, gdprConsentData, currentStoredId) { + getId(config, gdprConsentData, currentStoredId) { + const configParams = (config && config.params) || {}; return fetchId(configParams); } }; diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index 9516934de42..cb60a1b559c 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -54,10 +54,10 @@ export const pubCommonIdSubmodule = { /** * performs action to obtain id * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {IdResponse} */ - getId: function ({create = true, pixelUrl} = {}) { + getId: function ({params: {create = true, pixelUrl} = {}} = {}) { try { if (typeof window[PUB_COMMON_ID] === 'object') { // If the page includes its own pubcid module, then save a copy of id. @@ -75,11 +75,11 @@ export const pubCommonIdSubmodule = { /** * performs action to extend an id * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleParams} [config] * @param {Object} storedId existing id * @returns {IdResponse|undefined} */ - extendId: function({extend = false, pixelUrl} = {}, storedId) { + extendId: function({params: {extend = false, pixelUrl} = {}} = {}, storedId) { try { if (typeof window[PUB_COMMON_ID] === 'object') { // If the page includes its onw pubcid module, then there is nothing to do. diff --git a/modules/pubProvidedSystem.js b/modules/pubProvidedSystem.js index 575633e622f..0b2175f57cb 100644 --- a/modules/pubProvidedSystem.js +++ b/modules/pubProvidedSystem.js @@ -34,10 +34,11 @@ export const pubProvidedIdSubmodule = { /** * performs action to obtain id and return a value. * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {{id: array}} */ - getId(configParams) { + getId(config) { + const configParams = (config && config.params) || {}; let res = []; if (utils.isArray(configParams.eids)) { res = res.concat(configParams.eids); diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 5c2a3df0595..cdd840c4f54 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -296,10 +296,10 @@ export const sharedIdSubmodule = { /** * performs action to obtain id and return a value. * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {sharedId} */ - getId(configParams) { + getId(config) { const resp = function (callback) { utils.logInfo('SharedId: Sharedid doesnt exists, new cookie creation'); ajax(ID_SVC, idGenerationCallback(callback), undefined, {method: 'GET', withCredentials: true}); @@ -309,11 +309,12 @@ export const sharedIdSubmodule = { /** * performs actions even if the id exists and returns a value - * @param configParams + * @param config * @param storedId * @returns {{callback: *}} */ - extendId(configParams, storedId) { + extendId(config, storedId) { + const configParams = (config && config.params) || {}; utils.logInfo('SharedId: Existing shared id ' + storedId.id); const resp = function (callback) { const needSync = isIdSynced(configParams, storedId); diff --git a/modules/unifiedIdSystem.js b/modules/unifiedIdSystem.js index f916030d643..3db4003c424 100644 --- a/modules/unifiedIdSystem.js +++ b/modules/unifiedIdSystem.js @@ -30,10 +30,11 @@ export const unifiedIdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} [configParams] + * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId(configParams) { + getId(config) { + const configParams = (config && config.params) || {}; if (!configParams || (typeof configParams.partner !== 'string' && typeof configParams.url !== 'string')) { utils.logError('User ID - unifiedId submodule requires either partner or url to be defined'); return; diff --git a/modules/userId/index.js b/modules/userId/index.js index 14f7ad3599b..ce442d4a2d3 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -14,7 +14,7 @@ * If IdResponse#callback is defined, then it'll called at the end of auction. * It's permissible to return neither, one, or both fields. * @name Submodule#getId - * @param {SubmoduleParams} configParams + * @param {SubmoduleConfig} config * @param {ConsentData|undefined} consentData * @param {(Object|undefined)} cacheIdObj * @return {(IdResponse|undefined)} A response object that contains id and/or callback. @@ -27,7 +27,7 @@ * If IdResponse#callback is defined, then it'll called at the end of auction. * It's permissible to return neither, one, or both fields. * @name Submodule#extendId - * @param {SubmoduleParams} configParams + * @param {SubmoduleConfig} config * @param {Object} storedId - existing id, if any * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ @@ -37,7 +37,7 @@ * @summary decode a stored value for passing to bid requests * @name Submodule#decode * @param {Object|string} value - * @param {SubmoduleParams|undefined} configParams + * @param {SubmoduleConfig|undefined} config * @return {(Object|undefined)} */ @@ -85,6 +85,7 @@ * @property {(array|undefined)} identifiersToResolve - the identifiers from either ls|cookie to be attached to the getId query * @property {(string|undefined)} providedIdentifierName - defines the name of an identifier that can be found in local storage or in the cookie jar that can be sent along with the getId request. This parameter should be used whenever a customer is able to provide the most stable identifier possible * @property {(LiveIntentCollectConfig|undefined)} liCollectConfig - the config for LiveIntent's collect requests + * @property {(string|undefined)} pd - publisher provided data for reconciling ID5 IDs */ /** @@ -322,7 +323,7 @@ function processSubmoduleCallbacks(submodules, cb) { setStoredValue(submodule, idObj); } // cache decoded value (this is copied to every adUnit bid) - submodule.idObj = submodule.submodule.decode(idObj); + submodule.idObj = submodule.submodule.decode(idObj, submodule.config); } else { utils.logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } @@ -505,10 +506,10 @@ function initSubmodules(submodules, consentData) { if (!storedId || refreshNeeded || !storedConsentDataMatchesConsentData(storedConsentData, consentData)) { // No id previously saved, or a refresh is needed, or consent has changed. Request a new id from the submodule. - response = submodule.submodule.getId(submodule.config.params, consentData, storedId); + response = submodule.submodule.getId(submodule.config, consentData, storedId); } else if (typeof submodule.submodule.extendId === 'function') { // If the id exists already, give submodule a chance to decide additional actions that need to be taken - response = submodule.submodule.extendId(submodule.config.params, storedId); + response = submodule.submodule.extendId(submodule.config, storedId); } if (utils.isPlainObject(response)) { @@ -526,16 +527,16 @@ function initSubmodules(submodules, consentData) { if (storedId) { // cache decoded value (this is copied to every adUnit bid) - submodule.idObj = submodule.submodule.decode(storedId, submodule.config.params); + submodule.idObj = submodule.submodule.decode(storedId, submodule.config); } } else if (submodule.config.value) { // cache decoded value (this is copied to every adUnit bid) submodule.idObj = submodule.config.value; } else { - const response = submodule.submodule.getId(submodule.config.params, consentData, undefined); + const response = submodule.submodule.getId(submodule.config, consentData, undefined); if (utils.isPlainObject(response)) { if (typeof response.callback === 'function') { submodule.callback = response.callback; } - if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config.params); } + if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); } } } carry.push(submodule); diff --git a/modules/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js index ea1173cd61e..d800286b00e 100644 --- a/modules/zeotapIdPlusIdSystem.js +++ b/modules/zeotapIdPlusIdSystem.js @@ -41,7 +41,7 @@ export const zeotapIdPlusSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleParams} configParams + * @param {SubmoduleConfig} config * @return {{id: string | undefined} | undefined} */ getId() { diff --git a/test/spec/modules/britepoolIdSystem_spec.js b/test/spec/modules/britepoolIdSystem_spec.js index f2dd2ef533f..ddb61806006 100644 --- a/test/spec/modules/britepoolIdSystem_spec.js +++ b/test/spec/modules/britepoolIdSystem_spec.js @@ -28,17 +28,17 @@ describe('BritePool Submodule', () => { }); it('trigger id resolution pixel when no identifiers set', () => { - britepoolIdSubmodule.getId({}); + britepoolIdSubmodule.getId({ params: {} }); expect(triggerPixelStub.called).to.be.true; }); it('trigger id resolution pixel when no identifiers set with api_key param', () => { - britepoolIdSubmodule.getId({ api_key }); + britepoolIdSubmodule.getId({ params: { api_key } }); expect(triggerPixelStub.called).to.be.true; }); it('does not trigger id resolution pixel when identifiers set', () => { - britepoolIdSubmodule.getId({ api_key, aaid }); + britepoolIdSubmodule.getId({ params: { api_key, aaid } }); expect(triggerPixelStub.called).to.be.false; }); @@ -110,7 +110,7 @@ describe('BritePool Submodule', () => { expect(getter).to.equal(getter_override); // Making sure it did not become part of params expect(params.getter).to.be.undefined; - const response = britepoolIdSubmodule.getId({ api_key, aaid, url: url_override, getter: getter_override }); + const response = britepoolIdSubmodule.getId({ params: { api_key, aaid, url: url_override, getter: getter_override } }); assert.deepEqual(response, { id: { 'primaryBPID': bpid } }); }); @@ -119,7 +119,7 @@ describe('BritePool Submodule', () => { expect(getter).to.equal(getter_callback_override); // Making sure it did not become part of params expect(params.getter).to.be.undefined; - const response = britepoolIdSubmodule.getId({ api_key, aaid, url: url_override, getter: getter_callback_override }); + const response = britepoolIdSubmodule.getId({ params: { api_key, aaid, url: url_override, getter: getter_callback_override } }); expect(response.callback).to.not.be.undefined; response.callback(result => { assert.deepEqual(result, { 'primaryBPID': bpid }); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index cea6bdf92b9..ac000c1e6dd 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -95,10 +95,12 @@ describe('ID5 ID System', function() { it('should fail if no partner is provided in the config', function() { expect(id5IdSubmodule.getId()).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ params: { } })).to.be.eq(undefined); }); it('should call the ID5 server with 1puid field for legacy storedObj format', function () { - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_LEGACY_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_LEGACY_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; @@ -115,7 +117,7 @@ describe('ID5 ID System', function() { }); it('should call the ID5 server with signature field for new storedObj format', function () { - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; @@ -134,8 +136,8 @@ describe('ID5 ID System', function() { it('should call the ID5 server with pd field when pd config is set', function () { const pubData = 'b50ca08271795a8e7e4012813f23d505193d75c0f2e2bb99baa63aa822f66ed3'; - let config = getId5FetchConfig().params; - config.pd = pubData; + let config = getId5FetchConfig(); + config.params.pd = pubData; let submoduleCallback = id5IdSubmodule.getId(config, undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); @@ -154,8 +156,8 @@ describe('ID5 ID System', function() { }); it('should call the ID5 server with empty pd field when pd config is not set', function () { - let config = getId5FetchConfig().params; - config.pd = undefined; + let config = getId5FetchConfig(); + config.params.pd = undefined; let submoduleCallback = id5IdSubmodule.getId(config, undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); @@ -174,7 +176,7 @@ describe('ID5 ID System', function() { it('should call the ID5 server with nb=1 when no stored value exists', function () { coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; @@ -194,7 +196,7 @@ describe('ID5 ID System', function() { let expStr = (new Date(Date.now() + 25000).toUTCString()); coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig().params, undefined, ID5_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index 9f36ba92558..c729be4c1d6 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -3,7 +3,7 @@ import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; const pid = '14'; -const defaultConfigParams = {pid: pid}; +const defaultConfigParams = { params: {pid: pid} }; const responseHeader = {'Content-Type': 'application/json'} describe('IdentityLinkId tests', function () { @@ -18,12 +18,12 @@ describe('IdentityLinkId tests', function () { }); it('should log an error if no configParams were passed when getId', function () { - identityLinkSubmodule.getId(); + identityLinkSubmodule.getId({ params: {} }); expect(logErrorStub.calledOnce).to.be.true; }); it('should log an error if pid configParam was not passed when getId', function () { - identityLinkSubmodule.getId({}); + identityLinkSubmodule.getId({ params: {} }); expect(logErrorStub.calledOnce).to.be.true; }); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 951cfab5f50..4b45342cb6f 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -6,10 +6,10 @@ import {server} from 'test/mocks/xhr.js'; const partner = 10; const pai = '11'; const pcid = '12'; -const defaultConfigParams = {partner: partner}; -const paiConfigParams = {partner: partner, pai: pai}; -const pcidConfigParams = {partner: partner, pcid: pcid}; -const allConfigParams = {partner: partner, pai: pai, pcid: pcid}; +const defaultConfigParams = { params: {partner: partner} }; +const paiConfigParams = { params: {partner: partner, pai: pai} }; +const pcidConfigParams = { params: {partner: partner, pcid: pcid} }; +const allConfigParams = { params: {partner: partner, pai: pai, pcid: pcid} }; const responseHeader = {'Content-Type': 'application/json'} describe('IntentIQ tests', function () { @@ -24,19 +24,19 @@ describe('IntentIQ tests', function () { }); it('should log an error if no configParams were passed when getId', function () { - let submodule = intentIqIdSubmodule.getId(); + let submodule = intentIqIdSubmodule.getId({ params: {} }); expect(logErrorStub.calledOnce).to.be.true; expect(submodule).to.be.undefined; }); it('should log an error if partner configParam was not passed when getId', function () { - let submodule = intentIqIdSubmodule.getId({}); + let submodule = intentIqIdSubmodule.getId({ params: {} }); expect(logErrorStub.calledOnce).to.be.true; expect(submodule).to.be.undefined; }); it('should log an error if partner configParam was not a numeric value', function () { - let submodule = intentIqIdSubmodule.getId({partner: '10'}); + let submodule = intentIqIdSubmodule.getId({ params: {partner: '10'} }); expect(logErrorStub.calledOnce).to.be.true; expect(submodule).to.be.undefined; }); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index b19d38d5859..80f776168c4 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -4,7 +4,7 @@ import {uspDataHandler} from '../../../src/adapterManager.js'; import {server} from 'test/mocks/xhr.js'; const PUBLISHER_ID = '89899'; -const defaultConfigParams = {publisherId: PUBLISHER_ID}; +const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; const responseHeader = {'Content-Type': 'application/json'} describe('LiveIntentId', function () { @@ -55,8 +55,8 @@ describe('LiveIntentId', function () { }); it('should initialize LiveConnect with the config params when decode and emit an event', function () { - liveIntentIdSubmodule.decode({}, { - ...defaultConfigParams, + liveIntentIdSubmodule.decode({}, { params: { + ...defaultConfigParams.params, ...{ url: 'https://dummy.liveintent.com', liCollectConfig: { @@ -64,7 +64,7 @@ describe('LiveIntentId', function () { collectorUrl: 'https://collector.liveintent.com' } } - }); + } }); expect(pixel.src).to.match(/https:\/\/collector.liveintent.com\/p\?aid=a-0001&wpn=prebid.*/) }); @@ -95,7 +95,7 @@ describe('LiveIntentId', function () { it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function () { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId({...defaultConfigParams, ...{'url': 'https://dummy.liveintent.com/idex'}}).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899'); @@ -110,13 +110,13 @@ describe('LiveIntentId', function () { it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function () { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId({ - ...defaultConfigParams, + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { + ...defaultConfigParams.params, ...{ 'url': 'https://dummy.liveintent.com/idex', 'partner': 'rubicon' } - }).callback; + } }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899'); @@ -179,12 +179,12 @@ describe('LiveIntentId', function () { const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' getDataFromLocalStorageStub.withArgs('_li_duid').returns(oldCookie); getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc'); - const configParams = { - ...defaultConfigParams, + const configParams = { params: { + ...defaultConfigParams.params, ...{ 'identifiersToResolve': ['_thirdPC'] } - }; + }}; let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); @@ -201,12 +201,12 @@ describe('LiveIntentId', function () { it('should include an additional identifier value to resolve even if it is an object', function () { getCookieStub.returns(null); getDataFromLocalStorageStub.withArgs('_thirdPC').returns({'key': 'value'}); - const configParams = { - ...defaultConfigParams, + const configParams = { params: { + ...defaultConfigParams.params, ...{ 'identifiersToResolve': ['_thirdPC'] } - }; + }}; let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 1cc89240bc3..5e62af9b2fa 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -92,7 +92,7 @@ describe('Parrable ID System', function() { }) it('creates xhr to Parrable that synchronizes the ID', function() { - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); getIdResult.callback(callbackSpy); @@ -128,7 +128,7 @@ describe('Parrable ID System', function() { let uspString = '1YNN'; uspDataHandler.setConsentData(uspString); parrableIdSubmodule.getId( - P_CONFIG_MOCK.params, + P_CONFIG_MOCK, null, null ).callback(callbackSpy); @@ -138,7 +138,7 @@ describe('Parrable ID System', function() { it('should log an error and continue to callback if ajax request errors', function () { let callBackSpy = sinon.spy(); - let submoduleCallback = parrableIdSubmodule.getId({partner: 'prebid'}).callback; + let submoduleCallback = parrableIdSubmodule.getId({ params: {partner: 'prebid'} }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; expect(request.url).to.contain('h.parrable.com'); @@ -155,7 +155,7 @@ describe('Parrable ID System', function() { describe('response id', function() { it('provides the stored Parrable values if a cookie exists', function() { writeParrableCookie({ eid: P_COOKIE_EID }); - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); removeParrableCookie(); expect(getIdResult.id).to.deep.equal({ @@ -171,7 +171,7 @@ describe('Parrable ID System', function() { storage.setCookie(oldEidCookieName, oldEid); storage.setCookie(oldOptoutCookieName, 'true'); - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK.params); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); expect(getIdResult.id).to.deep.equal({ eid: oldEid, ibaOptout: true @@ -212,9 +212,9 @@ describe('Parrable ID System', function() { }); it('permits an impression when no timezoneFilter is configured', function() { - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', - })).to.have.property('callback'); + } })).to.have.property('callback'); }); it('permits an impression from a blocked timezone when a cookie exists', function() { @@ -224,12 +224,12 @@ describe('Parrable ID System', function() { writeParrableCookie({ eid: P_COOKIE_EID }); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { blockedZones: [ blockedZone ] } - })).to.have.property('callback'); + } })).to.have.property('callback'); expect(resolvedOptions.called).to.equal(false); removeParrableCookie(); @@ -240,12 +240,12 @@ describe('Parrable ID System', function() { const resolvedOptions = sinon.stub().returns({ timeZone: allowedZone }); Intl.DateTimeFormat.returns({ resolvedOptions }); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { allowedZones: [ allowedZone ] } - })).to.have.property('callback'); + } })).to.have.property('callback'); expect(resolvedOptions.called).to.equal(true); }); @@ -254,12 +254,12 @@ describe('Parrable ID System', function() { const resolvedOptions = sinon.stub().returns({ timeZone: 'Iceland' }); Intl.DateTimeFormat.returns({ resolvedOptions }); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { blockedZones: [ blockedZone ] } - })).to.have.property('callback'); + } })).to.have.property('callback'); expect(resolvedOptions.called).to.equal(true); }); @@ -268,12 +268,12 @@ describe('Parrable ID System', function() { const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); Intl.DateTimeFormat.returns({ resolvedOptions }); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { blockedZones: [ blockedZone ] } - })).to.equal(null); + } })).to.equal(null); expect(resolvedOptions.called).to.equal(true); }); @@ -282,13 +282,13 @@ describe('Parrable ID System', function() { const resolvedOptions = sinon.stub().returns({ timeZone: timezone }); Intl.DateTimeFormat.returns({ resolvedOptions }); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { allowedZones: [ timezone ], blockedZones: [ timezone ] } - })).to.equal(null); + } })).to.equal(null); expect(resolvedOptions.called).to.equal(true); }); }); @@ -312,12 +312,12 @@ describe('Parrable ID System', function() { writeParrableCookie({ eid: P_COOKIE_EID }); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { blockedOffsets: [ blockedOffset ] } - })).to.have.property('callback'); + } })).to.have.property('callback'); removeParrableCookie(); }); @@ -326,12 +326,12 @@ describe('Parrable ID System', function() { const allowedOffset = -5; Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { allowedOffsets: [ allowedOffset ] } - })).to.have.property('callback'); + } })).to.have.property('callback'); expect(Date.prototype.getTimezoneOffset.called).to.equal(true); }); @@ -340,12 +340,12 @@ describe('Parrable ID System', function() { const blockedOffset = 5; Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { blockedOffsets: [ blockedOffset ] } - })).to.have.property('callback'); + }})).to.have.property('callback'); expect(Date.prototype.getTimezoneOffset.called).to.equal(true); }); @@ -353,12 +353,12 @@ describe('Parrable ID System', function() { const blockedOffset = -5; Date.prototype.getTimezoneOffset.returns(blockedOffset * 60); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { blockedOffsets: [ blockedOffset ] } - })).to.equal(null); + } })).to.equal(null); expect(Date.prototype.getTimezoneOffset.called).to.equal(true); }); @@ -366,13 +366,13 @@ describe('Parrable ID System', function() { const offset = -5; Date.prototype.getTimezoneOffset.returns(offset * 60); - expect(parrableIdSubmodule.getId({ + expect(parrableIdSubmodule.getId({ params: { partner: 'prebid-test', timezoneFilter: { allowedOffset: [ offset ], blockedOffsets: [ offset ] } - })).to.equal(null); + } })).to.equal(null); expect(Date.prototype.getTimezoneOffset.called).to.equal(true); }); }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index c4e52d9a121..5dac5db7b54 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1567,7 +1567,7 @@ describe('User ID', function () { 'mid': value['MOCKID'] }; }, - getId: function (params, storedId) { + getId: function (config, storedId) { if (storedId) return {}; return {id: {'MOCKID': '1234'}}; } From ea498f97d0a0f874d1df5e32c50f868600ada2d8 Mon Sep 17 00:00:00 2001 From: Baptiste HAUDEGAND Date: Tue, 6 Oct 2020 17:50:31 +0200 Subject: [PATCH 044/190] Fix timeToFirstByte unit test (#5820) * Debug timeToFirstByte unit test * review --- modules/teadsBidAdapter.js | 8 ++++---- test/spec/modules/teadsBidAdapter_spec.js | 16 +++++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 08ae1854669..6d55aabbfb5 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -133,8 +133,8 @@ function getTimeToFirstByte(win) { performance.getEntriesByType('navigation')[0] && performance.getEntriesByType('navigation')[0].responseStart && performance.getEntriesByType('navigation')[0].requestStart && - performance.getEntriesByType('navigation')[0].responseStart >= 0 && - performance.getEntriesByType('navigation')[0].requestStart >= 0 && + performance.getEntriesByType('navigation')[0].responseStart > 0 && + performance.getEntriesByType('navigation')[0].requestStart > 0 && Math.round( performance.getEntriesByType('navigation')[0].responseStart - performance.getEntriesByType('navigation')[0].requestStart ); @@ -146,8 +146,8 @@ function getTimeToFirstByte(win) { const ttfbWithTimingV1 = performance && performance.timing.responseStart && performance.timing.requestStart && - performance.timing.responseStart >= 0 && - performance.timing.requestStart >= 0 && + performance.timing.responseStart > 0 && + performance.timing.requestStart > 0 && performance.timing.responseStart - performance.timing.requestStart; return ttfbWithTimingV1 ? ttfbWithTimingV1.toString() : ''; diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 17841b271d4..a6ae17ec8ff 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -200,8 +200,8 @@ describe('teadsBidAdapter', () => { performance.getEntriesByType('navigation')[0] && performance.getEntriesByType('navigation')[0].responseStart && performance.getEntriesByType('navigation')[0].requestStart && - performance.getEntriesByType('navigation')[0].responseStart >= 0 && - performance.getEntriesByType('navigation')[0].requestStart >= 0 && + performance.getEntriesByType('navigation')[0].responseStart > 0 && + performance.getEntriesByType('navigation')[0].requestStart > 0 && Math.round( performance.getEntriesByType('navigation')[0].responseStart - performance.getEntriesByType('navigation')[0].requestStart ); @@ -211,9 +211,15 @@ describe('teadsBidAdapter', () => { if (ttfbExpectedV2) { expect(payload.timeToFirstByte).to.deep.equal(ttfbExpectedV2.toString()); } else { - const ttfbExpectedV1 = performance.timing.responseStart - performance.timing.requestStart; - - expect(payload.timeToFirstByte).to.deep.equal(ttfbExpectedV1.toString()); + const ttfbWithTimingV1 = performance && + performance.timing.responseStart && + performance.timing.requestStart && + performance.timing.responseStart > 0 && + performance.timing.requestStart > 0 && + performance.timing.responseStart - performance.timing.requestStart; + const ttfbExpectedV1 = ttfbWithTimingV1 ? ttfbWithTimingV1.toString() : ''; + + expect(payload.timeToFirstByte).to.deep.equal(ttfbExpectedV1); } }); From 8982e0904fde99454f4c10985ea46d019b683e5c Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 6 Oct 2020 15:08:39 -0400 Subject: [PATCH 045/190] rubicon: adding pubcid support (#5824) * rubicon: adding pubcid support * adding to orderedParams * removed eids filter so all eids will be supported * fix eids test * fixed eids assertions Co-authored-by: Isaac A. Dettman --- modules/rubiconBidAdapter.js | 11 +++++++-- test/spec/modules/rubiconBidAdapter_spec.js | 26 ++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index a446cbc3ce0..d4411a82e8f 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -236,8 +236,7 @@ export const spec = { const eids = utils.deepAccess(bidderRequest, 'bids.0.userIdAsEids'); if (eids && eids.length) { - // filter out unsupported id systems - utils.deepSetValue(data, 'user.ext.eids', eids.filter(eid => ['adserver.org', 'pubcid.org', 'liveintent.com', 'liveramp.com', 'sharedid.org', 'criteo.com'].indexOf(eid.source) !== -1)); + utils.deepSetValue(data, 'user.ext.eids', eids); // liveintent requires additional props to be set const liveIntentEid = find(data.user.ext.eids, eid => eid.source === 'liveintent.com'); @@ -389,6 +388,10 @@ export const spec = { 'tpid_tdid', 'tpid_liveintent.com', 'tg_v.LIseg', + 'ppuid', + 'eid_pubcid.org', + 'eid_sharedid.org', + 'eid_criteo.com', 'rf', 'p_geo.latitude', 'p_geo.longitude', @@ -519,6 +522,10 @@ export const spec = { if (sharedId) { data['eid_sharedid.org'] = `${sharedId.uids[0].id}^${sharedId.uids[0].atype}^${sharedId.uids[0].ext.third}`; } + const pubcid = find(bidRequest.userIdAsEids, eid => eid.source === 'pubcid.org'); + if (pubcid) { + data['eid_pubcid.org'] = `${pubcid.uids[0].id}^${pubcid.uids[0].atype}`; + } const criteoId = find(bidRequest.userIdAsEids, eid => eid.source === 'criteo.com'); if (criteoId) { data['eid_criteo.com'] = `${criteoId.uids[0].id}^${criteoId.uids[0].atype}`; diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index b2adc1f39ff..a38743d634a 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1145,6 +1145,20 @@ describe('the rubicon adapter', function () { }); }); + describe('pubcid support', function () { + it('should send eid_pubcid.org when userIdAsEids contains pubcid', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + pubcid: '1111' + }; + clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['eid_pubcid.org']).to.equal('1111^1'); + }); + }); + describe('Criteo support', function () { it('should send eid_criteo.com when userIdAsEids contains criteo', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); @@ -1442,10 +1456,16 @@ describe('the rubicon adapter', function () { expect(post.user.ext.eids[4].source).to.equal('pubcid.org'); expect(post.user.ext.eids[4].uids[0].atype).to.equal(1); expect(post.user.ext.eids[4].uids[0].id).to.equal('4000'); + // example should exist + expect(post.user.ext.eids[5].source).to.equal('example.com'); + expect(post.user.ext.eids[5].uids[0].id).to.equal('333333'); + // id-partner.com + expect(post.user.ext.eids[6].source).to.equal('id-partner.com'); + expect(post.user.ext.eids[6].uids[0].id).to.equal('4444444'); // CriteoId should exist - expect(post.user.ext.eids[5].source).to.equal('criteo.com'); - expect(post.user.ext.eids[5].uids[0].id).to.equal('1111'); - expect(post.user.ext.eids[5].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[7].source).to.equal('criteo.com'); + expect(post.user.ext.eids[7].uids[0].id).to.equal('1111'); + expect(post.user.ext.eids[7].uids[0].atype).to.equal(1); expect(post.regs.ext.gdpr).to.equal(1); expect(post.regs.ext.us_privacy).to.equal('1NYN'); From 7578cbba698167e4fd7c4515461090fd781fc3f6 Mon Sep 17 00:00:00 2001 From: pm-shashank-jain Date: Wed, 7 Oct 2020 13:26:24 +0530 Subject: [PATCH 046/190] Changes for UOe-5712/5705 --- modules/pubmaticBidAdapter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index fcea197107a..575be3bd836 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -6,7 +6,7 @@ import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; -const ENDPOINT = 'https://hbopenbid.pubmatic.com/translator?source=prebid-client'; +const ENDPOINT = 'https://hbopenbid.pubmatic.com/translator?source=ow-client'; const USER_SYNC_URL_IFRAME = 'https://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p='; const USER_SYNC_URL_IMAGE = 'https://image8.pubmatic.com/AdServer/ImgSync?p='; const DEFAULT_CURRENCY = 'USD'; @@ -883,8 +883,8 @@ export const spec = { utils.logError(`${LOG_WARN_PREFIX}: no context specified in bid. Rejecting bid: `, bid); return false; } - if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU)) { - utils.logError(`${LOG_WARN_PREFIX}: for "outstream" bids outstreamAU is required. Rejecting bid: `, bid); + if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU && !bid.hasOwnProperty('renderer'))) { + utils.logError(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting bid: `, bid); return false; } } else { From 16fabf4cb78daaa16ac16c3ea6c0def06fa8df9b Mon Sep 17 00:00:00 2001 From: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Date: Wed, 7 Oct 2020 18:14:56 +0530 Subject: [PATCH 047/190] Appnexus: Add omid support (#5821) * basic implementation complete * add unit tests * remove redundant field tags[].video.frameworks --- modules/appnexusBidAdapter.js | 32 ++++++++++++++ test/spec/modules/appnexusBidAdapter_spec.js | 46 ++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index f3ebf9bf4f6..ff0e3230007 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -157,6 +157,7 @@ export const spec = { const memberIdBid = find(bidRequests, hasMemberId); const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; const schain = bidRequests[0].schain; + const omidSupport = find(bidRequests, hasOmidSupport); const payload = { tags: [...tags], @@ -168,6 +169,13 @@ export const spec = { schain: schain }; + if (omidSupport) { + payload['iab_support'] = { + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + } + } + if (member > 0) { payload.member_id = member; } @@ -766,16 +774,27 @@ function bidToTag(bid) { type = (utils.isArray(type)) ? type[0] : type; tag.video[param] = VIDEO_MAPPING[param][type]; break; + // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks + case 'frameworks': + break; default: tag.video[param] = bid.params.video[param]; } }); + + if (bid.params.video.frameworks && utils.isArray(bid.params.video.frameworks)) { + tag['video_frameworks'] = bid.params.video.frameworks; + } } if (bid.renderer) { tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); } + if (bid.params.frameworks && utils.isArray(bid.params.frameworks)) { + tag['banner_frameworks'] = bid.params.frameworks; + } + let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { tag.ad_types.push(BANNER); @@ -844,6 +863,19 @@ function hasAdPod(bid) { ); } +function hasOmidSupport(bid) { + let hasOmid = false; + const bidderParams = bid.params; + const videoParams = bid.params.video; + if (bidderParams.frameworks && utils.isArray(bidderParams.frameworks)) { + hasOmid = includes(bid.params.frameworks, 6); + } + if (!hasOmid && videoParams && videoParams.frameworks && utils.isArray(videoParams.frameworks)) { + hasOmid = includes(bid.params.video.frameworks, 6); + } + return hasOmid; +} + /** * Expand an adpod placement into a set of request objects according to the * total adpod duration and the range of duration seconds. Sets minduration/ diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 7b563c4ec1d..4102896ba94 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -829,6 +829,52 @@ describe('AppNexusAdapter', function () { user_id: 'sample-criteo-userid', }); }); + + it('should populate iab_support object at the root level if omid support is detected', function () { + // with bid.params.frameworks + let bidRequest_A = Object.assign({}, bidRequests[0], { + params: { + frameworks: [1, 2, 5, 6], + video: { + frameworks: [1, 2, 5, 6] + } + } + }); + let request = spec.buildRequests([bidRequest_A]); + let payload = JSON.parse(request.data); + expect(payload.iab_support).to.be.an('object'); + expect(payload.iab_support).to.deep.equal({ + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + }); + expect(payload.tags[0].banner_frameworks).to.be.an('array'); + expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video_frameworks).to.be.an('array'); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video.frameworks).to.not.exist; + + // without bid.params.frameworks + const bidRequest_B = Object.assign({}, bidRequests[0]); + request = spec.buildRequests([bidRequest_B]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + + // with video.frameworks but it is not an array + const bidRequest_C = Object.assign({}, bidRequests[0], { + params: { + video: { + frameworks: "'1', '2', '3', '6'" + } + } + }); + request = spec.buildRequests([bidRequest_C]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + }); }) describe('interpretResponse', function () { From f907ee6f4312c889bc41b22bd7b44026c7b40302 Mon Sep 17 00:00:00 2001 From: Ben Anderson Date: Wed, 7 Oct 2020 10:19:37 -0400 Subject: [PATCH 048/190] new userId module - neustar's fabrick (#5802) * submitting userId module for neustar's fabrick - https://www.home.neustar/fabrick * fixing 'gulp test' errors * fixing another test issue (related to ie) * removing another (last) repeat * - expose full user id config (including storage) to user id modules (#5803 - removing TODO from test * - updates to test Co-authored-by: Anderson, Ben --- modules/.submodules.json | 3 +- modules/fabrickIdSystem.js | 147 ++++++++++++++++++++++ modules/fabrickIdSystem.md | 24 ++++ test/mocks/fabrickId.json | 3 + test/spec/modules/fabrickIdSystem_spec.js | 106 ++++++++++++++++ 5 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 modules/fabrickIdSystem.js create mode 100644 modules/fabrickIdSystem.md create mode 100644 test/mocks/fabrickId.json create mode 100644 test/spec/modules/fabrickIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 18e75dd1794..0e5fdba4807 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -15,7 +15,8 @@ "intentIqIdSystem", "zeotapIdPlusIdSystem", "haloIdSystem", - "quantcastIdSystem" + "quantcastIdSystem", + "fabrickIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js new file mode 100644 index 00000000000..e61b377eefa --- /dev/null +++ b/modules/fabrickIdSystem.js @@ -0,0 +1,147 @@ +/** + * This module adds neustar's fabrickId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/fabrickIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js' +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; + +/** @type {Submodule} */ +export const fabrickIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'fabrickId', + + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ + decode(value) { + if (value && value.fabrickId) { + return { 'fabrickId': value.fabrickId }; + } else { + return undefined; + } + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function getId + * @param {SubmoduleConfig} [config] + * @param {ConsentData} + * @param {Object} cacheIdObj - existing id, if any consentData] + * @returns {IdResponse|undefined} + */ + getId(config, consentData, cacheIdObj) { + try { + const configParams = (config && config.params) || {}; + if (window.fabrickMod1) { + window.fabrickMod1(configParams, consentData, cacheIdObj); + } + if (!configParams || typeof configParams.apiKey !== 'string') { + utils.logError('fabrick submodule requires an apiKey.'); + return; + } + try { + let url = _getBaseUrl(configParams); + let keysArr = Object.keys(configParams); + for (let i in keysArr) { + let k = keysArr[i]; + if (k === 'url' || k === 'refererInfo') { + continue; + } + let v = configParams[k]; + if (Array.isArray(v)) { + for (let j in v) { + url += `${k}=${v[j]}&`; + } + } else { + url += `${k}=${v}&`; + } + } + // pull off the trailing & + url = url.slice(0, -1) + const referer = _getRefererInfo(configParams); + const urls = new Set(); + url = truncateAndAppend(urls, url, 'r', referer.referer); + if (referer.stack && referer.stack[0]) { + url = truncateAndAppend(urls, url, 'r', referer.stack[0]); + } + url = truncateAndAppend(urls, url, 'r', referer.canonicalUrl); + url = truncateAndAppend(urls, url, 'r', window.location.href); + + const resp = function (callback) { + const callbacks = { + success: response => { + if (window.fabrickMod2) { + return window.fabrickMod2( + callback, response, configParams, consentData, cacheIdObj); + } else { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + responseObj = {}; + } + } + callback(responseObj); + } + }, + error: error => { + utils.logError(`fabrickId fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, null, {method: 'GET', withCredentials: true}); + }; + return {callback: resp}; + } catch (e) { + utils.logError(`fabrickIdSystem encountered an error`, e); + } + } catch (e) { + utils.logError(`fabrickIdSystem encountered an error`, e); + } + } +}; + +function _getRefererInfo(configParams) { + if (configParams.refererInfo) { + return configParams.refererInfo; + } else { + return getRefererInfo(); + } +} + +function _getBaseUrl(configParams) { + if (configParams.url) { + return configParams.url; + } else { + return `https://fid.agkn.com/f?`; + } +} + +function truncateAndAppend(urls, url, paramName, s) { + if (s && url.length < 2000) { + if (s.length > 200) { + s = s.substring(0, 200); + } + // Don't send the same url in multiple params + if (!urls.has(s)) { + urls.add(s); + return `${url}&${paramName}=${s}` + } + } + return url; +} + +submodule('userId', fabrickIdSubmodule); diff --git a/modules/fabrickIdSystem.md b/modules/fabrickIdSystem.md new file mode 100644 index 00000000000..268c861710a --- /dev/null +++ b/modules/fabrickIdSystem.md @@ -0,0 +1,24 @@ +## Neustar Fabrick User ID Submodule + +Fabrick ID Module - https://www.home.neustar/fabrick +Product and Sales Inquiries: 1-855-898-0036 + +## Example configuration for publishers: +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'fabrickId', + storage: { + name: 'pbjs_fabrickId', + type: 'cookie', + expires: 7 + }, + params: { + apiKey: 'your apiKey', // provided to you by Neustar + e: '31c5543c1734d25c7206f5fd591525d0295bec6fe84ff82f946a34fe970a1e66' // example hash identifier (sha256) + } + }] + } +}); +``` diff --git a/test/mocks/fabrickId.json b/test/mocks/fabrickId.json new file mode 100644 index 00000000000..a8723ec88ec --- /dev/null +++ b/test/mocks/fabrickId.json @@ -0,0 +1,3 @@ +{ + "fabrickId": 1980 +} diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js new file mode 100644 index 00000000000..cbd538816ab --- /dev/null +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -0,0 +1,106 @@ +import * as utils from '../../../src/utils.js'; +import {server} from '../../mocks/xhr.js'; + +import * as fabrickIdSystem from 'modules/fabrickIdSystem.js'; + +const defaultConfigParams = { + apiKey: '123', + e: 'abc', + p: ['def', 'hij'], + url: 'http://localhost:9999/test/mocks/fabrickId.json?' +}; +const responseHeader = {'Content-Type': 'application/json'} +const fabrickIdSubmodule = fabrickIdSystem.fabrickIdSubmodule; + +describe('Fabrick ID System', function() { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + fabrickIdSubmodule.getRefererInfoOverride = null; + }); + + it('should log an error if no configParams were passed into getId', function () { + fabrickIdSubmodule.getId(); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should error on json parsing', function() { + let submoduleCallback = fabrickIdSubmodule.getId({ + name: 'fabrickId', + params: defaultConfigParams + }).callback; + let callBackSpy = sinon.spy(); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + request.respond( + 200, + responseHeader, + '] this is not json {' + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should truncate the params', function() { + let r = ''; + for (let i = 0; i < 300; i++) { + r += 'r'; + } + let configParams = Object.assign({}, defaultConfigParams, { + refererInfo: { + referer: r, + stack: ['s-0'], + canonicalUrl: 'cu-0' + } + }); + let submoduleCallback = fabrickIdSubmodule.getId({ + name: 'fabrickId', + params: configParams + }).callback; + let callBackSpy = sinon.spy(); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + r = ''; + for (let i = 0; i < 200; i++) { + r += 'r'; + } + expect(request.url).to.match(new RegExp(`r=${r}&r=`)); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(logErrorStub.calledOnce).to.be.false; + }); + + it('should complete successfully', function() { + let configParams = Object.assign({}, defaultConfigParams, { + refererInfo: { + referer: 'r-0', + stack: ['s-0'], + canonicalUrl: 'cu-0' + } + }); + let submoduleCallback = fabrickIdSubmodule.getId({ + name: 'fabrickId', + params: configParams + }).callback; + let callBackSpy = sinon.spy(); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.match(/r=r-0&r=s-0&r=cu-0&r=http/); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(logErrorStub.calledOnce).to.be.false; + }); +}); From c57bab724b683bbe0efc9dd8de87ad8db332c946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Baudisch?= Date: Wed, 7 Oct 2020 18:01:45 +0200 Subject: [PATCH 049/190] Integrate option to pass clickThrough urls to renderAd method (#5796) * adding options to renderAd method * adding replaceClickThrough method to utils * implemented replaceClickThrough method in render ad to enable ssps adding url param clickthrough for publisher side counting * update to cover some validation and unit tests as requested by harpere * adding unit test for clickthrough implementation; --- src/prebid.js | 10 +++++++++- src/utils.js | 5 +++++ test/spec/unit/pbjs_api_spec.js | 8 ++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/prebid.js b/src/prebid.js index 31e0140cfe2..8d1d7ca1253 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -342,7 +342,7 @@ function emitAdRenderFail({ reason, message, bid, id }) { * @param {string} id bid id to locate the ad * @alias module:pbjs.renderAd */ -$$PREBID_GLOBAL$$.renderAd = function (doc, id) { +$$PREBID_GLOBAL$$.renderAd = function (doc, id, options) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); utils.logMessage('Calling renderAd with adId :' + id); @@ -354,6 +354,14 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id) { // replace macros according to openRTB with price paid = bid.cpm bid.ad = utils.replaceAuctionPrice(bid.ad, bid.cpm); bid.adUrl = utils.replaceAuctionPrice(bid.adUrl, bid.cpm); + + // replacing clickthrough if submitted + if (options && options.clickThrough) { + const { clickThrough } = options; + bid.ad = utils.replaceClickThrough(bid.ad, clickThrough); + bid.adUrl = utils.replaceClickThrough(bid.adUrl, clickThrough); + } + // save winning bids auctionManager.addWinningBid(bid); diff --git a/src/utils.js b/src/utils.js index 9426308daf4..8af7a25668d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -718,6 +718,11 @@ export function replaceAuctionPrice(str, cpm) { return str.replace(/\$\{AUCTION_PRICE\}/g, cpm); } +export function replaceClickThrough(str, clicktag) { + if (!str || !clicktag || typeof clicktag !== 'string') return; + return str.replace(/\${CLICKTHROUGH}/g, clicktag); +} + export function timestamp() { return new Date().getTime(); } diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 960ccf08c92..efb1338ac5d 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1197,6 +1197,14 @@ describe('Unit: Prebid Module', function () { assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids()[0], adResponse); }); + it('should replace ${CLICKTHROUGH} macro in winning bids response', function () { + pushBidResponseToAuction({ + ad: "" + }); + $$PREBID_GLOBAL$$.renderAd(doc, bidId, {clickThrough: 'https://someadserverclickurl.com'}); + expect(adResponse).to.have.property('ad').and.to.match(/https:\/\/someadserverclickurl\.com/i); + }); + it('fires billing url if present on s2s bid', function () { const burl = 'http://www.example.com/burl'; pushBidResponseToAuction({ From ba1a35c56405d1d1ee4a901ba173cba32578652c Mon Sep 17 00:00:00 2001 From: Hugo Duthil Date: Wed, 7 Oct 2020 18:16:56 +0200 Subject: [PATCH 050/190] Add credentials and explicit options to CriteoIdSystem (#5822) Co-authored-by: Hugo Duthil --- modules/criteoIdSystem.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index 64aaf61ef23..30641b6c225 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -103,7 +103,9 @@ function callCriteoUserSync(parsedCriteoData, gdprString) { } else if (jsonResponse.bundle) { saveOnAllStorages(bundleStorageKey, jsonResponse.bundle); } - } + }, + undefined, + { method: 'GET', contentType: 'application/json', withCredentials: true } ); } From e082c83adce4fd198fa0c8d4be6d1fe6caf7cb29 Mon Sep 17 00:00:00 2001 From: guiann Date: Wed, 7 Oct 2020 18:21:46 +0200 Subject: [PATCH 051/190] AdYouLike bidAdapter - Add information in bid request (#5828) * Remove useless bidderCode in bid response * send all the available sizes in the bid request * Use the banner sizes if given * avoid compatibility issue with old bid format * ad iframe and publisher domain paramters to bid requests * add publisher domain info in ad request * add a check in unit tests for publisherDomain * encode uri components Co-authored-by: Guillaume --- modules/adyoulikeBidAdapter.js | 26 ++++++++++--------- test/spec/modules/adyoulikeBidAdapter_spec.js | 1 + 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 725ee0f5626..40d3cf84369 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,5 +1,6 @@ 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'; const VERSION = '1.0'; @@ -104,15 +105,6 @@ function getHostname(bidderRequest) { return ''; } -/* Get current page referrer url */ -function getReferrerUrl(bidderRequest) { - let referer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - referer = bidderRequest.refererInfo.referer; - } - return referer; -} - /* Get current page canonical url */ function getCanonicalUrl() { let link; @@ -155,9 +147,14 @@ function createEndpoint(bidRequests, bidderRequest) { function createEndpointQS(bidderRequest) { const qs = {}; - const ref = getReferrerUrl(bidderRequest); - if (ref) { - qs.RefererUrl = encodeURIComponent(ref); + if (bidderRequest) { + const ref = bidderRequest.refererInfo; + if (ref) { + qs.RefererUrl = encodeURIComponent(ref.referer); + if (ref.numIframes > 0) { + qs.SafeFrame = true; + } + } } const can = getCanonicalUrl(); @@ -165,6 +162,11 @@ function createEndpointQS(bidderRequest) { qs.CanonicalUrl = encodeURIComponent(can); } + const domain = config.getConfig('publisherDomain'); + if (domain) { + qs.PublisherDomain = encodeURIComponent(domain); + } + return qs; } diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index d2d4e10c17f..ec8f2f00923 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -306,6 +306,7 @@ describe('Adyoulike Adapter', function () { expect(request.method).to.equal('POST'); expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); expect(request.url).to.contains('RefererUrl=' + encodeURIComponent(referrerUrl)); + expect(request.url).to.contains('PublisherDomain=http%3A%2F%2Flocalhost%3A9876'); expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); From 84e7121f2b025d049eb5da39f31755feb94a5591 Mon Sep 17 00:00:00 2001 From: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Date: Wed, 7 Oct 2020 14:48:32 -0400 Subject: [PATCH 052/190] 4.11.0 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35b0f6925e0..b3652fc288c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.11.0-pre", + "version": "4.11.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 752bd2713d746543b8e66d572459e8b6c1b43089 Mon Sep 17 00:00:00 2001 From: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Date: Wed, 7 Oct 2020 15:52:43 -0400 Subject: [PATCH 053/190] 4.12.0-pre --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b3652fc288c..686d47f53e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.11.0", + "version": "4.12.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 7d2709523afafad9e3ca91c2bd9477006df9f971 Mon Sep 17 00:00:00 2001 From: Renzo Toscani <7190938+rtoscani@users.noreply.github.com> Date: Thu, 8 Oct 2020 13:16:15 -0300 Subject: [PATCH 054/190] IDx user id submodule (#5826) * add idx user id * Update modules/idxIdSystem.js to match new SubmoduleConfig param Co-authored-by: Scott Co-authored-by: Scott --- modules/.submodules.json | 1 + modules/idxIdSystem.js | 61 ++++++++++++++ modules/idxIdSystem.md | 22 +++++ modules/userId/eids.js | 8 +- test/spec/modules/idxIdSystem_spec.js | 117 ++++++++++++++++++++++++++ 5 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 modules/idxIdSystem.js create mode 100644 modules/idxIdSystem.md create mode 100644 test/spec/modules/idxIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 0e5fdba4807..91cda9d95ad 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -16,6 +16,7 @@ "zeotapIdPlusIdSystem", "haloIdSystem", "quantcastIdSystem", + "idxIdSystem", "fabrickIdSystem" ], "adpod": [ diff --git a/modules/idxIdSystem.js b/modules/idxIdSystem.js new file mode 100644 index 00000000000..00e8a8bc5e5 --- /dev/null +++ b/modules/idxIdSystem.js @@ -0,0 +1,61 @@ +/** + * This module adds IDx to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/idxIdSystem + * @requires module:modules/userId + */ +import * as utils from '../src/utils.js' +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const IDX_MODULE_NAME = 'idx'; +const IDX_COOKIE_NAME = '_idx'; +export const storage = getStorageManager(); + +function readIDxFromCookie() { + return storage.cookiesAreEnabled ? storage.getCookie(IDX_COOKIE_NAME) : null; +} + +function readIDxFromLocalStorage() { + return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(IDX_COOKIE_NAME) : null; +} + +/** @type {Submodule} */ +export const idxIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: IDX_MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param { Object | string | undefined } value + * @return { Object | string | undefined } + */ + decode(value) { + const idxVal = value ? utils.isStr(value) ? value : utils.isPlainObject(value) ? value.id : undefined : undefined; + return idxVal ? { + 'idx': idxVal + } : undefined; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @return {{id: string | undefined } | undefined} + */ + getId() { + const idxString = readIDxFromLocalStorage() || readIDxFromCookie(); + if (typeof idxString == 'string' && idxString) { + try { + const idxObj = JSON.parse(idxString); + return idxObj && idxObj.idx ? { id: idxObj.idx } : undefined; + } catch (error) { + utils.logError(error); + } + } + return undefined; + } +}; +submodule('userId', idxIdSubmodule); diff --git a/modules/idxIdSystem.md b/modules/idxIdSystem.md new file mode 100644 index 00000000000..363120899cb --- /dev/null +++ b/modules/idxIdSystem.md @@ -0,0 +1,22 @@ +## IDx User ID Submodule + +For assistance setting up your module please contact us at [prebid@idx.lat](prebid@idx.lat). + +### Prebid Params + +Individual params may be set for the IDx Submodule. +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'idx', + }] + } +}); +``` +## Parameter Descriptions for the `userSync` Configuration Section +The below parameters apply only to the IDx integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID of the module - `"idx"` | `"idx"` | diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 4e4576dbb14..f6c58a5a0bf 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -150,7 +150,13 @@ const USER_IDS_CONFIG = { 'quantcastId': { source: 'quantcast.com', atype: 1 - } + }, + + // IDx + 'idx': { + source: 'idx.lat', + atype: 1 + }, }; // this function will create an eid object for the given UserId sub-module diff --git a/test/spec/modules/idxIdSystem_spec.js b/test/spec/modules/idxIdSystem_spec.js new file mode 100644 index 00000000000..14cd9a88d13 --- /dev/null +++ b/test/spec/modules/idxIdSystem_spec.js @@ -0,0 +1,117 @@ +import { expect } from 'chai'; +import find from 'core-js-pure/features/array/find.js'; +import { config } from 'src/config.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { storage, idxIdSubmodule } from 'modules/idxIdSystem.js'; + +const IDX_COOKIE_NAME = '_idx'; +const IDX_DUMMY_VALUE = 'idx value for testing'; +const IDX_COOKIE_STORED = '{ "idx": "' + IDX_DUMMY_VALUE + '" }'; +const ID_COOKIE_OBJECT = { id: IDX_DUMMY_VALUE }; +const IDX_COOKIE_OBJECT = { idx: IDX_DUMMY_VALUE }; + +function getConfigMock() { + return { + userSync: { + syncDelay: 0, + userIds: [{ + name: 'idx' + }] + } + } +} + +function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [ + [300, 200], + [300, 600] + ], + bids: [{ + bidder: 'sampleBidder', + params: { placementId: 'banner-only-bidder' } + }] + }; +} + +describe('IDx ID System', () => { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let getCookieStub, cookiesAreEnabledStub; + + beforeEach(() => { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + }); + + afterEach(() => { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + getCookieStub.restore(); + cookiesAreEnabledStub.restore(); + }); + + describe('IDx: test "getId" method', () => { + it('provides the stored IDx if a cookie exists', () => { + getCookieStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + let idx = idxIdSubmodule.getId(); + expect(idx).to.deep.equal(ID_COOKIE_OBJECT); + }); + + it('provides the stored IDx if cookie is absent but present in local storage', () => { + getDataFromLocalStorageStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + let idx = idxIdSubmodule.getId(); + expect(idx).to.deep.equal(ID_COOKIE_OBJECT); + }); + + it('returns undefined if both cookie and local storage are empty', () => { + let idx = idxIdSubmodule.getId(); + expect(idx).to.be.undefined; + }) + }); + + describe('IDx: test "decode" method', () => { + it('provides the IDx from a stored object', () => { + expect(idxIdSubmodule.decode(ID_COOKIE_OBJECT)).to.deep.equal(IDX_COOKIE_OBJECT); + }); + + it('provides the IDx from a stored string', () => { + expect(idxIdSubmodule.decode(IDX_DUMMY_VALUE)).to.deep.equal(IDX_COOKIE_OBJECT); + }); + }); + + describe('requestBids hook', () => { + let adUnits; + + beforeEach(() => { + adUnits = [getAdUnitMock()]; + setSubmoduleRegistry([idxIdSubmodule]); + init(config); + config.setConfig(getConfigMock()); + getCookieStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + }); + + it('when a stored IDx exists it is added to bids', (done) => { + requestBidsHook(() => { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.idx'); + expect(bid.userId.idx).to.equal(IDX_DUMMY_VALUE); + const idxIdAsEid = find(bid.userIdAsEids, e => e.source == 'idx.lat'); + expect(idxIdAsEid).to.deep.equal({ + source: 'idx.lat', + uids: [{ + id: IDX_DUMMY_VALUE, + atype: 1, + }] + }); + }); + }); + done(); + }, { adUnits }); + }); + }); +}); From ab4f80a9f129614f9d23c4fc2c0a686dde58ab44 Mon Sep 17 00:00:00 2001 From: liranbaruch Date: Fri, 9 Oct 2020 00:32:20 +0300 Subject: [PATCH 055/190] Adding Test mode for the IronSource bidder (#5831) * Change ironsource to be lower case all over code * Add test mode to the IronSource bidder --- modules/ironsourceBidAdapter.js | 20 ++++++++++++++-- modules/ironsourceBidAdapter.md | 2 ++ .../spec/modules/ironsourceBidAdapter_spec.js | 24 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/modules/ironsourceBidAdapter.js b/modules/ironsourceBidAdapter.js index 34650d46a0f..922e7b94c5b 100644 --- a/modules/ironsourceBidAdapter.js +++ b/modules/ironsourceBidAdapter.js @@ -7,7 +7,11 @@ const SUPPORTED_AD_TYPES = [VIDEO]; const BIDDER_CODE = 'ironsource'; const BIDDER_VERSION = '4.0.0'; const TTL = 360; -const SELLER_ENDPOINT = 'https://hb.yellowblue.io/hb'; +const SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; +const MODES = { + PRODUCTION: 'hb', + TEST: 'hb-test' +} const SUPPORTED_SYNC_METHODS = { IFRAME: 'iframe', PIXEL: 'pixel' @@ -86,9 +90,10 @@ registerBidder(spec); */ function buildVideoRequest(bid, bidderRequest) { const sellerParams = generateParameters(bid, bidderRequest); + const {params} = bid; return { method: 'GET', - url: SELLER_ENDPOINT, + url: getEndpoint(params.testMode), data: sellerParams }; } @@ -169,6 +174,17 @@ function isSyncMethodAllowed(syncRule, bidderCode) { return isInclude && utils.contains(bidders, bidderCode); } +/** + * Get the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : SELLER_ENDPOINT + MODES.PRODUCTION; +} + /** * Generate query parameters for the request * @param bid {bid} diff --git a/modules/ironsourceBidAdapter.md b/modules/ironsourceBidAdapter.md index 2f9e38b69e8..86756b08809 100644 --- a/modules/ironsourceBidAdapter.md +++ b/modules/ironsourceBidAdapter.md @@ -23,6 +23,7 @@ The adapter supports Video(instream). For the integration, IronSource returns co | `isOrg` | required | String | IronSource publisher Id provided by your IronSource representative | "56f91cd4d3e3660002000033" | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `ifa` | optional | String | The ID for advertisers (also referred to as "IDFA") | "XXX-XXX" +| `testMode` | optional | Boolean | This activates the test mode | false # Test Parameters ```javascript @@ -42,6 +43,7 @@ var adUnits = [ isOrg: '56f91cd4d3e3660002000033', // Required floorPrice: 2.00, // Optional ifa: 'XXX-XXX', // Optional + testMode: false // Optional } }] } diff --git a/test/spec/modules/ironsourceBidAdapter_spec.js b/test/spec/modules/ironsourceBidAdapter_spec.js index cfdc51e0235..0c59dfef14b 100644 --- a/test/spec/modules/ironsourceBidAdapter_spec.js +++ b/test/spec/modules/ironsourceBidAdapter_spec.js @@ -5,6 +5,7 @@ import { config } from 'src/config.js'; import { VIDEO } from '../../../src/mediaTypes.js'; const ENDPOINT = 'https://hb.yellowblue.io/hb'; +const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-test'; const TTL = 360; describe('ironsourceAdapter', function () { @@ -55,6 +56,21 @@ describe('ironsourceAdapter', function () { } ]; + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'isOrg': 'jdye8weeyirk00000001', + 'testMode': true + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + const bidderRequest = { bidderCode: 'ironsource', } @@ -67,6 +83,14 @@ describe('ironsourceAdapter', function () { } }); + it('sends bid request to test ENDPOINT via GET', function () { + const requests = spec.buildRequests(testModeBidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + it('should send the correct bid Id', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { From 776023771f9b5ce58178f9581c138121351e526a Mon Sep 17 00:00:00 2001 From: pm-shashank-jain Date: Fri, 9 Oct 2020 10:52:12 +0530 Subject: [PATCH 056/190] Manually took the changes for DVC related info --- modules/pubmaticAnalyticsAdapter.js | 18 ++++++++++++++++++ .../modules/pubmaticAnalyticsAdapter_spec.js | 12 ++++++++++++ 2 files changed, 30 insertions(+) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index dfe183076f4..c7eeaf87fdc 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -167,6 +167,23 @@ function getDomainFromUrl(url) { return a.hostname; } +function getDevicePlatform() { + var deviceType = 3; + try { + var ua = navigator.userAgent; + if (ua && utils.isStr(ua) && ua.trim() != '') { + ua = ua.toLowerCase().trim(); + var isMobileRegExp = new RegExp('(mobi|tablet|ios).*'); + if (ua.match(isMobileRegExp)) { + deviceType = 2; + } else { + deviceType = 1; + } + } + } catch (ex) {} + return deviceType; +} + function getValueForKgpv(bid, adUnitId) { if (bid.params.regexPattern) { return bid.params.regexPattern; @@ -233,6 +250,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['tst'] = Math.round((new window.Date()).getTime() / 1000); outputObj['pid'] = '' + profileId; outputObj['pdvid'] = '' + profileVersionId; + outputObj['dvc'] = {'plt': getDevicePlatform()}; outputObj['tgid'] = (function() { var testGroupId = parseInt(config.getConfig('testGroupId') || 0); if (testGroupId <= 15 && testGroupId >= 0) { diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 91c6ec1379d..58cafb8be65 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -11,6 +11,12 @@ let events = require('src/events'); let ajax = require('src/ajax'); let utils = require('src/utils'); +const DEFAULT_USER_AGENT = window.navigator.userAgent; +const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1'; +const setUADefault = () => { window.navigator.__defineGetter__('userAgent', function () { return DEFAULT_USER_AGENT }) }; +const setUAMobile = () => { window.navigator.__defineGetter__('userAgent', function () { return MOBILE_USER_AGENT }) }; +const setUANull = () => { window.navigator.__defineGetter__('userAgent', function () { return null }) }; + const { EVENTS: { AUCTION_INIT, @@ -247,6 +253,7 @@ describe('pubmatic analytics adapter', function () { let clock; beforeEach(function () { + setUADefault(); sandbox = sinon.sandbox.create(); xhr = sandbox.useFakeXMLHttpRequest(); @@ -643,6 +650,7 @@ describe('pubmatic analytics adapter', function () { }); it('Logger: currency conversion check', function() { + setUANull(); setConfig({ adServerCurrency: 'JPY', rates: { @@ -695,9 +703,11 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(100); expect(data.s[1].ps[0].ocry).to.equal('JPY'); + expect(data.dvc).to.deep.equal({'plt': 3}); }); it('Logger: regexPattern in bid.params', function() { + setUAMobile(); const BID_REQUESTED_COPY = utils.deepClone(MOCK.BID_REQUESTED); BID_REQUESTED_COPY.bids[1].params.regexPattern = '*'; events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); @@ -738,6 +748,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.dvc).to.deep.equal({'plt': 2}); // respective tracker slot let firstTracker = requests[1].url; expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); @@ -790,6 +801,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.dvc).to.deep.equal({'plt': 1}); // respective tracker slot let firstTracker = requests[1].url; expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); From 32495f9b91f3e0eb29fc8c8fc2ea226ec9c9bedd Mon Sep 17 00:00:00 2001 From: Gena Date: Fri, 9 Oct 2020 14:04:29 +0300 Subject: [PATCH 057/190] Adtelligent: Add new alias (#5825) --- modules/adtelligentBidAdapter.js | 25 +++++++++++++------ .../modules/adtelligentBidAdapter_spec.js | 14 ++++++++++- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index e22aafb73fc..51138a2cac7 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -6,12 +6,23 @@ import { Renderer } from '../src/Renderer.js'; import find from 'core-js-pure/features/array/find.js'; const subdomainSuffixes = ['', 1, 2]; -const getUri = (function () { - let num = 0; - return function () { - return 'https://ghb' + subdomainSuffixes[num++ % subdomainSuffixes.length] + '.adtelligent.com/v2/auction/' +const AUCTION_PATH = '/v2/auction/'; +const PROTOCOL = 'https://'; +const HOST_GETTERS = { + default: (function () { + let num = 0; + return function () { + return 'ghb' + subdomainSuffixes[num++ % subdomainSuffixes.length] + '.adtelligent.com' + } + }()), + appaloosa: function () { + return 'hb.appaloosa.media' } -}()) +} +const getUri = function (bidderCode) { + let getter = HOST_GETTERS[bidderCode] || HOST_GETTERS['default']; + return PROTOCOL + getter() + AUCTION_PATH +} const OUTSTREAM_SRC = 'https://player.adtelligent.com/outstream-unit/2.01/outstream.min.js'; const BIDDER_CODE = 'adtelligent'; const OUTSTREAM = 'outstream'; @@ -21,7 +32,7 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'selectmedia'], + aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa'], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { return !!utils.deepAccess(bid, 'params.aid'); @@ -82,7 +93,7 @@ export const spec = { data: Object.assign({}, tag, { BidRequests: bids }), adapterRequest, method: 'POST', - url: getUri() + url: getUri(adapterRequest.bidderCode) }; }) }, diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 9c694668703..62449771416 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/adtelligentBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; +import { deepClone } from 'src/utils.js'; const EXPECTED_ENDPOINTS = [ 'https://ghb.adtelligent.com/v2/auction/', @@ -9,7 +10,9 @@ const EXPECTED_ENDPOINTS = [ 'https://ghb2.adtelligent.com/v2/auction/', 'https://ghb.adtelligent.com/v2/auction/' ]; - +const aliasEP = { + appaloosa: 'https://hb.appaloosa.media/v2/auction/' +}; const DISPLAY_REQUEST = { 'bidder': 'adtelligent', 'params': { @@ -250,6 +253,15 @@ describe('adtelligentBidAdapter', () => { expect(bidReqUrls).to.deep.equal(EXPECTED_ENDPOINTS); }) + it('makes correct host for aliases', () => { + for (const alias in aliasEP) { + const bidReq = deepClone(DISPLAY_REQUEST) + bidReq.bidder = alias; + const [bidderRequest] = spec.buildRequests([bidReq], { bidderCode: alias }); + expect(bidderRequest.url).to.equal(aliasEP[alias]); + } + }) + it('building requests as arrays', () => { expect(videoRequest).to.be.a('array'); expect(displayRequest).to.be.a('array'); From 844999459c5ee0ec2836a6056f2ed07071d8089e Mon Sep 17 00:00:00 2001 From: hamper Date: Fri, 9 Oct 2020 14:30:49 +0300 Subject: [PATCH 058/190] Add vuukle adapter (#5773) * add vuukle adapter * add readme * doc: add email --- modules/vuukleBidAdapter.js | 63 ++++++++++++++++++++++ modules/vuukleBidAdapter.md | 26 +++++++++ test/spec/modules/vuukleBidAdapter_spec.js | 59 ++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 modules/vuukleBidAdapter.js create mode 100644 modules/vuukleBidAdapter.md create mode 100644 test/spec/modules/vuukleBidAdapter_spec.js diff --git a/modules/vuukleBidAdapter.js b/modules/vuukleBidAdapter.js new file mode 100644 index 00000000000..e9770b5e62e --- /dev/null +++ b/modules/vuukleBidAdapter.js @@ -0,0 +1,63 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'vuukle'; +const URL = 'https://pb.vuukle.com/adapter'; +const TIME_TO_LIVE = 360; + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + return true + }, + + buildRequests: function(bidRequests) { + const requests = bidRequests.map(function (bid) { + const parseSized = utils.parseSizesInput(bid.sizes); + const arrSize = parseSized[0].split('x'); + const params = { + url: encodeURIComponent(window.location.href), + sizes: JSON.stringify(parseSized), + width: arrSize[0], + height: arrSize[1], + params: JSON.stringify(bid.params), + rnd: Math.random(), + bidId: bid.bidId, + source: 'pbjs', + version: '$prebid.version$', + v: 1, + }; + + return { + method: 'GET', + url: URL, + data: params, + options: {withCredentials: false} + } + }); + + return requests; + }, + + interpretResponse: function(serverResponse, bidRequest) { + if (!serverResponse || !serverResponse.body || !serverResponse.body.ad) { + return []; + } + + const res = serverResponse.body; + const bidResponse = { + requestId: bidRequest.data.bidId, + cpm: res.cpm, + width: res.width, + height: res.height, + creativeId: res.creative_id, + currency: res.currency || 'USD', + netRevenue: true, + ttl: TIME_TO_LIVE, + ad: res.ad + }; + return [bidResponse]; + }, +} +registerBidder(spec); diff --git a/modules/vuukleBidAdapter.md b/modules/vuukleBidAdapter.md new file mode 100644 index 00000000000..ee7b54c6262 --- /dev/null +++ b/modules/vuukleBidAdapter.md @@ -0,0 +1,26 @@ +# Overview +``` +Module Name: Vuukle Bid Adapter +Module Type: Bidder Adapter +Maintainer: support@vuukle.com +``` + +# Description +Module that connects to Vuukle's server for bids. +Currently module supports only banner mediaType. + +# Test Parameters +``` + var adUnits = [{ + code: '/test/div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'vuukle', + params: {} + }] + }]; +``` diff --git a/test/spec/modules/vuukleBidAdapter_spec.js b/test/spec/modules/vuukleBidAdapter_spec.js new file mode 100644 index 00000000000..fdca4085c8e --- /dev/null +++ b/test/spec/modules/vuukleBidAdapter_spec.js @@ -0,0 +1,59 @@ +import { expect } from 'chai'; +import { spec } from 'modules/vuukleBidAdapter.js'; + +describe('vuukleBidAdapterTests', function() { + let bidRequestData = { + bids: [ + { + bidId: 'testbid', + bidder: 'vuukle', + params: { + test: 1 + }, + sizes: [[300, 250]] + } + ] + }; + let request = []; + + it('validate_pub_params', function() { + expect( + spec.isBidRequestValid({ + bidder: 'vuukle', + params: { + test: 1 + } + }) + ).to.equal(true); + }); + + it('validate_generated_params', function() { + request = spec.buildRequests(bidRequestData.bids); + let req_data = request[0].data; + + expect(req_data.bidId).to.equal('testbid'); + }); + + it('validate_response_params', function() { + let serverResponse = { + body: { + 'cpm': 0.01, + 'width': 300, + 'height': 250, + 'creative_id': '12345', + 'ad': 'test ad' + } + }; + + request = spec.buildRequests(bidRequestData.bids); + let bids = spec.interpretResponse(serverResponse, request[0]); + expect(bids).to.have.lengthOf(1); + + let bid = bids[0]; + expect(bid.ad).to.equal('test ad'); + expect(bid.cpm).to.equal(0.01); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('12345'); + }); +}); From b6bfbef1db6c4533a0a40cf61490d47022df2b37 Mon Sep 17 00:00:00 2001 From: tadam75 Date: Fri, 9 Oct 2020 21:04:38 +0200 Subject: [PATCH 059/190] Handling video outstream in smartadserver adapter. (#5739) * Handling video outstream in smartadserver adapter. * Fixing the outstream example with the queue handler. Co-authored-by: tadam --- modules/smartadserverBidAdapter.js | 8 +- modules/smartadserverBidAdapter.md | 39 +++++++ .../modules/smartadserverBidAdapter_spec.js | 104 ++++++++++++++++++ 3 files changed, 148 insertions(+), 3 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 97dd43fc5ba..8462e749b91 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -83,10 +83,11 @@ export const spec = { w: size[0], h: size[1] })); - } else if (videoMediaType && videoMediaType.context === 'instream') { + } else if (videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream')) { // Specific attributes for instream. let playerSize = videoMediaType.playerSize[0]; - payload.isVideo = true; + payload.isVideo = videoMediaType.context === 'instream'; + payload.mediaType = VIDEO; payload.videoData = { videoProtocol: bid.params.video.protocol, playerWidth: playerSize[0], @@ -146,10 +147,11 @@ export const spec = { ttl: response.ttl }; - if (bidRequest.isVideo) { + if (bidRequest.mediaType === VIDEO) { bidResponse.mediaType = VIDEO; bidResponse.vastUrl = response.adUrl; bidResponse.vastXml = response.ad; + bidResponse.content = response.ad; } else { bidResponse.adUrl = response.adUrl; bidResponse.ad = response.ad; diff --git a/modules/smartadserverBidAdapter.md b/modules/smartadserverBidAdapter.md index c6f68363d7c..05e29359fd2 100644 --- a/modules/smartadserverBidAdapter.md +++ b/modules/smartadserverBidAdapter.md @@ -94,4 +94,43 @@ Please reach out to your Technical account manager for more information. } }] }; +``` + +## Outstream Video + +``` + var outstreamVideoAdUnit = { + code: 'test-div', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid + }); + }); + } + }, + bids: [{ + bidder: "smart", + params: { + domain: 'https://prg.smartadserver.com', + siteId: 207435, + pageId: 896536, + formatId: 85089, + bidfloor: 5, + video: { + protocol: 6, + startDelay: 1 + } + } + }] + }; ``` \ No newline at end of file diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 2de7cb2c9ff..e3bca240a47 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -425,6 +425,7 @@ describe('Smart bid adapter tests', function () { expect(bid.mediaType).to.equal('video'); expect(bid.vastUrl).to.equal('http://awesome.fake-vast.url'); expect(bid.vastXml).to.equal(''); + expect(bid.content).to.equal(''); expect(bid.width).to.equal(640); expect(bid.height).to.equal(480); expect(bid.creativeId).to.equal('zioeufg'); @@ -473,6 +474,109 @@ describe('Smart bid adapter tests', function () { }); }); + describe('Outstream video tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + const OUTSTREAM_DEFAULT_PARAMS = [{ + adUnitCode: 'sas_43', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[800, 600]] // It seems prebid.js transforms the player size array into an array of array... + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '91', + target: 'test=prebid-outstream', + bidfloor: 0.430, + buId: '7579', + appName: 'Mozilla', + ckId: 43, + video: { + protocol: 7 + } + }, + requestId: 'efgh5679', + transactionId: 'zsfgzzga' + }]; + + var OUTSTREAM_BID_RESPONSE = { + body: { + cpm: 14, + width: 800, + height: 600, + creativeId: 'zioeufga', + currency: 'USD', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake-vast2.url', + ad: '', + cSyncUrl: 'http://awesome.fake2.csync.url' + } + }; + + it('Verify outstream video build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(OUTSTREAM_DEFAULT_PARAMS); + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('siteid').and.to.equal('1234'); + expect(requestContent).to.have.property('pageid').and.to.equal('5678'); + expect(requestContent).to.have.property('formatid').and.to.equal('91'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.43); + expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid-outstream'); + expect(requestContent).to.have.property('tagId').and.to.equal('sas_43'); + expect(requestContent).to.not.have.property('pageDomain'); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + expect(requestContent).to.have.property('buid').and.to.equal('7579'); + expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); + expect(requestContent).to.have.property('ckid').and.to.equal(43); + expect(requestContent).to.have.property('isVideo').and.to.equal(false); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(7); + expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(800); + expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(600); + }); + + it('Verify outstream parse response', function () { + const request = spec.buildRequests(OUTSTREAM_DEFAULT_PARAMS); + const bids = spec.interpretResponse(OUTSTREAM_BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(14); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastUrl).to.equal('http://awesome.fake-vast2.url'); + expect(bid.vastXml).to.equal(''); + expect(bid.content).to.equal(''); + expect(bid.width).to.equal(800); + expect(bid.height).to.equal(600); + expect(bid.creativeId).to.equal('zioeufga'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(OUTSTREAM_DEFAULT_PARAMS[0].bidId); + + expect(function () { + spec.interpretResponse(OUTSTREAM_BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + }); + describe('External ids tests', function () { it('Verify external ids in request and ids found', function () { config.setConfig({ From e39a812bb7c151b7ffeb835034bf055d041ba2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dlouh=C3=BD?= Date: Mon, 12 Oct 2020 11:12:27 +0200 Subject: [PATCH 060/190] add stroeerCoreBidAdapter (#5830) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add stroeerCoreBidAdapter * test correction * refactroring * add gvl id to spec Co-authored-by: Jakub Dlouhý Co-authored-by: karel koule Co-authored-by: Lukáš Havrlant --- modules/stroeerCoreBidAdapter.js | 196 +++++++ modules/stroeerCoreBidAdapter.md | 31 + .../modules/stroeerCoreBidAdapter_spec.js | 532 ++++++++++++++++++ 3 files changed, 759 insertions(+) create mode 100644 modules/stroeerCoreBidAdapter.js create mode 100644 modules/stroeerCoreBidAdapter.md create mode 100644 test/spec/modules/stroeerCoreBidAdapter_spec.js diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js new file mode 100644 index 00000000000..ec442f5125a --- /dev/null +++ b/modules/stroeerCoreBidAdapter.js @@ -0,0 +1,196 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const GVL_ID = 136; +const BIDDER_CODE = 'stroeerCore'; +const DEFAULT_HOST = 'hb.adscale.de'; +const DEFAULT_PATH = '/dsh'; +const DEFAULT_PORT = ''; +const FIVE_MINUTES_IN_SECONDS = 300; +const USER_SYNC_IFRAME_URL = 'https://js.adscale.de/pbsync.html'; + +const isSecureWindow = () => utils.getWindowSelf().location.protocol === 'https:'; +const isMainPageAccessible = () => getMostAccessibleTopWindow() === utils.getWindowTop(); + +function getTopWindowReferrer() { + try { + return utils.getWindowTop().document.referrer; + } catch (e) { + return utils.getWindowSelf().referrer; + } +} + +function getMostAccessibleTopWindow() { + let res = utils.getWindowSelf(); + + try { + while (utils.getWindowTop().top !== res && res.parent.location.href.length) { + res = res.parent; + } + } catch (ignore) { + } + + return res; +} + +function elementInView(elementId) { + const resolveElement = (elId) => { + const win = utils.getWindowSelf(); + + return win.document.getElementById(elId); + }; + + const visibleInWindow = (el, win) => { + const rect = el.getBoundingClientRect(); + const inView = (rect.top + rect.height >= 0) && (rect.top <= win.innerHeight); + + if (win !== win.parent) { + return inView && visibleInWindow(win.frameElement, win.parent); + } + + return inView; + }; + + try { + return visibleInWindow(resolveElement(elementId), utils.getWindowSelf()); + } catch (e) { + // old browser, element not found, cross-origin etc. + } + return undefined; +} + +function buildUrl({host: hostname = DEFAULT_HOST, port = DEFAULT_PORT, securePort, path: pathname = DEFAULT_PATH}) { + if (securePort) { + port = securePort; + } + + return utils.buildUrl({protocol: 'https', hostname, port, pathname}); +} + +function getGdprParams(gdprConsent) { + if (gdprConsent) { + const consentString = encodeURIComponent(gdprConsent.consentString || '') + const isGdpr = gdprConsent.gdprApplies ? 1 : 0; + + return `?gdpr=${isGdpr}&gdpr_consent=${consentString}` + } else { + return ''; + } +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVL_ID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (function () { + const validators = []; + + const createValidator = (checkFn, errorMsgFn) => { + return (bidRequest) => { + if (checkFn(bidRequest)) { + return true; + } else { + utils.logError(`invalid bid: ${errorMsgFn(bidRequest)}`, 'ERROR'); + return false; + } + } + }; + + function isBanner(bidReq) { + return (!bidReq.mediaTypes && !bidReq.mediaType) || + (bidReq.mediaTypes && bidReq.mediaTypes.banner) || + bidReq.mediaType === BANNER; + } + + validators.push(createValidator((bidReq) => isBanner(bidReq), + bidReq => `bid request ${bidReq.bidId} is not a banner`)); + validators.push(createValidator((bidReq) => typeof bidReq.params === 'object', + bidReq => `bid request ${bidReq.bidId} does not have custom params`)); + validators.push(createValidator((bidReq) => utils.isStr(bidReq.params.sid), + bidReq => `bid request ${bidReq.bidId} does not have a sid string field`)); + + return function (bidRequest) { + return validators.every(f => f(bidRequest)); + } + }()), + + buildRequests: function (validBidRequests = [], bidderRequest) { + const anyBid = bidderRequest.bids[0]; + + const payload = { + id: bidderRequest.auctionId, + bids: [], + ref: getTopWindowReferrer(), + ssl: isSecureWindow(), + mpa: isMainPageAccessible(), + timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart) + }; + + const userIds = anyBid.userId; + + if (!utils.isEmpty(userIds)) { + payload.user = { + euids: userIds + }; + } + + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent && gdprConsent.consentString != null && gdprConsent.gdprApplies != null) { + payload.gdpr = { + consent: bidderRequest.gdprConsent.consentString, applies: bidderRequest.gdprConsent.gdprApplies + }; + } + + function bidSizes(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes /* for prebid < 3 */ || []; + } + + validBidRequests.forEach(bid => { + payload.bids.push({ + bid: bid.bidId, sid: bid.params.sid, siz: bidSizes(bid), viz: elementInView(bid.adUnitCode) + }); + }); + + return { + method: 'POST', url: buildUrl(anyBid.params), data: payload + } + }, + + interpretResponse: function (serverResponse) { + const bids = []; + + if (serverResponse.body && typeof serverResponse.body === 'object') { + serverResponse.body.bids.forEach(bidResponse => { + bids.push({ + requestId: bidResponse.bidId, + cpm: bidResponse.cpm || 0, + width: bidResponse.width || 0, + height: bidResponse.height || 0, + ad: bidResponse.ad, + ttl: FIVE_MINUTES_IN_SECONDS, + currency: 'EUR', + netRevenue: true, + creativeId: '', + }); + }); + } + + return bids; + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + if (serverResponses.length > 0 && syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + getGdprParams(gdprConsent) + }]; + } + + return []; + } +}; + +registerBidder(spec); diff --git a/modules/stroeerCoreBidAdapter.md b/modules/stroeerCoreBidAdapter.md new file mode 100644 index 00000000000..fe6e92057c6 --- /dev/null +++ b/modules/stroeerCoreBidAdapter.md @@ -0,0 +1,31 @@ +## Overview + +``` +Module Name: Stroeer Bidder Adapter +Module Type: Bidder Adapter +Maintainer: help@cz.stroeer-labs.com +``` + + +## Ad unit configuration for publishers + +```javascript +const adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'stroeerCore', + params: { + sid: "06b782cc-091b-4f53-9cd2-0291679aa1ac" + } + }] +}]; +``` +### Config Notes + +* Slot id (`sid`) is required. The adapter will ignore bid requests from prebid if `sid` is not provided. This must be in the decoded form. For example, "1234" as opposed to "MTM0ODA=". +* The server ignores dimensions that are not supported by the slot or by the platform (such as 987x123). diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js new file mode 100644 index 00000000000..dd02ebb7c8e --- /dev/null +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -0,0 +1,532 @@ +import {assert} from 'chai'; +import {spec} from 'modules/stroeerCoreBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; + +describe('stroeerCore bid adapter', function () { + let sandbox; + let fakeServer; + let bidderRequest; + let clock; + + beforeEach(() => { + bidderRequest = buildBidderRequest(); + sandbox = sinon.sandbox.create(); + fakeServer = sandbox.useFakeServer(); + clock = sandbox.useFakeTimers(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + function assertStandardFieldsOnBid(bidObject, bidId, ad, width, height, cpm) { + assert.propertyVal(bidObject, 'requestId', bidId); + assert.propertyVal(bidObject, 'ad', ad); + assert.propertyVal(bidObject, 'width', width); + assert.propertyVal(bidObject, 'height', height); + assert.propertyVal(bidObject, 'cpm', cpm); + assert.propertyVal(bidObject, 'currency', 'EUR'); + assert.propertyVal(bidObject, 'netRevenue', true); + assert.propertyVal(bidObject, 'creativeId', ''); + } + + const AUCTION_ID = utils.getUniqueIdentifierStr(); + + // Vendor user ids and associated data + const userIds = Object.freeze({ + criteoId: 'criteo-user-id', + digitrustid: { + data: { + id: 'encrypted-user-id==', + keyv: 4, + privacy: {optout: false}, + producer: 'ABC', + version: 2 + } + }, + lipb: { + lipbid: 'T7JiRRvsRAmh88', + segments: ['999'] + } + }); + + const buildBidderRequest = () => ({ + auctionId: AUCTION_ID, + bidderRequestId: 'bidder-request-id-123', + bidderCode: 'stroeerCore', + timeout: 5000, + auctionStart: 10000, + bids: [{ + bidId: 'bid1', + bidder: 'stroeerCore', + adUnitCode: 'div-1', + mediaTypes: { + banner: { + sizes: [[300, 600], [160, 60]] + } + }, + params: { + sid: 'NDA=' + }, + userId: userIds + }, { + bidId: 'bid2', + bidder: 'stroeerCore', + adUnitCode: 'div-2', + mediaTypes: { + banner: { + sizes: [[728, 90]], + } + }, + params: { + sid: 'ODA=' + }, + userId: userIds + }], + }); + + const buildBidderRequestPreVersion3 = () => { + const request = buildBidderRequest(); + request.bids.forEach((bid) => { + bid.sizes = bid.mediaTypes.banner.sizes; + delete bid.mediaTypes; + bid.mediaType = 'banner'; + }); + return request; + }; + + const buildBidderResponse = () => ({ + 'bids': [{ + 'bidId': 'bid1', 'cpm': 4.0, 'width': 300, 'height': 600, 'ad': '
tag1
', 'tracking': {'brandId': 123} + }, { + 'bidId': 'bid2', 'cpm': 7.3, 'width': 728, 'height': 90, 'ad': '
tag2
' + }] + }); + + const createWindow = (href, params = {}) => { + let {parent, referrer, top, frameElement, placementElements = []} = params; + + const protocol = (href.indexOf('https') === 0) ? 'https:' : 'http:'; + const win = { + frameElement, + parent, + top, + location: { + protocol, href + }, + document: { + createElement: function () { + return { + setAttribute: function () { + } + } + }, + referrer, + getElementById: id => placementElements.find(el => el.id === id) + } + }; + + win.self = win; + + if (!parent) { + win.parent = win; + } + + if (!top) { + win.top = win; + } + + return win; + }; + + function createElement(id, offsetTop = 0) { + return { + id, + getBoundingClientRect: function () { + return { + top: offsetTop, height: 1 + } + } + } + } + + function setupSingleWindow(sandBox, placementElements = [createElement('div-1', 17), createElement('div-2', 54)]) { + const win = createWindow('http://www.xyz.com/', { + parent: win, top: win, frameElement: createElement(undefined, 304), placementElements: placementElements + }); + + win.innerHeight = 200; + + sandBox.stub(utils, 'getWindowSelf').returns(win); + sandBox.stub(utils, 'getWindowTop').returns(win); + + return win; + } + + function setupNestedWindows(sandBox, placementElements = [createElement('div-1', 17), createElement('div-2', 54)]) { + const topWin = createWindow('http://www.abc.org/', {referrer: 'http://www.google.com/?query=monkey'}); + topWin.innerHeight = 800; + + const midWin = createWindow('http://www.abc.org/', {parent: topWin, top: topWin, frameElement: createElement()}); + midWin.innerHeight = 400; + + const win = createWindow('http://www.xyz.com/', { + parent: midWin, top: topWin, frameElement: createElement(undefined, 304), placementElements + }); + + win.innerHeight = 200; + + sandBox.stub(utils, 'getWindowSelf').returns(win); + sandBox.stub(utils, 'getWindowTop').returns(topWin); + + return {topWin, midWin, win}; + } + + it('should only support BANNER mediaType', function () { + assert.deepEqual(spec.supportedMediaTypes, [BANNER]); + }); + + describe('bid validation entry point', () => { + let bidRequest; + + beforeEach(() => { + bidRequest = buildBidderRequest().bids[0]; + }); + + it('should have \"isBidRequestValid\" function', () => { + assert.isFunction(spec.isBidRequestValid); + }); + + it('should pass a valid bid', () => { + assert.isTrue(spec.isBidRequestValid(bidRequest)); + }); + + it('should exclude bids without slot id param', () => { + bidRequest.params.sid = undefined; + assert.isFalse(spec.isBidRequestValid(bidRequest)); + }); + + it('should exclude non-banner bids', () => { + delete bidRequest.mediaTypes.banner; + bidRequest.mediaTypes.video = { + playerSize: [640, 480] + }; + + assert.isFalse(spec.isBidRequestValid(bidRequest)); + }); + + it('should exclude non-banner, pre-version 3 bids', () => { + delete bidRequest.mediaTypes; + bidRequest.mediaType = VIDEO; + assert.isFalse(spec.isBidRequestValid(bidRequest)); + }); + }); + + describe('build request entry point', () => { + it('should have \"buildRequests\" function', () => { + assert.isFunction(spec.buildRequests); + }); + + describe('url on server request info object', () => { + let win; + beforeEach(() => { + win = setupSingleWindow(sandbox); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should use hardcoded url as default endpoint', () => { + const bidReq = buildBidderRequest(); + let serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + assert.equal(serverRequestInfo.method, 'POST'); + assert.isObject(serverRequestInfo.data); + assert.equal(serverRequestInfo.url, 'https://hb.adscale.de/dsh'); + }); + + describe('should use custom url if provided', () => { + const samples = [{ + protocol: 'http:', params: {sid: 'ODA=', host: 'other.com', port: '234', path: '/xyz'}, expected: 'https://other.com:234/xyz' + }, { + protocol: 'https:', params: {sid: 'ODA=', host: 'other.com', port: '234', path: '/xyz'}, expected: 'https://other.com:234/xyz' + }, { + protocol: 'https:', + params: {sid: 'ODA=', host: 'other.com', port: '234', securePort: '871', path: '/xyz'}, + expected: 'https://other.com:871/xyz' + }, { + protocol: 'http:', params: {sid: 'ODA=', port: '234', path: '/xyz'}, expected: 'https://hb.adscale.de:234/xyz' + }, ]; + + samples.forEach(sample => { + it(`should use ${sample.expected} as endpoint when given params ${JSON.stringify(sample.params)} and protocol ${sample.protocol}`, + function () { + win.location.protocol = sample.protocol; + + const bidReq = buildBidderRequest(); + bidReq.bids[0].params = sample.params; + bidReq.bids.length = 1; + + let serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + assert.equal(serverRequestInfo.method, 'POST'); + assert.isObject(serverRequestInfo.data); + assert.equal(serverRequestInfo.url, sample.expected); + }); + }); + }); + }); + + describe('payload on server request info object', () => { + let topWin; + let win; + + let placementElements; + beforeEach(() => { + placementElements = [createElement('div-1', 17), createElement('div-2', 54)]; + ({ topWin, win } = setupNestedWindows(sandbox, placementElements)); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should have expected JSON structure', () => { + clock.tick(13500); + const bidReq = buildBidderRequest(); + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const expectedTimeout = bidderRequest.timeout - (13500 - bidderRequest.auctionStart); + + assert.equal(expectedTimeout, 1500); + + const expectedJsonPayload = { + 'id': AUCTION_ID, + 'timeout': expectedTimeout, + 'ref': topWin.document.referrer, + 'mpa': true, + 'ssl': false, + 'bids': [{ + 'sid': 'NDA=', 'bid': 'bid1', 'siz': [[300, 600], [160, 60]], 'viz': true + }, { + 'sid': 'ODA=', 'bid': 'bid2', 'siz': [[728, 90]], 'viz': true + }], + 'user': { + 'euids': userIds + } + }; + + // trim away fields with undefined + const actualJsonPayload = JSON.parse(JSON.stringify(serverRequestInfo.data)); + + assert.deepEqual(actualJsonPayload, expectedJsonPayload); + }); + + it('should handle banner sizes for pre version 3', () => { + // Version 3 changes the way how banner sizes are accessed. + // We can support backwards compatibility with version 2.x + const bidReq = buildBidderRequestPreVersion3(); + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + assert.deepEqual(serverRequestInfo.data.bids[0].siz, [[300, 600], [160, 60]]); + assert.deepEqual(serverRequestInfo.data.bids[1].siz, [[728, 90]]); + }); + + describe('optional fields', () => { + it('should skip viz field when unable to determine visibility of placement', () => { + placementElements.length = 0; + const bidReq = buildBidderRequest(); + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + assert.lengthOf(serverRequestInfo.data.bids, 2); + + for (let bid of serverRequestInfo.data.bids) { + assert.isUndefined(bid.viz); + } + }); + + it('should skip ref field when unable to determine document referrer', () => { + // i.e., empty if user came from bookmark, or web page using 'rel="noreferrer" on link, etc + buildBidderRequest(); + + const serverRequestInfo = spec.buildRequests(bidderRequest.bids, bidderRequest); + assert.lengthOf(serverRequestInfo.data.bids, 2); + + for (let bid of serverRequestInfo.data.bids) { + assert.isUndefined(bid.ref); + } + }); + + const gdprSamples = [{consentString: 'RG9ua2V5IEtvbmc=', gdprApplies: true}, {consentString: 'UGluZyBQb25n', gdprApplies: false}]; + gdprSamples.forEach((sample) => { + it(`should add GDPR info ${JSON.stringify(sample)} when provided`, () => { + const bidReq = buildBidderRequest(); + bidReq.gdprConsent = sample; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const actualGdpr = serverRequestInfo.data.gdpr; + assert.propertyVal(actualGdpr, 'applies', sample.gdprApplies); + assert.propertyVal(actualGdpr, 'consent', sample.consentString); + }); + }); + + const skippableGdprSamples = [{consentString: null, gdprApplies: true}, // + {consentString: 'UGluZyBQb25n', gdprApplies: null}, // + {consentString: null, gdprApplies: null}, // + {consentString: undefined, gdprApplies: true}, // + {consentString: 'UGluZyBQb25n', gdprApplies: undefined}, // + {consentString: undefined, gdprApplies: undefined}]; + skippableGdprSamples.forEach((sample) => { + it(`should not add GDPR info ${JSON.stringify(sample)} when one or more values are missing`, () => { + const bidReq = buildBidderRequest(); + bidReq.gdprConsent = sample; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const actualGdpr = serverRequestInfo.data.gdpr; + assert.isUndefined(actualGdpr); + }); + }); + + it('should be able to build without third party user id data', () => { + const bidReq = buildBidderRequest(); + bidReq.bids.forEach(bid => delete bid.userId); + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + assert.lengthOf(serverRequestInfo.data.bids, 2); + assert.notProperty(serverRequestInfo, 'uids'); + }); + }); + }); + }); + + describe('interpret response entry point', () => { + it('should have \"interpretResponse\" function', () => { + assert.isFunction(spec.interpretResponse); + }); + + const invalidResponses = ['', ' ', ' ', undefined, null]; + invalidResponses.forEach(sample => { + it('should ignore invalid responses (\"' + sample + '\") response', () => { + const result = spec.interpretResponse({body: sample}); + assert.isArray(result); + assert.lengthOf(result, 0); + }); + }); + + it('should interpret a standard response', () => { + const bidderResponse = buildBidderResponse(); + + const result = spec.interpretResponse({body: bidderResponse}); + assertStandardFieldsOnBid(result[0], 'bid1', '
tag1
', 300, 600, 4); + assertStandardFieldsOnBid(result[1], 'bid2', '
tag2
', 728, 90, 7.3); + }); + + it('should return empty array, when response contains no bids', () => { + const result = spec.interpretResponse({body: {bids: []}}); + assert.deepStrictEqual(result, []); + }); + }); + + describe('get user syncs entry point', () => { + let win; + beforeEach(() => { + win = setupSingleWindow(sandbox); + + // fake + win.document.createElement = function () { + const attrs = {}; + return { + setAttribute: (name, value) => { + attrs[name] = value + }, + getAttribute: (name) => attrs[name], + hasAttribute: (name) => attrs[name] !== undefined, + tagName: 'SCRIPT', + } + } + }); + + it('should have \"getUserSyncs\" function', () => { + assert.isFunction(spec.getUserSyncs); + }); + + describe('when iframe option is enabled', () => { + it('should perform user connect when there was a response', () => { + const expectedUrl = 'https://js.adscale.de/pbsync.html'; + const userSyncResponse = spec.getUserSyncs({iframeEnabled: true}, ['']); + + assert.deepStrictEqual(userSyncResponse, [{type: 'iframe', url: expectedUrl}]); + }); + + it('should not perform user connect when there was no response', () => { + const userSyncResponse = spec.getUserSyncs({iframeEnabled: true}, []); + + assert.deepStrictEqual(userSyncResponse, []); + }); + + describe('and gdpr consent is defined', () => { + describe('and gdpr applies', () => { + it('should place gdpr query param to the user sync url with value of 1', () => { + const expectedUrl = 'https://js.adscale.de/pbsync.html?gdpr=1&gdpr_consent='; + const userSyncResponse = spec.getUserSyncs({iframeEnabled: true}, [''], {gdprApplies: true}); + + assert.deepStrictEqual(userSyncResponse, [{type: 'iframe', url: expectedUrl}]); + }); + }); + + describe('and gdpr does not apply', () => { + it('should place gdpr query param to the user sync url with zero value', () => { + const expectedUrl = 'https://js.adscale.de/pbsync.html?gdpr=0&gdpr_consent='; + const userSyncResponse = spec.getUserSyncs({iframeEnabled: true}, [''], {gdprApplies: false}); + + assert.deepStrictEqual(userSyncResponse, [{type: 'iframe', url: expectedUrl}]); + }); + + describe('because consent does not specify it', () => { + it('should place gdpr query param to the user sync url with zero value', () => { + const expectedUrl = 'https://js.adscale.de/pbsync.html?gdpr=0&gdpr_consent='; + const userSyncResponse = spec.getUserSyncs({iframeEnabled: true}, [''], {}); + + assert.deepStrictEqual(userSyncResponse, [{type: 'iframe', url: expectedUrl}]); + }); + }); + }); + + describe('and consent string is defined', () => { + it('should pass consent string to gdpr consent query param', () => { + const consentString = 'consent_string'; + const expectedUrl = `https://js.adscale.de/pbsync.html?gdpr=1&gdpr_consent=${consentString}`; + const userSyncResponse = spec.getUserSyncs({iframeEnabled: true}, [''], {gdprApplies: true, consentString}); + + assert.deepStrictEqual(userSyncResponse, [{type: 'iframe', url: expectedUrl}]); + }); + + it('should correctly escape invalid characters', () => { + const consentString = 'consent ?stri&ng'; + const expectedUrl = `https://js.adscale.de/pbsync.html?gdpr=1&gdpr_consent=consent%20%3Fstri%26ng`; + const userSyncResponse = spec.getUserSyncs({iframeEnabled: true}, [''], {gdprApplies: true, consentString}); + + assert.deepStrictEqual(userSyncResponse, [{type: 'iframe', url: expectedUrl}]); + }); + }); + }); + }); + + describe('when iframe option is disabled', () => { + it('should not perform user connect even when there was a response', () => { + const userSyncResponse = spec.getUserSyncs({iframeEnabled: false}, ['']); + + assert.deepStrictEqual(userSyncResponse, []); + }); + + it('should not perform user connect when there was no response', () => { + const userSyncResponse = spec.getUserSyncs({iframeEnabled: false}, []); + + assert.deepStrictEqual(userSyncResponse, []); + }); + }); + }); +}); From ad41a193d05957f42eac56dd8b6772c328647cbd Mon Sep 17 00:00:00 2001 From: Niksok Date: Mon, 12 Oct 2020 16:41:56 +0300 Subject: [PATCH 061/190] Added the ability to send multiple bids in one ad request for mediaforce bid adapter (#5834) * Added the ability to send multiple bids in one ad request for mediaforce bid adapter * Fixes after review for mediaforce bid adapter --- modules/mediaforceBidAdapter.js | 78 +++++--- .../spec/modules/mediaforceBidAdapter_spec.js | 169 +++++++++++++++++- 2 files changed, 216 insertions(+), 31 deletions(-) diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js index 7e5c06b1b48..7e29bb519b5 100644 --- a/modules/mediaforceBidAdapter.js +++ b/modules/mediaforceBidAdapter.js @@ -114,18 +114,28 @@ export const spec = { } const referer = bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.referer) : ''; + const auctionId = bidderRequest && bidderRequest.auctionId; + const timeout = bidderRequest && bidderRequest.timeout; const dnt = utils.getDNT() ? 1 : 0; - let requests = []; + const requestsMap = {}; + const requests = []; + let isTest = false; validBidRequests.forEach(bid => { + isTest = isTest || bid.params.is_test; let tagid = bid.params.placement_id; let bidfloor = bid.params.bidfloor ? parseFloat(bid.params.bidfloor) : 0; - let imp = []; let validImp = false; let impObj = { id: bid.bidId, tagid: tagid, - secure: 1, + secure: window.location.protocol === 'https' ? 1 : 0, bidfloor: bidfloor, + ext: { + mediaforce: { + transactionId: bid.transactionId + } + } + }; for (let mediaTypes in bid.mediaTypes) { switch (mediaTypes) { @@ -140,31 +150,47 @@ export const spec = { default: return; } } - validImp && imp.push(impObj); - let request = { - id: bid.transactionId, - site: { - page: referer, - ref: referer, - id: bid.params.publisher_id, - publisher: { - id: bid.params.publisher_id + let request = requestsMap[bid.params.publisher_id]; + if (!request) { + request = { + id: Math.round(Math.random() * 1e16).toString(16), + site: { + page: window.location.href, + ref: referer, + id: bid.params.publisher_id, + publisher: { + id: bid.params.publisher_id + }, }, - }, - device: { - ua: navigator.userAgent, - js: 1, - dnt: dnt, - language: getLanguage() - }, - imp - }; - requests.push({ - method: 'POST', - url: bid.params.is_test ? TEST_ENDPOINT_URL : ENDPOINT_URL, - data: JSON.stringify(request) - }); + device: { + ua: navigator.userAgent, + js: 1, + dnt: dnt, + language: getLanguage() + }, + ext: { + mediaforce: { + hb_key: auctionId + } + }, + tmax: timeout, + imp: [] + }; + requestsMap[bid.params.publisher_id] = request; + requests.push({ + method: 'POST', + url: ENDPOINT_URL, + data: request + }); + } + validImp && request.imp.push(impObj); + }); + requests.forEach((req) => { + if (isTest) { + req.url = TEST_ENDPOINT_URL; + } + req.data = JSON.stringify(req.data); }); return requests; }, diff --git a/test/spec/modules/mediaforceBidAdapter_spec.js b/test/spec/modules/mediaforceBidAdapter_spec.js index ee478acbc83..0b3271da770 100644 --- a/test/spec/modules/mediaforceBidAdapter_spec.js +++ b/test/spec/modules/mediaforceBidAdapter_spec.js @@ -100,6 +100,37 @@ describe('mediaforce bid adapter', function () { transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', }; + const multiBid = [ + { + publisher_id: 'pub123', + placement_id: '202', + }, + { + publisher_id: 'pub123', + placement_id: '203', + }, + { + publisher_id: 'pub124', + placement_id: '202', + }, + { + publisher_id: 'pub123', + placement_id: '203', + transactionId: '8df76688-1618-417a-87b1-60ad046841c9' + } + ].map(({publisher_id, placement_id, transactionId}) => { + return { + bidder: 'mediaforce', + params: {publisher_id, placement_id}, + mediaTypes: { + banner: { + sizes: [[300, 250], [600, 400]] + } + }, + transactionId: transactionId || 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }); + const refererInfo = { referer: 'https://www.prebid.org', reachedTop: true, @@ -111,7 +142,9 @@ describe('mediaforce bid adapter', function () { const requestUrl = `${baseUrl}/header_bid`; const dnt = utils.getDNT() ? 1 : 0; - const secure = 1; + const secure = window.location.protocol === 'https' ? 1 : 0; + const pageUrl = window.location.href; + const timeout = 1500; it('should return undefined if no validBidRequests passed', function () { assert.equal(spec.buildRequests([]), undefined); @@ -127,18 +160,29 @@ describe('mediaforce bid adapter', function () { bid.params.bidfloor = 0.5; let bidRequests = [bid]; - let bidderRequest = {bids: bidRequests, refererInfo: refererInfo}; + let bidderRequest = { + bids: bidRequests, + refererInfo: refererInfo, + timeout: timeout, + auctionId: '210a474e-88f0-4646-837f-4253b7cf14fb' + }; let [request] = spec.buildRequests(bidRequests, bidderRequest); let data = JSON.parse(request.data); assert.deepEqual(data, { - id: bid.transactionId, + id: data.id, + tmax: timeout, + ext: { + mediaforce: { + hb_key: bidderRequest.auctionId + } + }, site: { id: bid.params.publisher_id, publisher: {id: bid.params.publisher_id}, ref: encodeURIComponent(refererInfo.referer), - page: encodeURIComponent(refererInfo.referer), + page: pageUrl, }, device: { ua: navigator.userAgent, @@ -150,6 +194,11 @@ describe('mediaforce bid adapter', function () { tagid: bid.params.placement_id, secure: secure, bidfloor: bid.params.bidfloor, + ext: { + mediaforce: { + transactionId: bid.transactionId + } + }, banner: {w: 300, h: 250}, native: { ver: '1.2', @@ -170,7 +219,7 @@ describe('mediaforce bid adapter', function () { assert.deepEqual(request, { method: 'POST', url: requestUrl, - data: '{"id":"d45dd707-a418-42ec-b8a7-b70a6c6fab0b","site":{"page":"https%3A%2F%2Fwww.prebid.org","ref":"https%3A%2F%2Fwww.prebid.org","id":"pub123","publisher":{"id":"pub123"}},"device":{"ua":"' + navigator.userAgent + '","js":1,"dnt":' + dnt + ',"language":"' + language + '"},"imp":[{"tagid":"202","secure":1,"bidfloor":0.5,"banner":{"w":300,"h":250},"native":{"ver":"1.2","request":{"assets":[{"required":1,"id":1,"title":{"len":800}},{"required":1,"id":3,"img":{"type":3,"w":300,"h":250}},{"required":1,"id":5,"data":{"type":1}}],"context":1,"plcmttype":1,"ver":"1.2"}}}]}', + data: '{"id":"' + data.id + '","site":{"page":"' + pageUrl + '","ref":"https%3A%2F%2Fwww.prebid.org","id":"pub123","publisher":{"id":"pub123"}},"device":{"ua":"' + navigator.userAgent + '","js":1,"dnt":' + dnt + ',"language":"' + language + '"},"ext":{"mediaforce":{"hb_key":"210a474e-88f0-4646-837f-4253b7cf14fb"}},"tmax":1500,"imp":[{"tagid":"202","secure":' + secure + ',"bidfloor":0.5,"ext":{"mediaforce":{"transactionId":"d45dd707-a418-42ec-b8a7-b70a6c6fab0b"}},"banner":{"w":300,"h":250},"native":{"ver":"1.2","request":{"assets":[{"required":1,"id":1,"title":{"len":800}},{"required":1,"id":3,"img":{"type":3,"w":300,"h":250}},{"required":1,"id":5,"data":{"type":1}}],"context":1,"plcmttype":1,"ver":"1.2"}}}]}', }); }); @@ -186,6 +235,116 @@ describe('mediaforce bid adapter', function () { let data = JSON.parse(request.data); assert.deepEqual(data.imp[0].banner, {w: 300, h: 600, format: [{w: 300, h: 250}]}); }); + + it('should return proper requests for multiple imps', function () { + let bidderRequest = { + bids: multiBid, + refererInfo: refererInfo, + timeout: timeout, + auctionId: '210a474e-88f0-4646-837f-4253b7cf14fb' + }; + + let requests = spec.buildRequests(multiBid, bidderRequest); + assert.equal(requests.length, 2); + requests.forEach((req) => { + req.data = JSON.parse(req.data); + }); + + assert.deepEqual(requests, [ + { + method: 'POST', + url: requestUrl, + data: { + id: requests[0].data.id, + tmax: timeout, + ext: { + mediaforce: { + hb_key: bidderRequest.auctionId + } + }, + site: { + id: 'pub123', + publisher: {id: 'pub123'}, + ref: encodeURIComponent(refererInfo.referer), + page: pageUrl, + }, + device: { + ua: navigator.userAgent, + dnt: dnt, + js: 1, + language: language, + }, + imp: [{ + tagid: '202', + secure: secure, + bidfloor: 0, + ext: { + mediaforce: { + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }, + banner: {w: 300, h: 250, format: [{w: 600, h: 400}]}, + }, { + tagid: '203', + secure: secure, + bidfloor: 0, + ext: { + mediaforce: { + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }, + banner: {w: 300, h: 250, format: [{w: 600, h: 400}]}, + }, { + tagid: '203', + secure: secure, + bidfloor: 0, + ext: { + mediaforce: { + transactionId: '8df76688-1618-417a-87b1-60ad046841c9' + } + }, + banner: {w: 300, h: 250, format: [{w: 600, h: 400}]}, + }] + } + }, + { + method: 'POST', + url: requestUrl, + data: { + id: requests[1].data.id, + tmax: timeout, + ext: { + mediaforce: { + hb_key: bidderRequest.auctionId + } + }, + site: { + id: 'pub124', + publisher: {id: 'pub124'}, + ref: encodeURIComponent(refererInfo.referer), + page: pageUrl, + }, + device: { + ua: navigator.userAgent, + dnt: dnt, + js: 1, + language: language, + }, + imp: [{ + tagid: '202', + secure: secure, + bidfloor: 0, + ext: { + mediaforce: { + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }, + banner: {w: 300, h: 250, format: [{w: 600, h: 400}]}, + }] + } + } + ]); + }); }); describe('interpretResponse() banner', function () { From 77293a49808ee328ef6847e1f43c95f7495c49de Mon Sep 17 00:00:00 2001 From: Thomas Ladd Date: Tue, 13 Oct 2020 03:05:55 -0500 Subject: [PATCH 062/190] Force refresh userId (#5819) * Added global function for refreshing user id's * Refactored submodule initialization to allow for refresh * Added submodule initialization when refreshing user id's * Refactored refresh parameter to be optional Refactored refresh user id's parameter to be optional where an empty list will result in all modules being refreshed. * Added unit tests for refresh user id's * Added single module refresh test * Test callback in refreshUserIds test * Remove zeotapIdPlus expiration on cookie in test because it caused it to intermittently fail Co-authored-by: chammon --- modules/userId/index.js | 154 ++++++++++++------ test/spec/modules/userId_spec.js | 102 ++++++++++++ .../spec/modules/zeotapIdPlusIdSystem_spec.js | 41 +++-- 3 files changed, 231 insertions(+), 66 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index ce442d4a2d3..b934fa2d8eb 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -109,6 +109,11 @@ * @property {(function|undefined)} callback - function that will return an id */ +/** + * @typedef {Object} RefreshUserIdsOptions + * @property {(string[]|undefined)} submoduleNames - submodules to refresh + */ + import find from 'core-js-pure/features/array/find.js'; import {config} from '../../src/config.js'; import events from '../../src/events.js'; @@ -466,6 +471,54 @@ function getUserIdsAsEids() { return createEidsArray(getCombinedSubmoduleIds(initializedSubmodules)); } +/** +* This function will be exposed in the global-name-space so that userIds can be refreshed after initialization. +* @param {RefreshUserIdsOptions} options +*/ +function refreshUserIds(options, callback) { + let submoduleNames = options ? options.submoduleNames : null; + if (!submoduleNames) { + submoduleNames = []; + } + + initializeSubmodulesAndExecuteCallbacks(function() { + let consentData = gdprDataHandler.getConsentData() + + const storedConsentData = getStoredConsentData(); + setStoredConsentData(consentData); + + // gdpr consent with purpose one is required, otherwise exit immediately + let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); + if (!hasValidated && !hasGDPRConsent(consentData)) { + utils.logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); + return; + } + + let callbackSubmodules = []; + for (let submodule of userIdModules) { + if (submoduleNames.length > 0 && + submoduleNames.indexOf(submodule.submodule.name) === -1) { + continue; + } + + utils.logInfo(`${MODULE_NAME} - refreshing ${submodule.submodule.name}`); + populateSubmoduleId(submodule, consentData, storedConsentData, true); + + if (utils.isFn(submodule.callback)) { + callbackSubmodules.push(submodule); + } + } + + if (callbackSubmodules.length > 0) { + processSubmoduleCallbacks(callbackSubmodules); + } + + if (callback) { + callback(); + } + }); +} + /** * This hook returns updated list of submodules which are allowed to do get user id based on TCF 2 enforcement rules configured */ @@ -473,6 +526,57 @@ export const validateGdprEnforcement = hook('sync', function (submodules, consen return {userIdModules: submodules, hasValidated: consentData && consentData.hasValidated}; }, 'validateGdprEnforcement'); +function populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh) { + // There are two submodule configuration types to handle: storage or value + // 1. storage: retrieve user id data from cookie/html storage or with the submodule's getId method + // 2. value: pass directly to bids + if (submodule.config.storage) { + let storedId = getStoredValue(submodule.config.storage); + let response; + + let refreshNeeded = false; + if (typeof submodule.config.storage.refreshInSeconds === 'number') { + const storedDate = new Date(getStoredValue(submodule.config.storage, 'last')); + refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > submodule.config.storage.refreshInSeconds * 1000); + } + + if (!storedId || refreshNeeded || forceRefresh || !storedConsentDataMatchesConsentData(storedConsentData, consentData)) { + // No id previously saved, or a refresh is needed, or consent has changed. Request a new id from the submodule. + response = submodule.submodule.getId(submodule.config, consentData, storedId); + } else if (typeof submodule.submodule.extendId === 'function') { + // If the id exists already, give submodule a chance to decide additional actions that need to be taken + response = submodule.submodule.extendId(submodule.config, storedId); + } + + if (utils.isPlainObject(response)) { + if (response.id) { + // A getId/extendId result assumed to be valid user id data, which should be saved to users local storage or cookies + setStoredValue(submodule, response.id); + storedId = response.id; + } + + if (typeof response.callback === 'function') { + // Save async callback to be invoked after auction + submodule.callback = response.callback; + } + } + + if (storedId) { + // cache decoded value (this is copied to every adUnit bid) + submodule.idObj = submodule.submodule.decode(storedId, submodule.config); + } + } else if (submodule.config.value) { + // cache decoded value (this is copied to every adUnit bid) + submodule.idObj = submodule.config.value; + } else { + const response = submodule.submodule.getId(submodule.config, consentData, undefined); + if (utils.isPlainObject(response)) { + if (typeof response.callback === 'function') { submodule.callback = response.callback; } + if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); } + } + } +} + /** * @param {SubmoduleContainer[]} submodules * @param {ConsentData} consentData @@ -491,54 +595,7 @@ function initSubmodules(submodules, consentData) { } return userIdModules.reduce((carry, submodule) => { - // There are two submodule configuration types to handle: storage or value - // 1. storage: retrieve user id data from cookie/html storage or with the submodule's getId method - // 2. value: pass directly to bids - if (submodule.config.storage) { - let storedId = getStoredValue(submodule.config.storage); - let response; - - let refreshNeeded = false; - if (typeof submodule.config.storage.refreshInSeconds === 'number') { - const storedDate = new Date(getStoredValue(submodule.config.storage, 'last')); - refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > submodule.config.storage.refreshInSeconds * 1000); - } - - if (!storedId || refreshNeeded || !storedConsentDataMatchesConsentData(storedConsentData, consentData)) { - // No id previously saved, or a refresh is needed, or consent has changed. Request a new id from the submodule. - response = submodule.submodule.getId(submodule.config, consentData, storedId); - } else if (typeof submodule.submodule.extendId === 'function') { - // If the id exists already, give submodule a chance to decide additional actions that need to be taken - response = submodule.submodule.extendId(submodule.config, storedId); - } - - if (utils.isPlainObject(response)) { - if (response.id) { - // A getId/extendId result assumed to be valid user id data, which should be saved to users local storage or cookies - setStoredValue(submodule, response.id); - storedId = response.id; - } - - if (typeof response.callback === 'function') { - // Save async callback to be invoked after auction - submodule.callback = response.callback; - } - } - - if (storedId) { - // cache decoded value (this is copied to every adUnit bid) - submodule.idObj = submodule.submodule.decode(storedId, submodule.config); - } - } else if (submodule.config.value) { - // cache decoded value (this is copied to every adUnit bid) - submodule.idObj = submodule.config.value; - } else { - const response = submodule.submodule.getId(submodule.config, consentData, undefined); - if (utils.isPlainObject(response)) { - if (typeof response.callback === 'function') { submodule.callback = response.callback; } - if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); } - } - } + populateSubmoduleId(submodule, consentData, storedConsentData, false); carry.push(submodule); return carry; }, []); @@ -661,6 +718,7 @@ export function init(config) { // exposing getUserIds function in global-name-space so that userIds stored in Prebid can be used by external codes. (getGlobal()).getUserIds = getUserIds; (getGlobal()).getUserIdsAsEids = getUserIdsAsEids; + (getGlobal()).refreshUserIds = refreshUserIds; } // init config update listener to start the application diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 5dac5db7b54..ef393df82d5 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -311,6 +311,108 @@ describe('User ID', function () { expect(typeof (getGlobal()).getUserIdsAsEids).to.equal('function'); expect((getGlobal()).getUserIdsAsEids()).to.deep.equal(createEidsArray((getGlobal()).getUserIds())); }); + + it('pbjs.refreshUserIds refreshes', function() { + let sandbox = sinon.createSandbox(); + + let mockIdCallback = sandbox.stub().returns({id: {'MOCKID': '1111'}}); + + let mockIdSystem = { + name: 'mockId', + decode: function(value) { + return { + 'mid': value['MOCKID'] + }; + }, + getId: mockIdCallback + }; + + setSubmoduleRegistry([mockIdSystem]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'mockId', + value: {id: {mockId: '1111'}} + }] + } + }); + expect(typeof (getGlobal()).refreshUserIds).to.equal('function'); + + getGlobal().getUserIds(); // force initialization + + // update config so that getId will be called + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'mockId', + storage: {name: 'mockid', type: 'cookie'}, + }] + } + }); + + getGlobal().refreshUserIds(); + expect(mockIdCallback.callCount).to.equal(1); + }); + + it('pbjs.refreshUserIds refreshes single', function() { + coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('REFRESH', '', EXPIRED_COOKIE_DATE); + + let sandbox = sinon.createSandbox(); + let mockIdCallback = sandbox.stub().returns({id: {'MOCKID': '1111'}}); + let refreshUserIdsCallback = sandbox.stub(); + + let mockIdSystem = { + name: 'mockId', + decode: function(value) { + return { + 'mid': value['MOCKID'] + }; + }, + getId: mockIdCallback + }; + + let refreshedIdCallback = sandbox.stub().returns({id: {'REFRESH': '1111'}}); + + let refreshedIdSystem = { + name: 'refreshedId', + decode: function(value) { + return { + 'refresh': value['REFRESH'] + }; + }, + getId: refreshedIdCallback + }; + + setSubmoduleRegistry([refreshedIdSystem, mockIdSystem]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'mockId', + storage: {name: 'MOCKID', type: 'cookie'}, + }, + { + name: 'refreshedId', + storage: {name: 'refreshedid', type: 'cookie'}, + } + ] + } + }); + + getGlobal().getUserIds(); // force initialization + + getGlobal().refreshUserIds({submoduleNames: 'refreshedId'}, refreshUserIdsCallback); + + expect(refreshedIdCallback.callCount).to.equal(2); + expect(mockIdCallback.callCount).to.equal(1); + expect(refreshUserIdsCallback.callCount).to.equal(1); + }); }); describe('Opt out', function () { diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 54082618120..4f9e691f12e 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -34,26 +34,23 @@ function getAdUnitMock(code = 'adUnit-code') { }; } -describe('Zeotap ID System', function() { - let getDataFromLocalStorageStub, localStorageIsEnabledStub; - let getCookieStub, cookiesAreEnabledStub; - beforeEach(function () { - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); - getCookieStub = sinon.stub(storage, 'getCookie'); - cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); - }); +function unsetCookie() { + storage.setCookie(ZEOTAP_COOKIE_NAME, ''); +} - afterEach(function () { - getDataFromLocalStorageStub.restore(); - localStorageIsEnabledStub.restore(); - getCookieStub.restore(); - cookiesAreEnabledStub.restore(); - }); +function unsetLocalStorage() { + storage.setDataInLocalStorage(ZEOTAP_COOKIE_NAME, ''); +} +describe('Zeotap ID System', function() { describe('test method: getId', function() { + afterEach(() => { + unsetCookie(); + unsetLocalStorage(); + }); + it('provides the stored Zeotap id if a cookie exists', function() { - getCookieStub.withArgs(ZEOTAP_COOKIE_NAME).returns(ENCODED_ZEOTAP_COOKIE); + storage.setCookie(ZEOTAP_COOKIE_NAME, ENCODED_ZEOTAP_COOKIE); let id = zeotapIdPlusSubmodule.getId(); expect(id).to.deep.equal({ id: ENCODED_ZEOTAP_COOKIE @@ -61,7 +58,7 @@ describe('Zeotap ID System', function() { }); it('provides the stored Zeotap id if cookie is absent but present in local storage', function() { - getDataFromLocalStorageStub.withArgs(ZEOTAP_COOKIE_NAME).returns(ENCODED_ZEOTAP_COOKIE); + storage.setDataInLocalStorage(ZEOTAP_COOKIE_NAME, ENCODED_ZEOTAP_COOKIE); let id = zeotapIdPlusSubmodule.getId(); expect(id).to.deep.equal({ id: ENCODED_ZEOTAP_COOKIE @@ -99,10 +96,18 @@ describe('Zeotap ID System', function() { beforeEach(function() { adUnits = [getAdUnitMock()]; + storage.setCookie( + ZEOTAP_COOKIE_NAME, + ENCODED_ZEOTAP_COOKIE + ); setSubmoduleRegistry([zeotapIdPlusSubmodule]); init(config); config.setConfig(getConfigMock()); - getCookieStub.withArgs(ZEOTAP_COOKIE_NAME).returns(ENCODED_ZEOTAP_COOKIE); + }); + + afterEach(function() { + unsetCookie(); + unsetLocalStorage(); }); it('when a stored Zeotap ID exists it is added to bids', function(done) { From 511e16a0fdeb93bf18a9f9d6b19c1c0d0b0f2e29 Mon Sep 17 00:00:00 2001 From: hybrid-ai <58724131+hybrid-ai@users.noreply.github.com> Date: Tue, 13 Oct 2020 14:50:26 +0300 Subject: [PATCH 063/190] Hybrid adapter. Added support In-Image format (#5754) * Added Hybrid.ai adapter * Is used 'find' from 'core-js/library/fn/array/find' instead Array.find * Fixed missing file extensions for imports * Typo fixed * Fixed missing file extensions for imports * Added support In-Image format * Added more test * Fixed errors of lint * Deleted debug line Co-authored-by: s.shevtsov --- modules/hybridBidAdapter.js | 59 ++++++- modules/hybridBidAdapter.md | 184 +++++++++++++++++++++ test/spec/modules/hybridBidAdapter_spec.js | 70 +++++++- 3 files changed, 307 insertions(+), 6 deletions(-) diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index 5671756e0b2..dd55483ef33 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -10,12 +10,14 @@ const DSP_ENDPOINT = 'https://hbe198.hybrid.ai/prebidhb'; const TRAFFIC_TYPE_WEB = 1; const PLACEMENT_TYPE_BANNER = 1; const PLACEMENT_TYPE_VIDEO = 2; +const PLACEMENT_TYPE_IN_IMAGE = 3; const TTL = 60; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; const placementTypes = { 'banner': PLACEMENT_TYPE_BANNER, - 'video': PLACEMENT_TYPE_VIDEO + 'video': PLACEMENT_TYPE_VIDEO, + 'inImage': PLACEMENT_TYPE_IN_IMAGE }; function buildBidRequests(validBidRequests) { @@ -26,7 +28,8 @@ function buildBidRequests(validBidRequests) { transactionId: validBidRequest.transactionId, sizes: validBidRequest.sizes, placement: placementTypes[params.placement], - placeId: params.placeId + placeId: params.placeId, + imageUrl: params.imageUrl || '' }; return bidRequest; @@ -94,6 +97,33 @@ function buildBid(bidData) { bid.renderer = createRenderer(bid); } } + } else if (bidData.placement === PLACEMENT_TYPE_IN_IMAGE) { + bid.mediaType = BANNER; + bid.inImageContent = { + content: { + content: bidData.content, + actionUrls: {} + } + }; + let actionUrls = bid.inImageContent.content.actionUrls; + actionUrls.loadUrls = bidData.inImage.loadtrackers || []; + actionUrls.impressionUrls = bidData.inImage.imptrackers || []; + actionUrls.scrollActUrls = bidData.inImage.startvisibilitytrackers || []; + actionUrls.viewUrls = bidData.inImage.viewtrackers || []; + actionUrls.stopAnimationUrls = bidData.inImage.stopanimationtrackers || []; + actionUrls.closeBannerUrls = bidData.inImage.closebannertrackers || []; + + if (bidData.inImage.but) { + let inImageOptions = bid.inImageContent.content.inImageOptions = {}; + inImageOptions.hasButton = true; + inImageOptions.buttonLogoUrl = bidData.inImage.but_logo; + inImageOptions.buttonProductUrl = bidData.inImage.but_prod; + inImageOptions.buttonHead = bidData.inImage.but_head; + inImageOptions.buttonHeadColor = bidData.inImage.but_head_colour; + inImageOptions.dynparams = bidData.inImage.dynparams || {}; + } + + bid.ad = wrapAd(bid, bidData); } else { bid.ad = bidData.content; bid.mediaType = BANNER; @@ -116,6 +146,30 @@ function hasVideoMandatoryParams(mediaTypes) { return isHasVideoContext && isPlayerSize; } +function wrapAd(bid, bidData) { + return ` + + + + + + + + +
+ + + `; +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], @@ -133,6 +187,7 @@ export const spec = { !!bid.params.placement && ( (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'banner') || + (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'inImage' && !!bid.params.imageUrl) || (getMediaTypeFromBid(bid) === VIDEO && bid.params.placement === 'video' && hasVideoMandatoryParams(bid.mediaTypes)) ) ); diff --git a/modules/hybridBidAdapter.md b/modules/hybridBidAdapter.md index 245f010970a..098d8642415 100644 --- a/modules/hybridBidAdapter.md +++ b/modules/hybridBidAdapter.md @@ -52,3 +52,187 @@ var adUnits = [{ }]; ``` +# Sample In-Image Ad Unit + +```js +var adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [0, 0] + } + }, + bids: [{ + bidder: "hybrid", + params: { + placement: "inImage", + placeId: "102030405060708090000020", + imageUrl: "https://hybrid.ai/images/image.jpg" + } + }] +}]; +``` + +# Example page with In-Image + +```html + + + + + Prebid.js Banner Example + + + + + +

Prebid.js InImage Banner Test

+
+ + +
+ + +``` + +# Example page with In-Image and GPT + +```html + + + + + Prebid.js Banner Example + + + + + + +

Prebid.js Banner Ad Unit Test

+
+ + +
+ + +``` diff --git a/test/spec/modules/hybridBidAdapter_spec.js b/test/spec/modules/hybridBidAdapter_spec.js index e5ad878c9b1..d1899ef3d81 100644 --- a/test/spec/modules/hybridBidAdapter_spec.js +++ b/test/spec/modules/hybridBidAdapter_spec.js @@ -25,16 +25,22 @@ describe('Hybrid.ai Adapter', function() { placeId: PLACE_ID, placement: 'video' } + const inImageMandatoryParams = { + placeId: PLACE_ID, + placement: 'inImage', + imageUrl: 'https://hybrid.ai/images/image.jpg' + } const validBidRequests = [ getSlotConfigs({ banner: {} }, bannerMandatoryParams), - getSlotConfigs({ video: {playerSize: [[640, 480]]} }, videoMandatoryParams) + getSlotConfigs({ video: {playerSize: [[640, 480]], context: 'outstream'} }, videoMandatoryParams), + getSlotConfigs({ banner: {sizes: [0, 0]} }, inImageMandatoryParams) ] describe('isBidRequestValid method', function() { describe('returns true', function() { describe('when banner slot config has all mandatory params', () => { - describe('and placement has the correct value', function() { + describe('and banner placement has the correct value', function() { const slotConfig = getSlotConfigs( - { banner: {} }, + {banner: {}}, { placeId: PLACE_ID, placement: 'banner' @@ -43,6 +49,22 @@ describe('Hybrid.ai Adapter', function() { const isBidRequestValid = spec.isBidRequestValid(slotConfig) expect(isBidRequestValid).to.equal(true) }) + describe('and In-Image placement has the correct value', function() { + const slotConfig = getSlotConfigs( + { + banner: { + sizes: [[0, 0]] + } + }, + { + placeId: PLACE_ID, + placement: 'inImage', + imageUrl: 'imageUrl' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) describe('when video slot has all mandatory params.', function() { it('should return true, when video mediatype object are correct.', function() { const slotConfig = getSlotConfigs( @@ -84,6 +106,15 @@ describe('Hybrid.ai Adapter', function() { ) expect(isBidRequestValid).to.equal(false) }) + it('does not have the imageUrl.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placeId: PLACE_ID, + placement: 'inImage' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) it('does not have a the correct placement.', function() { const isBidRequestValid = spec.isBidRequestValid( createSlotconfig({ @@ -240,6 +271,36 @@ describe('Hybrid.ai Adapter', function() { expect(bids[0].netRevenue).to.equal(true) expect(typeof bids[0].ad).to.equal('string') }) + it('should return a In-Image bid', function() { + const request = spec.buildRequests([validBidRequests[2]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: 'html', + inImage: { + actionUrls: {} + }, + width: 100, + height: 100, + ttl: 360 + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(100) + expect(bids[0].height).to.equal(100) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].ad).to.equal('string') + }) }) describe('the bid is a video', function() { it('should return a video bid', function() { @@ -253,7 +314,8 @@ describe('Hybrid.ai Adapter', function() { currency: 'USD', content: 'html', width: 100, - height: 100 + height: 100, + transactionId: '31a58515-3634-4e90-9c96-f86196db1459' } ] } From a2c63e1b0217314ad1e379623d2b73a8e4be8430 Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Tue, 13 Oct 2020 06:03:28 -0700 Subject: [PATCH 064/190] PubMatic Analytics: internal kgpv param support in analytics (#5849) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * not passing GDPR data in analytics * adding support for OpenWrap regex support * added unit test cases --- modules/pubmaticAnalyticsAdapter.js | 17 ++- .../modules/pubmaticAnalyticsAdapter_spec.js | 101 ++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 04c6606a299..dfe183076f4 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -152,6 +152,7 @@ function parseBidResponse(bid) { 'mediaType', 'params', 'mi', + 'regexPattern', () => bid.regexPattern || undefined, 'partnerImpId', // partner impression ID 'dimensions', () => utils.pick(bid, [ 'width', @@ -166,6 +167,18 @@ function getDomainFromUrl(url) { return a.hostname; } +function getValueForKgpv(bid, adUnitId) { + if (bid.params.regexPattern) { + return bid.params.regexPattern; + } else if (bid.bidResponse && bid.bidResponse.regexPattern) { + return bid.bidResponse.regexPattern; + } else if (bid.params.kgpv) { + return bid.params.kgpv; + } else { + return adUnitId; + } +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { @@ -174,7 +187,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'pn': bid.bidder, 'bidid': bid.bidId, 'db': bid.bidResponse ? 0 : 1, - 'kgpv': bid.params.kgpv ? bid.params.kgpv : adUnitId, + 'kgpv': getValueForKgpv(bid, adUnitId), 'kgpsv': bid.params.kgpv ? bid.params.kgpv : adUnitId, 'psz': bid.bidResponse ? (bid.bidResponse.dimensions.width + 'x' + bid.bidResponse.dimensions.height) : '0x0', 'eg': bid.bidResponse ? bid.bidResponse.bidGrossCpmUSD : 0, @@ -268,7 +281,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&pn=' + enc(winningBid.bidder); pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); pixelURL += '&eg=' + enc(winningBid.bidResponse.bidGrossCpmUSD); - pixelURL += '&kgpv=' + enc(winningBid.params.kgpv || adUnitId); + pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); pixelURL += '&piid=' + enc(winningBid.bidResponse.partnerImpId || EMPTY_STRING); ajax( pixelURL, diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index d38037f40a1..91c6ec1379d 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -696,5 +696,106 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocpm).to.equal(100); expect(data.s[1].ps[0].ocry).to.equal('JPY'); }); + + it('Logger: regexPattern in bid.params', function() { + const BID_REQUESTED_COPY = utils.deepClone(MOCK.BID_REQUESTED); + BID_REQUESTED_COPY.bids[1].params.regexPattern = '*'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, BID_REQUESTED_COPY); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, BID2); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('*'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('728x90'); + expect(data.s[1].ps[0].eg).to.equal(1.52); + expect(data.s[1].ps[0].en).to.equal(1.52); + expect(data.s[1].ps[0].di).to.equal('the-deal-id'); + expect(data.s[1].ps[0].dc).to.equal('PMP'); + expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(0); + expect(data.s[1].ps[0].wb).to.equal(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 1 + expect(data.s[1].ps[0].af).to.equal('banner'); + expect(data.s[1].ps[0].ocpm).to.equal(1.52); + expect(data.s[1].ps[0].ocry).to.equal('USD'); + // respective tracker slot + let firstTracker = requests[1].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.kgpv).to.equal('*'); + }); + + it('Logger: regexPattern in bid.bidResponse', function() { + const BID2_COPY = utils.deepClone(BID2); + BID2_COPY.regexPattern = '*'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, BID2_COPY); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, Object.assign({}, BID2_COPY, { + 'status': 'rendered' + })); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('*'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('728x90'); + expect(data.s[1].ps[0].eg).to.equal(1.52); + expect(data.s[1].ps[0].en).to.equal(1.52); + expect(data.s[1].ps[0].di).to.equal('the-deal-id'); + expect(data.s[1].ps[0].dc).to.equal('PMP'); + expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(0); + expect(data.s[1].ps[0].wb).to.equal(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 1 + expect(data.s[1].ps[0].af).to.equal('banner'); + expect(data.s[1].ps[0].ocpm).to.equal(1.52); + expect(data.s[1].ps[0].ocry).to.equal('USD'); + // respective tracker slot + let firstTracker = requests[1].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.kgpv).to.equal('*'); + }); }); }); From 666c82fe44834043173d5201eca87a43a741f6a8 Mon Sep 17 00:00:00 2001 From: mmprebid <67095277+mmprebid@users.noreply.github.com> Date: Wed, 14 Oct 2020 02:23:49 +0530 Subject: [PATCH 065/190] TrueReach Bidder Adapter: Added User Sync Support (#5846) * Added Trureach Prebid Adapter * cleaned up truereach bidder adapter for release * truereach bidder adapter md file for release * [truereach] bidder adapter and md files update. bidderUrl no more configurable. * [Prebid] supporting nurl * [Prebid] changes required due to code style * [Prebid] prebid unit test * [Prebid] added advertiserDomains in response object * [Prebid] Secure Bidder Url. * Added usersync support * changes in bidder url Co-authored-by: Nitin Kumar Co-authored-by: arnav Co-authored-by: arnav --- modules/truereachBidAdapter.js | 20 ++++++++++++++++++ test/spec/modules/truereachBidAdapter_spec.js | 21 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/modules/truereachBidAdapter.js b/modules/truereachBidAdapter.js index 2de7edbc04d..92f9cc9951e 100755 --- a/modules/truereachBidAdapter.js +++ b/modules/truereachBidAdapter.js @@ -79,6 +79,26 @@ export const spec = { return bidResponses; }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = [] + + var gdprParams = ''; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'http://ads.momagic.com/jsp/usersync.jsp' + gdprParams + }); + } + return syncs; + } }; diff --git a/test/spec/modules/truereachBidAdapter_spec.js b/test/spec/modules/truereachBidAdapter_spec.js index 5fe053ceb91..36441722648 100644 --- a/test/spec/modules/truereachBidAdapter_spec.js +++ b/test/spec/modules/truereachBidAdapter_spec.js @@ -81,4 +81,25 @@ describe('truereachBidAdapterTests', function () { expect(bid.netRevenue).to.equal(false); expect(bid.meta.advertiserDomains[0]).to.equal('https://www.momagic.com/'); }); + + describe('user_sync', function() { + const user_sync_url = 'http://ads.momagic.com/jsp/usersync.jsp'; + it('register_iframe_pixel_if_iframeEnabled_is_true', function() { + let syncs = spec.getUserSyncs( + {iframeEnabled: true} + ); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal(user_sync_url); + }); + + it('if_pixelEnabled_is_true', function() { + let syncs = spec.getUserSyncs( + {pixelEnabled: true} + ); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(0); + }); + }); }); From 1b76f45f4f380bfd3574dcabb06b5acfacae4ef6 Mon Sep 17 00:00:00 2001 From: kero75 <72736014+kero75@users.noreply.github.com> Date: Tue, 13 Oct 2020 22:56:01 +0200 Subject: [PATCH 066/190] Don't parse the querystring when extracting the protocolHost (#5851) Co-authored-by: Karim El Shabrawy --- modules/criteoIdSystem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index 30641b6c225..83bc773cb30 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -31,7 +31,7 @@ function areCookiesWriteable() { } function extractProtocolHost (url, returnOnlyHost = false) { - const parsedUrl = utils.parseUrl(url) + const parsedUrl = utils.parseUrl(url, {noDecodeWholeURL: true}) return returnOnlyHost ? `${parsedUrl.hostname}` : `${parsedUrl.protocol}://${parsedUrl.hostname}${parsedUrl.port ? ':' + parsedUrl.port : ''}/`; From 3bc0bf9bf99642af954da18417190662b78eb193 Mon Sep 17 00:00:00 2001 From: Andrea Cannuni <57228257+ACannuniRP@users.noreply.github.com> Date: Tue, 13 Oct 2020 21:57:58 +0100 Subject: [PATCH 067/190] Add rubicon size 548 (#5853) * Rubicon Adapter: Add multiple sizes to sizeMap * Add new size 500x1000 (ID: 548) in Rubicon Adapter Co-authored-by: Bret Gorsline --- modules/rubiconBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index d4411a82e8f..069a7e7ead5 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -102,7 +102,8 @@ var sizeMap = { 274: '1800x200', 278: '320x500', 282: '320x400', - 288: '640x380' + 288: '640x380', + 548: '500x1000' }; utils._each(sizeMap, (item, key) => sizeMap[item] = key); From c251ad5207ed2260ffc8069d5101c7a8ee5bb351 Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 13 Oct 2020 17:12:49 -0400 Subject: [PATCH 068/190] PR Review Process: Adding RTD, UserId. General modernization. (#5829) * Adding RTD, UserId. General modernization. * Update PR_REVIEW.md Co-authored-by: Scott Menzer Co-authored-by: Scott Menzer --- PR_REVIEW.md | 82 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/PR_REVIEW.md b/PR_REVIEW.md index d7703cf20ae..1dd8fbcec0d 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -6,38 +6,28 @@ If the PR is for a standard bid adapter or a standard analytics adapter, just th For modules and core platform updates, the initial reviewer should request an additional team member to review as a sanity check. Merge should only happen when the PR has 2 `LGTM` from the core team and a documentation PR if required. ### General PR review Process +- All required global and bidder-adapter rules defined in the [Module Rules](https://docs.prebid.org/dev-docs/module-rules.html) must be followed. Please review these rules often - we depend on reviewers to enforce them. - Checkout the branch (these instructions are available on the github PR page as well). - Verify PR is a single change type. Example, refactor OR bugfix. If more than 1 type, ask submitter to break out requests. -- Verify code under review has at least 80% unit test coverage. If legacy code has no unit test coverage, ask for unit tests to be included in the PR. +- Verify code under review has at least 80% unit test coverage. If legacy code doesn't have enough unit test coverage, require that additional unit tests to be included in the PR. - Verify tests are green in Travis-ci + local build by running `gulp serve` | `gulp test` - Verify no code quality violations are present from linting (should be reported in terminal) +- Make sure the code is not setting cookies or localstorage directly -- it must use the `StorageManager`. - Review for obvious errors or bad coding practice / use best judgement here. - If the change is a new feature / change to core prebid.js - review the change with a Tech Lead on the project and make sure they agree with the nature of change. - If the change results in needing updates to docs (such as public API change, module interface etc), add a label for "needs docs" and inform the submitter they must submit a docs PR to update the appropriate area of Prebid.org **before the PR can merge**. Help them with finding where the docs are located on prebid.org if needed. - - Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/BIDDER.md file): - - If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true` - - If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true` - - If they support the US Privacy consentManagementUsp module, add `usp_supported: true` - - If they support one or more userId modules, add `userId: (list of supported vendors)` - - If they support video and/or native mediaTypes add `media_types: video, native`. Note that display is added by default. If you don't support display, add "no-display" as the first entry, e.g. `media_types: no-display, native` - - If they support COPPA, add `coppa_supported: true` - - If they support SChain, add `schain_supported: true` - - If their bidder doesn't work well with safeframed creatives, add `safeframes_ok: false`. This will alert publishers to not use safeframed creatives when creating the ad server entries for their bidder. - - If they're setting a deal ID in some scenarios, add `bidder_supports_deals: true` - - If they have an IAB Global Vendor List ID, add `gvl_id: ID`. There's no default. -- If all above is good, add a `LGTM` comment and request 1 additional core member to review. -- Once there is 2 `LGTM` on the PR, merge to master -- Ask the submitter to add a PR for documentation if applicable. +- If all above is good, add a `LGTM` comment and, if the change is in PBS-core or is an important module like the prebidServerBidAdapter, request 1 additional core member to review. +- Once there are 2 `LGTM` on the PR, merge to master - Add a line into the [draft release](https://github.com/prebid/Prebid.js/releases) notes for this submission. If no draft release is available, create one using [this template]( https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479) -- Add the PR to the appropriate project board (I.E. 1.23.0 Release) for the week, [see](https://github.com/prebid/Prebid.js/projects) -### New Adapter or updates to adapter process -- Follow steps above for general review process. In addition, please verify the following: +### Reviewing a New or Updated Bid Adapter +Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/bidder-adaptor.html + +Follow steps above for general review process. In addition, please verify the following: - Verify that bidder has submitted valid bid params and that bids are being received. - Verify that bidder is not manipulating the prebid.js auction in any way or doing things that go against the principles of the project. If unsure check with the Tech Lead. - Verify that code re-use is being done properly and that changes introduced by a bidder don't impact other bidders. - If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. -- All required global and bidder-adapter rules defined in the [Module Rules](https://docs.prebid.org/dev-docs/module-rules.html) must be followed. Please review these rules often - we depend on reviewers to enforce them. - All bidder parameter conventions must be followed: - Video params must be read from AdUnit.mediaTypes.video when available; however bidder config can override the ad unit. - First party data must be read from [`fpd.context` and `fpd.user`](https://docs.prebid.org/dev-docs/publisher-api-reference.html#setConfig-fpd). @@ -45,9 +35,61 @@ For modules and core platform updates, the initial reviewer should request an ad - Adapters cannot accept an schain parameter. Rather, they must look for the schain parameter at bidRequest.schain. - The bidRequest page referrer must checked in addition to any bidder-specific parameter. - If they're getting the COPPA flag, it must come from config.getConfig('coppa'); - +- Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/BIDDER.md file): + - If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true` + - If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true` + - If they support the US Privacy consentManagementUsp module, add `usp_supported: true` + - If they support one or more userId modules, add `userId: (list of supported vendors)` + - If they support video and/or native mediaTypes add `media_types: video, native`. Note that display is added by default. If you don't support display, add "no-display" as the first entry, e.g. `media_types: no-display, native` + - If they support COPPA, add `coppa_supported: true` + - If they support SChain, add `schain_supported: true` + - If their bidder doesn't work well with safeframed creatives, add `safeframes_ok: false`. This will alert publishers to not use safeframed creatives when creating the ad server entries for their bidder. + - If they're setting a deal ID in some scenarios, add `bidder_supports_deals: true` + - If they have an IAB Global Vendor List ID, add `gvl_id: ID`. There's no default. - After a new adapter is approved, let the submitter know they may open a PR in the [headerbid-expert repository](https://github.com/prebid/headerbid-expert) to have their adapter recognized by the [Headerbid Expert extension](https://chrome.google.com/webstore/detail/headerbid-expert/cgfkddgbnfplidghapbbnngaogeldmop). The PR should be to the [bidder patterns file](https://github.com/prebid/headerbid-expert/blob/master/bidderPatterns.js), adding an entry with their adapter's name and the url the adapter uses to send and receive bid responses. +### Reviewing a New or Updated Analytics Adapter +Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html + +No additional steps above the general review process and making sure it conforms to the [Module Rules](https://docs.prebid.org/dev-docs/module-rules.html). + +### Reviewing a New or Updated User ID Sub-Module +Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/modules/userId.html#id-providers + +Follow steps above for general review process. In addition: +- Try running the new user ID module with a basic config and confirm it hits the endpoint and stores the results. +- the filename should be camel case ending with `IdSystem` (e.g. `myCompanyIdSystem.js`) +- the `const MODULE_NAME` value should be camel case ending with `Id` (e.g. `myCompanyId` ) +- the response of the `decode` method should be an object with the key being ideally camel case similar to the module name and ending in `id` or `Id`, but in some cases this value is a shortened name and sometimes with the `id` part being all lowercase, provided there are no other uppercase letters. if there's no id or it's an invalid object, the response should be `undefined`. example "valid" values (although this is more style than a requirement) + - `mcid` + - `mcId` + - `myCompanyId` +- make sure they've added references of their new module everywhere required: + - modules/.submodules.json + - modules/userId/eids.js + - modules/userId/eids.md + - modules/userId/userId.md +- tests can go either within the userId_spec.js file or in their own _spec file if they wish +- GVLID is recommended in the *IdSystem file if they operate in EU +- make sure example configurations align to the actual code (some modules use the userId storage settings and allow pub configuration, while others handle reading/writing cookies on their own, so should not include the storage params in examples) +- the 3 available methods (getId, extendId, decode) should be used as they were intended + - decode (required method) should not be making requests to retrieve a new ID, it should just be decoding a response + - extendId (optional method) should not be making requests to retrieve a new ID, it should just be adding additional data to the id object + - getId (required method) should be the only method that gets a new ID (from ajax calls or a cookie/local storage). this ensures that decode and extend do not unnecessarily delay the auction in places where it is not expected. +- in the eids.js file, the source should be the actual domain of the provider, not a made up domain. +- in the eids.js file, the key in the array should be the same value as the key in the decode function +- make sure all supported config params align in the submodule js file and the docs / examples + +### Reviewing a New or Updated Real-Time-Data Sub-Module +Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/add-rtd-submodule.html + +Follow steps above for general review process. In addition: +- The RTD Provider must include a `providerRtdProvider.md` file. This file must have example parameters and document a sense of what to expect: what should change in the bidrequest, or what targeting data should be added? +- Try running the new sub-module and confirm the provided test parameters. +- Make sure the sub-module is "phoning home" as early as possible, but not more often than needed. +- Consider whether the kind of data the module is obtaining could have privacy implications. If so, make sure they're utilizing the `consent` data passed to them. + + ## Ticket Coordinator Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. Every Monday morning a reminder is From 916972b1686c1dff4d55ce5b5e5ad0ab40081eb6 Mon Sep 17 00:00:00 2001 From: mamatic <52153441+mamatic@users.noreply.github.com> Date: Wed, 14 Oct 2020 11:18:10 +0200 Subject: [PATCH 069/190] ATS-analytics - add retry logic to not fire request for envelope every time, and cut down analytics requests to 1/10 (#5839) * ATS-analytics - add retry logic to not fire request for envelope every time, and cut down analytics requests to 1/10 * ATS-analytics - fix test naming --- modules/atsAnalyticsAdapter.js | 22 +++++++++----- modules/identityLinkIdSystem.js | 17 +++++++++-- test/spec/modules/atsAnalyticsAdapter_spec.js | 3 +- .../spec/modules/identityLinkIdSystem_spec.js | 30 +++++++++++++++++++ 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index 9811c306738..a5bb3797bbf 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -126,13 +126,16 @@ let atsAnalyticsAdapter = Object.assign(adapter( callHandler(eventType, args); } if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - // send data to ats analytic endpoint - try { - let dataToSend = {'Data': atsAnalyticsAdapter.context.events}; - let strJSON = JSON.stringify(dataToSend); - ajax(atsAnalyticsAdapter.context.host, function () { - }, strJSON, {method: 'POST', contentType: 'application/json'}); - } catch (err) { + if (atsAnalyticsAdapter.shouldFireRequest()) { + // send data to ats analytic endpoint + try { + let dataToSend = {'Data': atsAnalyticsAdapter.context.events}; + let strJSON = JSON.stringify(dataToSend); + utils.logInfo('atsAnalytics tried to send analytics data!'); + ajax(atsAnalyticsAdapter.context.host, function () { + }, strJSON, {method: 'POST', contentType: 'application/json'}); + } catch (err) { + } } } } @@ -141,6 +144,11 @@ let atsAnalyticsAdapter = Object.assign(adapter( // save the base class function atsAnalyticsAdapter.originEnableAnalytics = atsAnalyticsAdapter.enableAnalytics; +// add check to not fire request every time, but instead to send 1/10 events +atsAnalyticsAdapter.shouldFireRequest = function () { + return (Math.floor((Math.random() * 11)) === 10); +} + // override enableAnalytics so we can get access to the config passed in from the page atsAnalyticsAdapter.enableAnalytics = function (config) { if (!config.options.pid) { diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index 73fc6408581..33dd74380ac 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -8,6 +8,9 @@ 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'; + +export const storage = getStorageManager(); /** @type {Submodule} */ export const identityLinkSubmodule = { @@ -76,7 +79,6 @@ export const identityLinkSubmodule = { }; // return envelope from third party endpoint function getEnvelope(url, callback) { - utils.logInfo('A 3P retrieval is attempted!'); const callbacks = { success: response => { let responseObj; @@ -94,7 +96,18 @@ function getEnvelope(url, callback) { callback(); } }; - ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); + + if (!storage.getCookie('_lr_retry_request')) { + setRetryCookie(); + utils.logInfo('A 3P retrieval is attempted!'); + ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); + } +} + +function setRetryCookie() { + let now = new Date(); + now.setTime(now.getTime() + 3600000); + storage.setCookie('_lr_retry_request', 'true', now.toUTCString()); } submodule('userId', identityLinkSubmodule); diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js index 849ccf88c51..84206337fad 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -18,7 +18,7 @@ describe('ats analytics adapter', function () { describe('track', function () { it('builds and sends request and response data', function () { - sinon.spy(atsAnalyticsAdapter, 'track'); + sinon.stub(atsAnalyticsAdapter, 'shouldFireRequest').returns(true); let initOptions = { pid: '10433394', @@ -134,7 +134,6 @@ describe('ats analytics adapter', function () { let requests = server.requests.filter(req => { return req.url.indexOf(initOptions.host) > -1; }); - expect(requests.length).to.equal(1); let realAfterBid = JSON.parse(requests[0].requestBody); diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index c729be4c1d6..3c544fa8d08 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -1,6 +1,9 @@ import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; +import {getStorageManager} from '../../../src/storageManager.js'; + +export const storage = getStorageManager(); const pid = '14'; const defaultConfigParams = { params: {pid: pid} }; @@ -11,6 +14,8 @@ describe('IdentityLinkId tests', function () { beforeEach(function () { logErrorStub = sinon.stub(utils, 'logError'); + // remove _lr_retry_request cookie before test + storage.setCookie('_lr_retry_request', 'true', 'Thu, 01 Jan 1970 00:00:01 GMT'); }); afterEach(function () { @@ -124,4 +129,29 @@ describe('IdentityLinkId tests', function () { ); expect(callBackSpy.calledOnce).to.be.true; }); + + it('should not call the LiveRamp envelope endpoint if cookie _lr_retry_request exist', function () { + let now = new Date(); + now.setTime(now.getTime() + 3000); + storage.setCookie('_lr_retry_request', 'true', now.toUTCString()); + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request).to.be.eq(undefined); + }); + + it('should call the LiveRamp envelope endpoint if cookie _lr_retry_request does not exist', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); }); From c4d0f99fa4c601b9cd13526178d172fbcd6f46d1 Mon Sep 17 00:00:00 2001 From: Hugo Duthil Date: Wed, 14 Oct 2020 12:12:55 +0200 Subject: [PATCH 070/190] Add examples and tests for criteo User Id Module (#5838) Co-authored-by: Hugo Duthil --- integrationExamples/gpt/userId_example.html | 5 +- modules/userId/userId.md | 18 ++++ test/spec/modules/userId_spec.js | 95 ++++++++++++++------- 3 files changed, 85 insertions(+), 33 deletions(-) diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index dddc0915db2..7375293fdf0 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -269,7 +269,10 @@ }, { name: "quantcastId" - } + }, + { + name: "criteo" + }, ], syncDelay: 5000, auctionDelay: 1000 diff --git a/modules/userId/userId.md b/modules/userId/userId.md index a9ab6ccc483..c46f67f9a9a 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -70,6 +70,13 @@ pbjs.setConfig({ name: 'sharedid', expires: 28 } + }, { + name: 'criteo', + storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible + type: 'cookie', + name: '_criteoId', + expires: 1 + } }], syncDelay: 5000, auctionDelay: 1000 @@ -141,6 +148,13 @@ pbjs.setConfig({ expires: 90, // Expiration of cookies in days refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' }, + }, { + name: 'criteo', + storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible + type: 'html5', + name: '_criteoId', + expires: 1 + } }], syncDelay: 5000 } @@ -164,6 +178,10 @@ pbjs.setConfig({ { name: "netId", value: { "netId": "fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg" } + }, + { + name: "criteo", + value: { "criteoId": "wK-fkF8zaEIlMkZMbHl3eFo4NEtoNmZaeXJtYkFjZlVuWjBhcjJMaTRYd3pZNSUyQnlKRHNGRXlpdzdjd3pjVzhjcSUyQmY4eTFzN3VSZjV1ZyUyRlA0U2ZiR0UwN2I4bDZRJTNEJTNE" } }], syncDelay: 5000 } diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index ef393df82d5..5b1faa46979 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -20,6 +20,7 @@ import { resetConsentData, setConsentConfig } from 'modules/consentManagement.js'; +import {server} from 'test/mocks/xhr.js'; import {unifiedIdSubmodule} from 'modules/unifiedIdSystem.js'; import {pubCommonIdSubmodule} from 'modules/pubCommonIdSystem.js'; import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; @@ -32,8 +33,8 @@ import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {haloIdSubmodule} from 'modules/haloIdSystem.js'; -import {server} from 'test/mocks/xhr.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedSystem.js'; +import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -41,22 +42,11 @@ const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; const CONSENT_LOCAL_STORAGE_NAME = '_pbjs_userid_consent_data'; describe('User ID', function () { - function getConfigMock(configArr1, configArr2, configArr3, configArr4, configArr5, configArr6, configArr7, configArr8, configArr9, configArr10) { + function getConfigMock(...configArrays) { return { userSync: { syncDelay: 0, - userIds: [ - (configArr1 && configArr1.length >= 3) ? getStorageMock.apply(null, configArr1) : null, - (configArr2 && configArr2.length >= 3) ? getStorageMock.apply(null, configArr2) : null, - (configArr3 && configArr3.length >= 3) ? getStorageMock.apply(null, configArr3) : null, - (configArr4 && configArr4.length >= 3) ? getStorageMock.apply(null, configArr4) : null, - (configArr5 && configArr5.length >= 3) ? getStorageMock.apply(null, configArr5) : null, - (configArr6 && configArr6.length >= 3) ? getStorageMock.apply(null, configArr6) : null, - (configArr7 && configArr7.length >= 3) ? getStorageMock.apply(null, configArr7) : null, - (configArr8 && configArr8.length >= 3) ? getStorageMock.apply(null, configArr8) : null, - (configArr9 && configArr9.length >= 3) ? getStorageMock.apply(null, configArr9) : null, - (configArr10 && configArr10.length >= 3) ? getStorageMock.apply(null, configArr10) : null - ].filter(i => i) + userIds: configArrays.map(configArray => (configArray && configArray.length >= 3) ? getStorageMock.apply(null, configArray) : null) } } } @@ -463,7 +453,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -471,14 +461,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); 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]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -489,7 +479,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]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -506,15 +496,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]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); }); - it('config with 11 configurations should result in 12 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule]); + it('config with 13 configurations should result in 13 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -552,14 +542,16 @@ describe('User ID', function () { storage: {name: 'haloId', type: 'cookie'} }, { name: 'zeotapIdPlus' + }, { + name: 'criteo' }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 12 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 13 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -574,7 +566,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -589,7 +581,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]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -931,6 +923,29 @@ describe('User ID', function () { }, {adUnits}); }); + it('test hook from criteoIdModule cookie', function (done) { + coreStorage.setCookie('storage_bidid', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([criteoIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['criteo', 'storage_bidid', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.criteoId'); + expect(bid.userId.criteoId).to.equal('test_bidid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'criteo.com', + uids: [{id: 'test_bidid', atype: 1}] + }); + }); + }); + coreStorage.setCookie('storage_bidid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + it('test hook from liveIntentId html5', function (done) { // simulate existing browser local storage values localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier'})); @@ -1412,7 +1427,7 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId and haloId have data to pass', function (done) { + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId and Criteo 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())); @@ -1426,8 +1441,9 @@ describe('User ID', function () { 'ts': 1590525289611 }), (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())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1438,7 +1454,8 @@ describe('User ID', function () { ['sharedId', 'sharedid', 'cookie'], ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], - ['haloId', 'haloId', 'cookie'])); + ['haloId', 'haloId', 'cookie'], + ['criteo', 'storage_criteo', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1475,7 +1492,11 @@ describe('User ID', function () { // also check that haloId id was copied to bid expect(bid).to.have.deep.nested.property('userId.haloId'); expect(bid.userId.haloId).to.equal('testHaloId'); - expect(bid.userIdAsEids.length).to.equal(10); + // 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'); + + expect(bid.userIdAsEids.length).to.equal(11); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1488,11 +1509,12 @@ describe('User ID', function () { coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, 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 and haloId 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())); @@ -1506,6 +1528,7 @@ describe('User ID', function () { coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); @@ -1524,6 +1547,7 @@ describe('User ID', function () { attachIdSystem(intentIqIdSubmodule); attachIdSystem(zeotapIdPlusSubmodule); attachIdSystem(haloIdSubmodule); + attachIdSystem(criteoIdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1534,7 +1558,8 @@ describe('User ID', function () { ['sharedId', 'sharedid', 'cookie'], ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], - ['haloId', 'haloId', 'cookie'])); + ['haloId', 'haloId', 'cookie'], + ['criteo', 'storage_criteo', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1572,7 +1597,12 @@ 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); + + // also check that criteo id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.criteoId'); + expect(bid.userId.criteoId).to.equal('test_bidid'); + + expect(bid.userIdAsEids.length).to.equal(11); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1585,6 +1615,7 @@ describe('User ID', function () { coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); From 681b4ea83168ea312b079dd5dc732eab35e85ce6 Mon Sep 17 00:00:00 2001 From: r-ishigami <62228392+r-ishigami@users.noreply.github.com> Date: Wed, 14 Oct 2020 22:35:35 +0900 Subject: [PATCH 071/190] Fix size validate (#5841) * add relaido adapter * remove event listener * fixed UserSyncs and e.data * fix conflicts * updated size validate Co-authored-by: cmertv-sishigami --- modules/relaidoBidAdapter.js | 30 ++++++++---- test/spec/modules/relaidoBidAdapter_spec.js | 54 +++++++++++++++++++++ 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index b3b8a647137..bc2854de40b 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -68,7 +68,7 @@ function buildRequests(validBidRequests, bidderRequest) { payload.width = playerSize[0][0]; payload.height = playerSize[0][1]; } else if (hasBannerMediaType(bidRequest)) { - const sizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + const sizes = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes')); payload.width = sizes[0][0]; payload.height = sizes[0][1]; } @@ -234,15 +234,9 @@ function isBannerValid(bid) { if (!isMobile()) { return false; } - const sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); - if (sizes && utils.isArray(sizes)) { - if (utils.isArray(sizes[0])) { - const width = sizes[0][0]; - const height = sizes[0][1]; - if (width >= 300 && height >= 250) { - return true; - } - } + const sizes = getValidSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes')); + if (sizes.length > 0) { + return true; } return false; } @@ -291,6 +285,22 @@ function hasVideoMediaType(bid) { return !!utils.deepAccess(bid, 'mediaTypes.video'); } +function getValidSizes(sizes) { + let result = []; + if (sizes && utils.isArray(sizes) && sizes.length > 0) { + for (let i = 0; i < sizes.length; i++) { + if (utils.isArray(sizes[i]) && sizes[i].length == 2) { + const width = sizes[i][0]; + const height = sizes[i][1]; + if ((width >= 300 && height >= 250) || (width == 1 && height == 1)) { + result.push([width, height]); + } + } + } + } + return result; +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index cd4918460db..42818232cda 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -86,6 +86,60 @@ describe('RelaidoAdapter', function () { setUADefault(); }); + it('should return false when missing 300x250 over and 1x1 by banner', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: { + sizes: [ + [100, 100], + [300, 100] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + setUADefault(); + }); + + it('should return true when 300x250 by banner', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: { + sizes: [ + [300, 250] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + setUADefault(); + }); + + it('should return true when 1x1 by banner', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: { + sizes: [ + [1, 1] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + setUADefault(); + }); + + it('should return true when 300x250 over by banner', function () { + setUAMobile(); + bidRequest.mediaTypes = { + banner: { + sizes: [ + [100, 100], + [300, 250] + ] + } + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + setUADefault(); + }); + it('should return false when the uuid are missing', function () { localStorage.removeItem(UUID_KEY); const result = !!(utils.isSafariBrowser()); From e9d1fb5f459d0ab786bb4cfc2f08e5b45c98a688 Mon Sep 17 00:00:00 2001 From: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Date: Wed, 14 Oct 2020 20:34:36 +0530 Subject: [PATCH 072/190] fix adunit.bid undefined edge case (#5827) --- modules/pubCommonId.js | 8 +++--- modules/sizeMappingV2.js | 9 ++++++- modules/userId/index.js | 36 +++++++++++++------------ src/prebid.js | 7 +++++ test/spec/modules/sizeMappingV2_spec.js | 18 ++++++++++--- test/spec/unit/pbjs_api_spec.js | 26 ++++++++++++++++++ 6 files changed, 80 insertions(+), 24 deletions(-) diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 174fa6ffe6e..427f775c44b 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -212,9 +212,11 @@ export function requestBidHook(next, config) { // into bid requests later. if (adUnits && pubcid) { adUnits.forEach((unit) => { - unit.bids.forEach((bid) => { - Object.assign(bid, {crumbs: {pubcid}}); - }); + if (unit.bids && utils.isArray(unit.bids)) { + unit.bids.forEach((bid) => { + Object.assign(bid, {crumbs: {pubcid}}); + }); + } }); } diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 4df537e0eb3..ffd242e57ac 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -66,7 +66,7 @@ export function isUsingNewSizeMapping(adUnits) { }); // checks for the presence of sizeConfig property at the adUnit.bids[].bidder object - adUnit.bids.forEach(bidder => { + adUnit.bids && utils.isArray(adUnit.bids) && adUnit.bids.forEach(bidder => { if (bidder.sizeConfig) { if (isUsingSizeMappingBool === false) { isUsingSizeMappingBool = true; @@ -168,8 +168,15 @@ export function checkAdUnitSetupHook(adUnits) { } const validatedAdUnits = []; adUnits.forEach(adUnit => { + const bids = adUnit.bids; const mediaTypes = adUnit.mediaTypes; let validatedBanner, validatedVideo, validatedNative; + + if (!bids || !utils.isArray(bids)) { + utils.logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`); + return; + } + if (!mediaTypes || Object.keys(mediaTypes).length === 0) { utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); return; diff --git a/modules/userId/index.js b/modules/userId/index.js index b934fa2d8eb..83573be8682 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -115,14 +115,14 @@ */ import find from 'core-js-pure/features/array/find.js'; -import {config} from '../../src/config.js'; +import { config } from '../../src/config.js'; import events from '../../src/events.js'; import * as utils from '../../src/utils.js'; -import {getGlobal} from '../../src/prebidGlobal.js'; -import {gdprDataHandler} from '../../src/adapterManager.js'; +import { getGlobal } from '../../src/prebidGlobal.js'; +import { gdprDataHandler } from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; -import {module, hook} from '../../src/hook.js'; -import {createEidsArray} from './eids.js'; +import { module, hook } from '../../src/hook.js'; +import { createEidsArray } from './eids.js'; import { getCoreStorageManager } from '../../src/storageManager.js'; const MODULE_NAME = 'User ID'; @@ -319,8 +319,8 @@ function hasGDPRConsent(consentData) { * @param {function} cb - callback for after processing is done. */ function processSubmoduleCallbacks(submodules, cb) { - const done = cb ? utils.delayExecution(cb, submodules.length) : function() { }; - submodules.forEach(function(submodule) { + const done = cb ? utils.delayExecution(cb, submodules.length) : function () { }; + submodules.forEach(function (submodule) { submodule.callback(function callbackCompleted(idObj) { // if valid, id data should be saved to cookie/html storage if (idObj) { @@ -371,11 +371,13 @@ function addIdDataToAdUnitBids(adUnits, submodules) { const combinedSubmoduleIdsAsEids = createEidsArray(combinedSubmoduleIds); if (Object.keys(combinedSubmoduleIds).length) { adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - // create a User ID object on the bid, - bid.userId = combinedSubmoduleIds; - bid.userIdAsEids = combinedSubmoduleIdsAsEids; - }); + if (adUnit.bids && utils.isArray(adUnit.bids)) { + adUnit.bids.forEach(bid => { + // create a User ID object on the bid, + bid.userId = combinedSubmoduleIds; + bid.userIdAsEids = combinedSubmoduleIdsAsEids; + }); + } }); } } @@ -398,7 +400,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { // delay auction until ids are available delayed = true; let continued = false; - const continueCallback = function() { + const continueCallback = function () { if (!continued) { continued = true; continueAuction(); @@ -415,7 +417,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { // when syncDelay is zero, process callbacks now, otherwise delay process with a setTimeout if (syncDelay > 0) { - setTimeout(function() { + setTimeout(function () { processSubmoduleCallbacks(submodulesWithCallbacks); }, syncDelay); } else { @@ -443,7 +445,7 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { */ export function requestBidsHook(fn, reqBidsConfigObj) { // initialize submodules only when undefined - initializeSubmodulesAndExecuteCallbacks(function() { + initializeSubmodulesAndExecuteCallbacks(function () { // pass available user id data to bid adapters addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules); // calling fn allows prebid to continue processing @@ -523,7 +525,7 @@ function refreshUserIds(options, callback) { * This hook returns updated list of submodules which are allowed to do get user id based on TCF 2 enforcement rules configured */ export const validateGdprEnforcement = hook('sync', function (submodules, consentData) { - return {userIdModules: submodules, hasValidated: consentData && consentData.hasValidated}; + return { userIdModules: submodules, hasValidated: consentData && consentData.hasValidated }; }, 'validateGdprEnforcement'); function populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh) { @@ -588,7 +590,7 @@ function initSubmodules(submodules, consentData) { setStoredConsentData(consentData); // gdpr consent with purpose one is required, otherwise exit immediately - let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); + let { userIdModules, hasValidated } = validateGdprEnforcement(submodules, consentData); if (!hasValidated && !hasGDPRConsent(consentData)) { utils.logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); return []; diff --git a/src/prebid.js b/src/prebid.js index 8d1d7ca1253..8bfb6024d7a 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -149,7 +149,14 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { adUnits.forEach(adUnit => { const mediaTypes = adUnit.mediaTypes; + const bids = adUnit.bids; let validatedBanner, validatedVideo, validatedNative; + + if (!bids || !utils.isArray(bids)) { + utils.logError(`Detected adUnit.code '${adUnit.code}' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.`); + return; + } + if (!mediaTypes || Object.keys(mediaTypes).length === 0) { utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); return; diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 77e17ef360f..ab06ddc8f51 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -205,6 +205,18 @@ describe('sizeMappingV2', function () { expect(adUnits[0].code).to.equal('div-gpt-ad-1460505748561-1'); }); + it('should filter out adUnit if it does not contain the required property "bids"', function() { + let adUnits = utils.deepClone(AD_UNITS); + delete adUnits[0].mediaTypes; + // before checkAdUnitSetupHook is called, the length of the adUnits should equal '2' + expect(adUnits.length).to.equal(2); + adUnits = checkAdUnitSetupHook(adUnits); + + // after checkAdUnitSetupHook is called, the length of the adUnits should be '1' + expect(adUnits.length).to.equal(1); + expect(adUnits[0].code).to.equal('div-gpt-ad-1460505748561-1'); + }); + it('should filter out adUnit if it has declared property mediaTypes with an empty object', function () { let adUnits = utils.deepClone(AD_UNITS); adUnits[0].mediaTypes = {}; @@ -657,7 +669,7 @@ describe('sizeMappingV2', function () { }, native: {} }, - bids: [] + bids: [{bidder: 'appnexus', params: 1234}] }]; checkAdUnitSetupHook(adUnit); @@ -679,7 +691,7 @@ describe('sizeMappingV2', function () { }, native: {} }, - bids: [] + bids: [{bidder: 'appnexus', params: 1234}] }]; checkAdUnitSetupHook(adUnit); @@ -698,7 +710,7 @@ describe('sizeMappingV2', function () { mediaTypes: { native: {} }, - bids: [] + bids: [{bidder: 'appnexus', params: 1234}] }]; checkAdUnitSetupHook(adUnit); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index efb1338ac5d..afad1d665fd 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1773,6 +1773,32 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].mediaTypes.native.icon).to.exist; assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.')); }); + + it('should throw error message and remove adUnit if adUnit.bids is not defined correctly', function () { + const adUnits = [{ + code: 'ad-unit-1', + mediaTypes: { + banner: { + sizes: [300, 400] + } + }, + bids: [{code: 'appnexus', params: 1234}] + }, { + code: 'bad-ad-unit-2', + mediaTypes: { + banner: { + sizes: [300, 400] + } + } + }]; + + $$PREBID_GLOBAL$$.requestBids({ + adUnits: adUnits + }); + expect(auctionArgs.adUnits.length).to.equal(1); + expect(auctionArgs.adUnits[1]).to.not.exist; + assert.ok(logErrorSpy.calledWith("Detected adUnit.code 'bad-ad-unit-2' did not have 'adUnit.bids' defined or 'adUnit.bids' is not an array. Removing adUnit from auction.")); + }); }); }); }); From 4df3696faca76c78ee04317ce214b3947bc85d01 Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Wed, 14 Oct 2020 08:33:49 -0700 Subject: [PATCH 073/190] PubMatic Analytics: pass device platform related information (#5855) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * not passing GDPR data in analytics * adding support for OpenWrap regex support * added unit test cases * passing device platform in logger call; test cases added --- modules/pubmaticAnalyticsAdapter.js | 18 +++ .../modules/pubmaticAnalyticsAdapter_spec.js | 113 ++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index dfe183076f4..c7eeaf87fdc 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -167,6 +167,23 @@ function getDomainFromUrl(url) { return a.hostname; } +function getDevicePlatform() { + var deviceType = 3; + try { + var ua = navigator.userAgent; + if (ua && utils.isStr(ua) && ua.trim() != '') { + ua = ua.toLowerCase().trim(); + var isMobileRegExp = new RegExp('(mobi|tablet|ios).*'); + if (ua.match(isMobileRegExp)) { + deviceType = 2; + } else { + deviceType = 1; + } + } + } catch (ex) {} + return deviceType; +} + function getValueForKgpv(bid, adUnitId) { if (bid.params.regexPattern) { return bid.params.regexPattern; @@ -233,6 +250,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['tst'] = Math.round((new window.Date()).getTime() / 1000); outputObj['pid'] = '' + profileId; outputObj['pdvid'] = '' + profileVersionId; + outputObj['dvc'] = {'plt': getDevicePlatform()}; outputObj['tgid'] = (function() { var testGroupId = parseInt(config.getConfig('testGroupId') || 0); if (testGroupId <= 15 && testGroupId >= 0) { diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 91c6ec1379d..11de7b13208 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -11,6 +11,12 @@ let events = require('src/events'); let ajax = require('src/ajax'); let utils = require('src/utils'); +const DEFAULT_USER_AGENT = window.navigator.userAgent; +const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1'; +const setUADefault = () => { window.navigator.__defineGetter__('userAgent', function () { return DEFAULT_USER_AGENT }) }; +const setUAMobile = () => { window.navigator.__defineGetter__('userAgent', function () { return MOBILE_USER_AGENT }) }; +const setUANull = () => { window.navigator.__defineGetter__('userAgent', function () { return null }) }; + const { EVENTS: { AUCTION_INIT, @@ -247,6 +253,7 @@ describe('pubmatic analytics adapter', function () { let clock; beforeEach(function () { + setUADefault(); sandbox = sinon.sandbox.create(); xhr = sandbox.useFakeXMLHttpRequest(); @@ -643,6 +650,7 @@ describe('pubmatic analytics adapter', function () { }); it('Logger: currency conversion check', function() { + setUANull(); setConfig({ adServerCurrency: 'JPY', rates: { @@ -695,6 +703,111 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(100); expect(data.s[1].ps[0].ocry).to.equal('JPY'); + expect(data.dvc).to.deep.equal({'plt': 3}); + }); + + it('Logger: regexPattern in bid.params', function() { + setUAMobile(); + const BID_REQUESTED_COPY = utils.deepClone(MOCK.BID_REQUESTED); + BID_REQUESTED_COPY.bids[1].params.regexPattern = '*'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, BID_REQUESTED_COPY); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, BID2); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('*'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('728x90'); + expect(data.s[1].ps[0].eg).to.equal(1.52); + expect(data.s[1].ps[0].en).to.equal(1.52); + expect(data.s[1].ps[0].di).to.equal('the-deal-id'); + expect(data.s[1].ps[0].dc).to.equal('PMP'); + expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(0); + expect(data.s[1].ps[0].wb).to.equal(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 1 + expect(data.s[1].ps[0].af).to.equal('banner'); + expect(data.s[1].ps[0].ocpm).to.equal(1.52); + expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.dvc).to.deep.equal({'plt': 2}); + // respective tracker slot + let firstTracker = requests[1].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.kgpv).to.equal('*'); + }); + + it('Logger: regexPattern in bid.bidResponse', function() { + const BID2_COPY = utils.deepClone(BID2); + BID2_COPY.regexPattern = '*'; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, BID2_COPY); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, Object.assign({}, BID2_COPY, { + 'status': 'rendered' + })); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].ps.length).to.equal(1); + expect(data.s[1].ps[0].pn).to.equal('pubmatic'); + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].db).to.equal(0); + expect(data.s[1].ps[0].kgpv).to.equal('*'); + expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); + expect(data.s[1].ps[0].psz).to.equal('728x90'); + expect(data.s[1].ps[0].eg).to.equal(1.52); + expect(data.s[1].ps[0].en).to.equal(1.52); + expect(data.s[1].ps[0].di).to.equal('the-deal-id'); + expect(data.s[1].ps[0].dc).to.equal('PMP'); + expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[1].ps[0].l2).to.equal(0); + expect(data.s[1].ps[0].ss).to.equal(1); + expect(data.s[1].ps[0].t).to.equal(0); + expect(data.s[1].ps[0].wb).to.equal(0); // bidPriceUSD is not getting set as currency module is not added, so unable to set wb to 1 + expect(data.s[1].ps[0].af).to.equal('banner'); + expect(data.s[1].ps[0].ocpm).to.equal(1.52); + expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.dvc).to.deep.equal({'plt': 1}); + // respective tracker slot + let firstTracker = requests[1].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.kgpv).to.equal('*'); }); it('Logger: regexPattern in bid.params', function() { From 2c1cd899aa1da7b91ed63e080b2d35667aacd6b9 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 14 Oct 2020 10:48:39 -0700 Subject: [PATCH 074/190] Prebid 4.12.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 686d47f53e9..0f0ee0cde08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.12.0-pre", + "version": "4.12.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 13a0425f82d93f453dca5e5de14ac5e60d2a78a9 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 14 Oct 2020 11:09:55 -0700 Subject: [PATCH 075/190] git commit -m "Increment pre version" --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f0ee0cde08..6273d680e19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.12.0", + "version": "4.13.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f545a56b63b9e5138f08d6c2909a7981d24d2400 Mon Sep 17 00:00:00 2001 From: roygiladi <58809838+roygiladi@users.noreply.github.com> Date: Thu, 15 Oct 2020 13:10:33 +0300 Subject: [PATCH 076/190] add ooloAnalyticsAdapter (#5852) * oolo analytics adapter added * update md * fix startsWith undefined * adjust tests * update tests - replace .find with .filter * update .md description --- modules/ooloAnalyticsAdapter.js | 547 ++++++++++++ modules/ooloAnalyticsAdapter.md | 24 + .../spec/modules/ooloAnalyticsAdapter_spec.js | 793 ++++++++++++++++++ 3 files changed, 1364 insertions(+) create mode 100644 modules/ooloAnalyticsAdapter.js create mode 100644 modules/ooloAnalyticsAdapter.md create mode 100644 test/spec/modules/ooloAnalyticsAdapter_spec.js diff --git a/modules/ooloAnalyticsAdapter.js b/modules/ooloAnalyticsAdapter.js new file mode 100644 index 00000000000..7195d6ac177 --- /dev/null +++ b/modules/ooloAnalyticsAdapter.js @@ -0,0 +1,547 @@ +import adapter from '../src/AnalyticsAdapter.js' +import adapterManager from '../src/adapterManager.js' +import CONSTANTS from '../src/constants.json' +import * as utils from '../src/utils.js' +import { ajax } from '../src/ajax.js' +import { config } from '../src/config.js' + +const baseUrl = 'https://pbdata.oolo.io/' +const ENDPOINTS = { + CONFIG: 'https://config-pbdata.oolo.io', + PAGE_DATA: baseUrl + 'page', + AUCTION: baseUrl + 'auctionData', + BID_WON: baseUrl + 'bidWonData', + AD_RENDER_FAILED: baseUrl + 'adRenderFailedData', + RAW: baseUrl + 'raw', + HBCONFIG: baseUrl + 'hbconfig', +} + +const pbModuleVersion = '1.0.0' +const prebidVersion = '$prebid.version$' +const analyticsType = 'endpoint' +const ADAPTER_CODE = 'oolo' +const AUCTION_END_SEND_TIMEOUT = 1500 +export const PAGEVIEW_ID = +generatePageViewId() + +const { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + NO_BID, + BID_WON, + BID_TIMEOUT, + AD_RENDER_FAILED +} = CONSTANTS.EVENTS + +const SERVER_EVENTS = { + AUCTION: 'auction', + WON: 'bidWon', + AD_RENDER_FAILED: 'adRenderFailed' +} + +const SERVER_BID_STATUS = { + NO_BID: 'noBid', + BID_REQUESTED: 'bidRequested', + BID_RECEIVED: 'bidReceived', + BID_TIMEDOUT: 'bidTimedOut', + BID_WON: 'bidWon' +} + +let auctions = {} +let initOptions = {} +let eventsQueue = [] + +const onAuctionInit = (args) => { + const { auctionId, adUnits, timestamp } = args + + let auction = auctions[auctionId] = { + ...args, + adUnits: {}, + auctionStart: timestamp, + _sentToServer: false + } + + handleCustomFields(auction, AUCTION_INIT, args) + + utils._each(adUnits, adUnit => { + auction.adUnits[adUnit.code] = { + ...adUnit, + auctionId, + adunid: adUnit.code, + bids: {}, + } + }) +} + +const onBidRequested = (args) => { + const { auctionId, bids, start, timeout } = args + const _start = start || Date.now() + const auction = auctions[auctionId] + const auctionAdUnits = auction.adUnits + + bids.forEach(bid => { + const { adUnitCode } = bid + const bidId = parseBidId(bid) + + auctionAdUnits[adUnitCode].bids[bidId] = { + ...bid, + timeout, + start: _start, + rs: _start - auction.auctionStart, + bidStatus: SERVER_BID_STATUS.BID_REQUESTED, + } + }) +} + +const onBidResponse = (args) => { + const { auctionId, adUnitCode } = args + const auction = auctions[auctionId] + const bidId = parseBidId(args) + let bid = auction.adUnits[adUnitCode].bids[bidId] + + Object.assign(bid, args, { + bidStatus: SERVER_BID_STATUS.BID_RECEIVED, + end: args.responseTimestamp, + re: args.responseTimestamp - auction.auctionStart + }) +} + +const onNoBid = (args) => { + const { auctionId, adUnitCode } = args + const bidId = parseBidId(args) + const end = Date.now() + const auction = auctions[auctionId] + let bid = auction.adUnits[adUnitCode].bids[bidId] + + Object.assign(bid, args, { + bidStatus: SERVER_BID_STATUS.NO_BID, + end, + re: end - auction.auctionStart + }) +} + +const onBidWon = (args) => { + const { auctionId, adUnitCode } = args + const bidId = parseBidId(args) + const bid = auctions[auctionId].adUnits[adUnitCode].bids[bidId] + + Object.assign(bid, args, { + bidStatus: SERVER_BID_STATUS.BID_WON, + isW: true, + isH: true + }) + + if (auctions[auctionId]._sentToServer) { + const payload = { + auctionId, + adunid: adUnitCode, + bid: mapBid(bid, BID_WON) + } + + sendEvent(SERVER_EVENTS.WON, payload) + } +} + +const onBidTimeout = (args) => { + utils._each(args, bid => { + const { auctionId, adUnitCode } = bid + const bidId = parseBidId(bid) + let bidCache = auctions[auctionId].adUnits[adUnitCode].bids[bidId] + + Object.assign(bidCache, bid, { + bidStatus: SERVER_BID_STATUS.BID_TIMEDOUT, + }) + }) +} + +const onAuctionEnd = (args) => { + const { auctionId, adUnits, ...restAuctionEnd } = args + + Object.assign(auctions[auctionId], restAuctionEnd, { + auctionEnd: args.auctionEnd || Date.now() + }) + + // wait for bidWon before sending to server + setTimeout(() => { + auctions[auctionId]._sentToServer = true + const finalAuctionData = buildAuctionData(auctions[auctionId]) + + sendEvent(SERVER_EVENTS.AUCTION, finalAuctionData) + }, initOptions.serverConfig.BID_WON_TIMEOUT || AUCTION_END_SEND_TIMEOUT) +} + +const onAdRenderFailed = (args) => { + const data = utils.deepClone(args) + data.timestamp = Date.now() + + if (data.bid) { + data.bid = mapBid(data.bid, AD_RENDER_FAILED) + } + + sendEvent(SERVER_EVENTS.AD_RENDER_FAILED, data) +} + +var ooloAdapter = Object.assign( + adapter({ analyticsType }), { + track({ eventType, args }) { + // wait for server configuration before processing the events + if (typeof initOptions.serverConfig !== 'undefined' && eventsQueue.length === 0) { + handleEvent(eventType, args) + } else { + eventsQueue.push({ eventType, args }) + } + } + } +) + +function handleEvent(eventType, args) { + try { + const { sendRaw } = initOptions.serverConfig.events[eventType] + if (sendRaw) { + sendEvent(eventType, args, true) + } + } catch (e) { } + + switch (eventType) { + case AUCTION_INIT: + onAuctionInit(args) + break + case BID_REQUESTED: + onBidRequested(args) + break + case NO_BID: + onNoBid(args) + break + case BID_RESPONSE: + onBidResponse(args) + break + case BID_WON: + onBidWon(args) + break + case BID_TIMEOUT: + onBidTimeout(args) + break + case AUCTION_END: + onAuctionEnd(args) + break + case AD_RENDER_FAILED: + onAdRenderFailed(args) + break + } +} + +function sendEvent(eventType, args, isRaw) { + let data = utils.deepClone(args) + + Object.assign(data, buildCommonDataProperties(), { + eventType + }) + + if (isRaw) { + let rawEndpoint + try { + const { endpoint, omitRawFields } = initOptions.serverConfig.events[eventType] + rawEndpoint = endpoint + handleCustomRawFields(data, omitRawFields) + } catch (e) { } + ajaxCall(rawEndpoint || ENDPOINTS.RAW, () => { }, JSON.stringify(data)) + } else { + let endpoint + if (eventType === SERVER_EVENTS.AD_RENDER_FAILED) { + endpoint = ENDPOINTS.AD_RENDER_FAILED + } else if (eventType === SERVER_EVENTS.WON) { + endpoint = ENDPOINTS.BID_WON + } else { + endpoint = ENDPOINTS.AUCTION + } + + ajaxCall(endpoint, () => { }, JSON.stringify(data)) + } +} + +function checkEventsQueue() { + while (eventsQueue.length) { + const event = eventsQueue.shift() + handleEvent(event.eventType, event.args) + } +} + +function buildAuctionData(auction) { + const auctionData = utils.deepClone(auction) + const keysToRemove = ['adUnitCodes', 'auctionStatus', 'bidderRequests', 'bidsReceived', 'noBids', 'winningBids', 'timestamp', 'config'] + + keysToRemove.forEach(key => { + delete auctionData[key] + }) + + handleCustomFields(auctionData, AUCTION_END, auction) + + // turn bids object into array of objects + Object.keys(auctionData.adUnits).forEach(adUnit => { + const adUnitObj = auctionData.adUnits[adUnit] + adUnitObj.bids = Object.keys(adUnitObj.bids).map(key => mapBid(adUnitObj.bids[key])) + delete adUnitObj['adUnitCode'] + delete adUnitObj['code'] + delete adUnitObj['transactionId'] + }) + + // turn adUnits objects into array of adUnits + auctionData.adUnits = Object.keys(auctionData.adUnits).map(key => auctionData.adUnits[key]) + + return auctionData +} + +function buildCommonDataProperties() { + return { + pvid: PAGEVIEW_ID, + pid: initOptions.pid, + pbModuleVersion + } +} + +function buildLogMessage(message) { + return `oolo: ${message}` +} + +function parseBidId(bid) { + return bid.bidId || bid.requestId +} + +function mapBid({ + bidStatus, + start, + end, + mediaType, + creativeId, + originalCpm, + originalCurrency, + source, + netRevenue, + currency, + width, + height, + timeToRespond, + responseTimestamp, + ...rest +}, eventType) { + const bidObj = { + bst: bidStatus, + s: start, + e: responseTimestamp || end, + mt: mediaType, + crId: creativeId, + oCpm: originalCpm, + oCur: originalCurrency, + src: source, + nrv: netRevenue, + cur: currency, + w: width, + h: height, + ttr: timeToRespond, + ...rest, + } + + delete bidObj['bidRequestsCount'] + delete bidObj['bidderRequestId'] + delete bidObj['bidderRequestsCount'] + delete bidObj['bidderWinsCount'] + delete bidObj['schain'] + delete bidObj['refererInfo'] + delete bidObj['statusMessage'] + delete bidObj['status'] + delete bidObj['adUrl'] + delete bidObj['ad'] + delete bidObj['usesGenericKeys'] + delete bidObj['requestTimestamp'] + + try { + handleCustomFields(bidObj, eventType || BID_RESPONSE, rest) + } catch (e) { } + + return bidObj +} + +function handleCustomFields(obj, eventType, args) { + try { + const { pickFields, omitFields } = initOptions.serverConfig.events[eventType] + + if (pickFields && obj && args) { + Object.assign(obj, utils.pick(args, pickFields)) + } + + if (omitFields && obj && args) { + omitFields.forEach(field => { + utils.deepSetValue(obj, field, undefined) + }) + } + } catch (e) { } +} + +function handleCustomRawFields(obj, omitRawFields) { + try { + if (omitRawFields && obj) { + omitRawFields.forEach(field => { + utils.deepSetValue(obj, field, undefined) + }) + } + } catch (e) { } +} + +function getServerConfig() { + const defaultConfig = { events: {} } + + ajaxCall( + ENDPOINTS.CONFIG + '?pid=' + initOptions.pid, + { + success: function (data) { + try { + initOptions.serverConfig = JSON.parse(data) || defaultConfig + } catch (e) { + initOptions.serverConfig = defaultConfig + } + checkEventsQueue() + }, + error: function () { + initOptions.serverConfig = defaultConfig + checkEventsQueue() + } + }, + null + ) +} + +function sendPage() { + setTimeout(() => { + const payload = { + timestamp: Date.now(), + screenWidth: window.screen.width, + screenHeight: window.screen.height, + url: window.location.href, + protocol: window.location.protocol, + origin: utils.getOrigin(), + referrer: getTopWindowReferrer(), + pbVersion: prebidVersion, + } + + Object.assign(payload, buildCommonDataProperties(), getPagePerformance()) + ajaxCall(ENDPOINTS.PAGE_DATA, () => { }, JSON.stringify(payload)) + }, 0) +} + +function sendHbConfigData() { + const conf = {} + const pbjsConfig = config.getConfig() + + Object.keys(pbjsConfig).forEach(key => { + if (key[0] !== '_') { + conf[key] = pbjsConfig[key] + } + }) + + ajaxCall(ENDPOINTS.HBCONFIG, () => { }, JSON.stringify(conf)) +} + +function getPagePerformance() { + let timing + + try { + timing = window.top.performance.timing + } catch (e) { } + + if (!timing) { + return + } + + const { navigationStart, domContentLoadedEventEnd, loadEventEnd } = timing + const domContentLoadTime = domContentLoadedEventEnd - navigationStart + const pageLoadTime = loadEventEnd - navigationStart + + return { + domContentLoadTime, + pageLoadTime, + } +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer + } catch (e) { + return '' + } +} + +function generatePageViewId(min = 10000, max = 90000) { + var randomNumber = Math.floor((Math.random() * max) + min) + var currentdate = new Date() + var currentTime = { + getDate: currentdate.getDate(), + getMonth: currentdate.getMonth(), + getFullYear: currentdate.getFullYear(), + getHours: currentdate.getHours(), + getMinutes: currentdate.getMinutes(), + getSeconds: currentdate.getSeconds(), + getMilliseconds: currentdate.getMilliseconds() + } + return ((currentTime.getDate <= 9) ? '0' + (currentTime.getDate) : (currentTime.getDate)) + '' + + (currentTime.getMonth + 1 <= 9 ? '0' + (currentTime.getMonth + 1) : (currentTime.getMonth + 1)) + '' + + currentTime.getFullYear % 100 + '' + + (currentTime.getHours <= 9 ? '0' + currentTime.getHours : currentTime.getHours) + '' + + (currentTime.getMinutes <= 9 ? '0' + currentTime.getMinutes : currentTime.getMinutes) + '' + + (currentTime.getSeconds <= 9 ? '0' + currentTime.getSeconds : currentTime.getSeconds) + '' + + (currentTime.getMilliseconds % 100 <= 9 ? '0' + (currentTime.getMilliseconds % 100) : (currentTime.getMilliseconds % 100)) + '' + + (randomNumber) +} + +function ajaxCall(endpoint, callback, data, options = {}) { + if (data) { + options.contentType = 'application/json' + } + + return ajax(endpoint, callback, data, options) +} + +ooloAdapter.originEnableAnalytics = ooloAdapter.enableAnalytics +ooloAdapter.enableAnalytics = function (config) { + ooloAdapter.originEnableAnalytics(config) + initOptions = config ? config.options : {} + + if (!initOptions.pid) { + utils.logError(buildLogMessage('enableAnalytics missing config object with "pid"')) + return + } + + getServerConfig() + sendHbConfigData() + + if (document.readyState === 'complete') { + sendPage() + } else { + window.addEventListener('load', sendPage) + } + + utils.logInfo(buildLogMessage('enabled analytics adapter'), config) + ooloAdapter.enableAnalytics = function () { + utils.logInfo(buildLogMessage('Analytics adapter already enabled..')) + } +} + +ooloAdapter.originDisableAnalytics = ooloAdapter.disableAnalytics +ooloAdapter.disableAnalytics = function () { + auctions = {} + initOptions = {} + ooloAdapter.originDisableAnalytics() +} + +adapterManager.registerAnalyticsAdapter({ + adapter: ooloAdapter, + code: ADAPTER_CODE +}) + +// export for testing +export { + buildAuctionData, + generatePageViewId +} + +export default ooloAdapter diff --git a/modules/ooloAnalyticsAdapter.md b/modules/ooloAnalyticsAdapter.md new file mode 100644 index 00000000000..1ffb9cbe050 --- /dev/null +++ b/modules/ooloAnalyticsAdapter.md @@ -0,0 +1,24 @@ +# Overview + +Module Name: oolo Analytics Adapter +Module Type: Analytics Adapter +Maintainer: admin@oolo.io + +# Description + +Analytics adapter for oolo. + +oolo is an anomaly detection based monitoring solution for web publishers, built by adops for adops. Its purpose is to eliminate most of the daily manual work that teams are required to do, while increasing the overall performance, due to full & faster detection of problems and opportunities. + +Contact admin@oolo.io for information. + +# Usage + +```javascript +pbjs.enableAnalytics({ + provider: 'oolo', + options: { + pid: 12345 // id provided by oolo + } +}) +``` diff --git a/test/spec/modules/ooloAnalyticsAdapter_spec.js b/test/spec/modules/ooloAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..f82f7856fb2 --- /dev/null +++ b/test/spec/modules/ooloAnalyticsAdapter_spec.js @@ -0,0 +1,793 @@ +import ooloAnalytics, { PAGEVIEW_ID } from 'modules/ooloAnalyticsAdapter.js'; +import {expect} from 'chai'; +import {server} from 'test/mocks/xhr.js'; +import constants from 'src/constants.json' +import events from 'src/events' +import { config } from 'src/config'; +import { buildAuctionData, generatePageViewId } from 'modules/ooloAnalyticsAdapter'; + +const auctionId = '0ea14159-2058-4b87-a966-9d7652176a56'; +const auctionStart = 1598513385415 +const timeout = 3000 +const adUnit1 = 'top_1'; +const adUnit2 = 'top_2'; +const bidId1 = '392b5a6b05d648' +const bidId2 = '392b5a6b05d649' +const bidId3 = '392b5a6b05d650' + +const auctionInit = { + timestamp: auctionStart, + auctionId: auctionId, + timeout: timeout, + auctionStart, + adUnits: [{ + code: adUnit1, + transactionId: 'abalksdkjfh-12sade' + }, { + code: adUnit2, + transactionId: 'abalksdkjfh-12sadf' + }] +}; + +const bidRequested = { + auctionId, + bidderCode: 'appnexus', + bidderRequestId: '2946b569352ef2', + start: 1598513405254, + bids: [ + { + auctionId, + bidId: bidId1, + bidderRequestId: '2946b569352ef2', + bidder: 'appnexus', + adUnitCode: adUnit1, + sizes: [[728, 90], [970, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 90]], + }, + }, + params: { + placementId: '4799418', + }, + schain: {} + }, + { + auctionId, + bidId: bidId2, + bidderRequestId: '2946b569352ef3', + bidder: 'rubicon', + adUnitCode: adUnit2, + sizes: [[728, 90], [970, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 90]], + }, + }, + params: { + placementId: '4799418', + }, + schain: {} + }, + { + auctionId, + bidId: bidId3, + bidderRequestId: '2946b569352ef4', + bidder: 'ix', + adUnitCode: adUnit2, + sizes: [[728, 90], [970, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 90]], + }, + }, + params: { + placementId: '4799418', + }, + schain: {} + }, + ], +} + +const noBid = { + auctionId, + adUnitCode: adUnit2, + bidId: bidId2, + requestId: bidId2 +} + +const bidResponse = { + auctionId, + bidderCode: 'appnexus', + mediaType: 'banner', + width: 0, + height: 0, + statusMessage: 'Bid available', + adId: '222bb26f9e8bd', + cpm: 0.112256, + responseTimestamp: 1598513485254, + requestTimestamp: 1462919238936, + bidder: 'appnexus', + adUnitCode: adUnit1, + timeToRespond: 401, + pbLg: '0.00', + pbMg: '0.10', + pbHg: '0.11', + pbAg: '0.10', + size: '0x0', + requestId: bidId1, + creativeId: '123456', + adserverTargeting: { + hb_bidder: 'appnexus', + hb_adid: '222bb26f9e8bd', + hb_pb: '10.00', + hb_size: '0x0', + foobar: '0x0', + }, + netRevenue: true, + currency: 'USD', + ttl: 300, +} + +const auctionEnd = { + auctionId: auctionId +}; + +const bidTimeout = [ + { + adUnitCode: adUnit2, + auctionId: auctionId, + bidId: bidId3, + bidder: 'ix', + timeout: timeout + } +]; + +const bidWon = { + auctionId, + adUnitCode: adUnit1, + bidId: bidId1, + cpm: 0.5 +} + +function simulateAuction () { + events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); + events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(constants.EVENTS.NO_BID, noBid); + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(constants.EVENTS.AUCTION_END, auctionEnd); +} + +describe('oolo Prebid Analytic', () => { + let clock + + beforeEach(() => { + sinon.stub(events, 'getEvents').returns([]); + clock = sinon.useFakeTimers() + }); + + afterEach(() => { + ooloAnalytics.disableAnalytics(); + events.getEvents.restore() + clock.restore(); + }) + + describe('enableAnalytics init options', () => { + it('should not enable analytics if invalid config', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: undefined + } + }) + + expect(server.requests).to.have.length(0) + }) + + it('should send prebid config to the server', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + const conf = {} + const pbjsConfig = config.getConfig() + + Object.keys(pbjsConfig).forEach(key => { + if (key[0] !== '_') { + conf[key] = pbjsConfig[key] + } + }) + + expect(server.requests[1].url).to.contain('/hbconf') + expect(JSON.parse(server.requests[1].requestBody)).to.deep.equal(conf) + }) + + it('should request server config and send page data', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + expect(server.requests[0].url).to.contain('?pid=123') + + const pageData = JSON.parse(server.requests[2].requestBody) + + expect(pageData).to.have.property('timestamp') + expect(pageData).to.have.property('screenWidth') + expect(pageData).to.have.property('screenHeight') + expect(pageData).to.have.property('url') + expect(pageData).to.have.property('protocol') + expect(pageData).to.have.property('origin') + expect(pageData).to.have.property('referrer') + expect(pageData).to.have.property('pbVersion') + expect(pageData).to.have.property('pvid') + expect(pageData).to.have.property('pid') + expect(pageData).to.have.property('pbModuleVersion') + expect(pageData).to.have.property('domContentLoadTime') + expect(pageData).to.have.property('pageLoadTime') + }) + }) + + describe('data handling and sending events', () => { + it('should send an "auction" event to the server', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(500) + + simulateAuction() + + expect(server.requests.length).to.equal(3) + clock.tick(2000) + expect(server.requests.length).to.equal(4) + + const request = JSON.parse(server.requests[3].requestBody); + + expect(request).to.include({ + eventType: 'auction', + pid: 123, + auctionId, + auctionStart, + auctionEnd: 0, + timeout, + }) + expect(request.pvid).to.be.a('number') + expect(request.adUnits).to.have.length(2) + + const auctionAdUnit1 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit1)[0] + const auctionAdUnit2 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit2)[0] + + expect(auctionAdUnit1.auctionId).to.equal(auctionId) + expect(auctionAdUnit1.bids).to.be.an('array') + expect(auctionAdUnit1.bids).to.have.length(1) + + // bid response + expect(auctionAdUnit1.bids[0].bidId).to.equal(bidId1) + expect(auctionAdUnit1.bids[0].bst).to.equal('bidReceived') + expect(auctionAdUnit1.bids[0].bidder).to.equal('appnexus') + expect(auctionAdUnit1.bids[0].cpm).to.equal(0.112256) + expect(auctionAdUnit1.bids[0].cur).to.equal('USD') + expect(auctionAdUnit1.bids[0].s).to.equal(bidRequested.start) + expect(auctionAdUnit1.bids[0].e).to.equal(bidResponse.responseTimestamp) + expect(auctionAdUnit1.bids[0].rs).to.equal(bidRequested.start - auctionStart) + expect(auctionAdUnit1.bids[0].re).to.equal(bidResponse.responseTimestamp - auctionStart) + expect(auctionAdUnit1.bids[0].h).to.equal(0) + expect(auctionAdUnit1.bids[0].w).to.equal(0) + expect(auctionAdUnit1.bids[0].mt).to.equal('banner') + expect(auctionAdUnit1.bids[0].nrv).to.equal(true) + expect(auctionAdUnit1.bids[0].params).to.have.keys('placementId') + expect(auctionAdUnit1.bids[0].size).to.equal('0x0') + expect(auctionAdUnit1.bids[0].crId).to.equal('123456') + expect(auctionAdUnit1.bids[0].ttl).to.equal(bidResponse.ttl) + expect(auctionAdUnit1.bids[0].ttr).to.equal(bidResponse.timeToRespond) + + expect(auctionAdUnit2.auctionId).to.equal(auctionId) + expect(auctionAdUnit2.bids).to.be.an('array') + expect(auctionAdUnit2.bids).to.have.length(2) + + // no bid + expect(auctionAdUnit2.bids[0].bidId).to.equal(bidId2) + expect(auctionAdUnit2.bids[0].bst).to.equal('noBid') + expect(auctionAdUnit2.bids[0].bidder).to.equal('rubicon') + expect(auctionAdUnit2.bids[0].s).to.be.a('number') + expect(auctionAdUnit2.bids[0].e).to.be.a('number') + expect(auctionAdUnit2.bids[0].params).to.have.keys('placementId') + + // timeout + expect(auctionAdUnit2.bids[1].bidId).to.equal(bidId3) + expect(auctionAdUnit2.bids[1].bst).to.equal('bidTimedOut') + expect(auctionAdUnit2.bids[1].bidder).to.equal('ix') + expect(auctionAdUnit2.bids[1].s).to.be.a('number') + expect(auctionAdUnit2.bids[1].e).to.be.a('undefined') + }) + + it('should push events to a queue and process them once server configuration returns', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); + events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // configuration returned in an arbitrary moment + server.requests[0].respond(500) + + events.emit(constants.EVENTS.NO_BID, noBid); + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody); + + expect(request).to.include({ + eventType: 'auction', + pid: 123, + auctionId, + auctionStart, + auctionEnd: 0, + timeout, + }) + expect(request.pvid).to.be.a('number') + expect(request.adUnits).to.have.length(2) + + const auctionAdUnit1 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit1)[0] + const auctionAdUnit2 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit2)[0] + + expect(auctionAdUnit1.auctionId).to.equal(auctionId) + expect(auctionAdUnit1.bids).to.be.an('array') + expect(auctionAdUnit1.bids).to.have.length(1) + + // bid response + expect(auctionAdUnit1.bids[0].bidId).to.equal(bidId1) + expect(auctionAdUnit1.bids[0].bst).to.equal('bidReceived') + expect(auctionAdUnit1.bids[0].bidder).to.equal('appnexus') + expect(auctionAdUnit1.bids[0].cpm).to.equal(0.112256) + expect(auctionAdUnit1.bids[0].cur).to.equal('USD') + expect(auctionAdUnit1.bids[0].s).to.equal(bidRequested.start) + expect(auctionAdUnit1.bids[0].e).to.equal(bidResponse.responseTimestamp) + expect(auctionAdUnit1.bids[0].rs).to.equal(bidRequested.start - auctionStart) + expect(auctionAdUnit1.bids[0].re).to.equal(bidResponse.responseTimestamp - auctionStart) + expect(auctionAdUnit1.bids[0].h).to.equal(0) + expect(auctionAdUnit1.bids[0].w).to.equal(0) + expect(auctionAdUnit1.bids[0].mt).to.equal('banner') + expect(auctionAdUnit1.bids[0].nrv).to.equal(true) + expect(auctionAdUnit1.bids[0].params).to.have.keys('placementId') + expect(auctionAdUnit1.bids[0].size).to.equal('0x0') + expect(auctionAdUnit1.bids[0].crId).to.equal('123456') + expect(auctionAdUnit1.bids[0].ttl).to.equal(bidResponse.ttl) + expect(auctionAdUnit1.bids[0].ttr).to.equal(bidResponse.timeToRespond) + + expect(auctionAdUnit2.auctionId).to.equal(auctionId) + expect(auctionAdUnit2.bids).to.be.an('array') + expect(auctionAdUnit2.bids).to.have.length(2) + + // no bid + expect(auctionAdUnit2.bids[0].bidId).to.equal(bidId2) + expect(auctionAdUnit2.bids[0].bst).to.equal('noBid') + expect(auctionAdUnit2.bids[0].bidder).to.equal('rubicon') + expect(auctionAdUnit2.bids[0].s).to.be.a('number') + expect(auctionAdUnit2.bids[0].e).to.be.a('number') + expect(auctionAdUnit2.bids[0].params).to.have.keys('placementId') + + // timeout + expect(auctionAdUnit2.bids[1].bidId).to.equal(bidId3) + expect(auctionAdUnit2.bids[1].bst).to.equal('bidTimedOut') + expect(auctionAdUnit2.bids[1].bidder).to.equal('ix') + expect(auctionAdUnit2.bids[1].s).to.be.a('number') + expect(auctionAdUnit2.bids[1].e).to.be.a('undefined') + }) + + it('should send "auction" event without all the fields that were set to undefined', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(500) + + simulateAuction() + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody); + + const auctionAdUnit1 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit1)[0] + const auctionAdUnit2 = request.adUnits.filter(adUnit => adUnit.adunid === adUnit2)[0] + + expect(auctionAdUnit1.code).to.equal(undefined) + expect(auctionAdUnit1.transactionId).to.equal(undefined) + expect(auctionAdUnit1.adUnitCode).to.equal(undefined) + expect(auctionAdUnit1.bids[0].auctionStart).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bids).to.equal(undefined) + expect(auctionAdUnit1.bids[0].refererInfo).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bidRequestsCount).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bidderRequestId).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bidderRequestsCount).to.equal(undefined) + expect(auctionAdUnit1.bids[0].bidderWinsCount).to.equal(undefined) + expect(auctionAdUnit1.bids[0].schain).to.equal(undefined) + expect(auctionAdUnit1.bids[0].src).to.equal(undefined) + expect(auctionAdUnit1.bids[0].transactionId).to.equal(undefined) + + // no bid + expect(auctionAdUnit2.bids[0].schain).to.equal(undefined) + expect(auctionAdUnit2.bids[0].src).to.equal(undefined) + expect(auctionAdUnit2.bids[0].transactionId).to.equal(undefined) + }) + + it('should mark bid winner and send to the server along with the auction data', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(500) + simulateAuction() + events.emit(constants.EVENTS.BID_WON, bidWon); + clock.tick(1500) + + // no bidWon + expect(server.requests).to.have.length(4) + + const request = JSON.parse(server.requests[3].requestBody); + expect(request.adUnits[0].bids[0].isW).to.equal(true) + }) + + it('should take BID_WON_TIMEOUT from server config if exists', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': {}, + 'BID_WON_TIMEOUT': 500 + })) + + simulateAuction() + events.emit(constants.EVENTS.BID_WON, bidWon); + clock.tick(499) + + // no auction data + expect(server.requests).to.have.length(3) + + clock.tick(1) + + // auction data + expect(server.requests).to.have.length(4) + const request = JSON.parse(server.requests[3].requestBody); + expect(request.adUnits[0].bids[0].isW).to.equal(true) + }) + + it('should send a "bidWon" event to the server', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(500) + simulateAuction() + clock.tick(1500) + events.emit(constants.EVENTS.BID_WON, bidWon); + + expect(server.requests).to.have.length(5) + + const request = JSON.parse(server.requests[4].requestBody); + + expect(request.eventType).to.equal('bidWon') + expect(request.auctionId).to.equal(auctionId) + expect(request.adunid).to.equal(adUnit1) + expect(request.bid.bst).to.equal('bidWon') + expect(request.bid.cur).to.equal('USD') + expect(request.bid.cpm).to.equal(0.5) + }) + + it('should sent adRenderFailed to the server', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(500) + simulateAuction() + clock.tick(1500) + events.emit(constants.EVENTS.AD_RENDER_FAILED, { bidId: 'abcdef', reason: 'exception' }); + + expect(server.requests).to.have.length(5) + + const request = JSON.parse(server.requests[4].requestBody); + + expect(request.eventType).to.equal('adRenderFailed') + expect(request.pvid).to.equal(PAGEVIEW_ID) + expect(request.bidId).to.equal('abcdef') + expect(request.reason).to.equal('exception') + }) + + it('should pick fields according to server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'bidRequested': { + 'sendRaw': 0, + 'pickFields': ['transactionId'] + }, + 'noBid': { + 'sendRaw': 0, + 'pickFields': ['src'] + }, + 'bidResponse': { + 'sendRaw': 0, + 'pickFields': ['adUrl', 'statusMessage'] + }, + 'auctionEnd': { + 'sendRaw': 0, + 'pickFields': ['winningBids'] + }, + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit }); + events.emit(constants.EVENTS.BID_REQUESTED, { ...bidRequested, bids: bidRequested.bids.map(b => { b.transactionId = '123'; return b }) }); + events.emit(constants.EVENTS.NO_BID, { ...noBid, src: 'client' }); + events.emit(constants.EVENTS.BID_RESPONSE, { ...bidResponse, adUrl: '...' }); + events.emit(constants.EVENTS.AUCTION_END, { ...auctionEnd, winningBids: [] }); + events.emit(constants.EVENTS.BID_WON, { ...bidWon, statusMessage: 'msg2' }); + + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody) + + expect(request.adUnits[0].bids[0].transactionId).to.equal('123') + expect(request.adUnits[0].bids[0].adUrl).to.equal('...') + expect(request.adUnits[0].bids[0].statusMessage).to.equal('msg2') + expect(request.adUnits[1].bids[0].src).to.equal('client') + }) + + it('should omit fields according to server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 0, + 'omitFields': ['custom_1'] + }, + 'bidResponse': { + 'sendRaw': 0, + 'omitFields': ['custom_2', 'custom_4', 'custom_5'] + } + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit, custom_1: true }); + events.emit(constants.EVENTS.BID_REQUESTED, { ...bidRequested, bids: bidRequested.bids.map(b => { b.custom_2 = true; return b }) }); + events.emit(constants.EVENTS.NO_BID, { ...noBid, custom_3: true }); + events.emit(constants.EVENTS.BID_RESPONSE, { ...bidResponse, custom_4: true }); + events.emit(constants.EVENTS.AUCTION_END, { ...auctionEnd }); + events.emit(constants.EVENTS.BID_WON, { ...bidWon, custom_5: true }); + + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody) + + expect(request.custom_1).to.equal(undefined) + expect(request.custom_6).to.equal(undefined) + expect(request.adUnits[0].bids[0].custom_2).to.equal(undefined) + expect(request.adUnits[0].bids[0].custom_4).to.equal(undefined) + expect(request.adUnits[0].bids[0].custom_5).to.equal(undefined) + expect(request.adUnits[0].bids[0].custom_7).to.equal(undefined) + }) + + it('should omit fields from raw data according to server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 1, + 'omitRawFields': ['custom_1'] + }, + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit, custom_1: true }); + + clock.tick(1500) + + const request = JSON.parse(server.requests[3].requestBody) + + expect(request.eventType).to.equal('auctionInit') + expect(request.custom_1).to.equal(undefined) + }) + + it('should send raw data to custom endpoint if exists in server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 1, + 'endpoint': 'https://pbjs.com' + }, + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit }); + + expect(server.requests[3].url).to.equal('https://pbjs.com') + }) + + it('should send raw events based on server configuration', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + clock.next() + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 0, + }, + 'bidRequested': { + 'sendRaw': 1, + } + } + })) + + events.emit(constants.EVENTS.AUCTION_INIT, auctionInit) + events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); + + const request = JSON.parse(server.requests[3].requestBody) + + expect(request).to.deep.equal({ + eventType: 'bidRequested', + pid: 123, + pvid: PAGEVIEW_ID, + pbModuleVersion: '1.0.0', + ...bidRequested + }) + }) + + it('should queue events and raw events until server configuration resolves', () => { + ooloAnalytics.enableAnalytics({ + provider: 'oolo', + options: { + pid: 123 + } + }) + + simulateAuction() + clock.tick(1500) + + expect(server.requests).to.have.length(3) + + server.requests[0].respond(200, {}, JSON.stringify({ + 'events': { + 'auctionInit': { + 'sendRaw': 1, + }, + 'bidRequested': { + 'sendRaw': 1, + } + } + })) + + expect(server.requests).to.have.length(5) + expect(JSON.parse(server.requests[3].requestBody).eventType).to.equal('auctionInit') + expect(JSON.parse(server.requests[4].requestBody).eventType).to.equal('bidRequested') + }) + }); + + describe('buildAuctionData', () => { + let auction = { + auctionId, + auctionStart, + auctionEnd, + adUnitCodes: ['mid_1'], + auctionStatus: 'running', + bidderRequests: [], + bidsReceived: [], + noBids: [], + winningBids: [], + timestamp: 1234567, + config: {}, + adUnits: { + mid_1: { + adUnitCode: 'mid_1', + code: 'mid_1', + transactionId: '123dsafasdf', + bids: { + [bidId1]: { + adUnitCode: 'mid_1', + cpm: 0.5 + } + } + } + } + } + + it('should turn adUnits and bids objects into arrays', () => { + const auctionData = buildAuctionData(auction, []) + + expect(auctionData.adUnits).to.be.an('array') + expect(auctionData.adUnits[0].bids).to.be.an('array') + }) + + it('should remove fields from the auction', () => { + const auctionData = buildAuctionData(auction, []) + const auctionFields = Object.keys(auctionData) + const adUnitFields = Object.keys(auctionData.adUnits[0]) + + expect(auctionFields).not.to.contain('adUnitCodes') + expect(auctionFields).not.to.contain('auctionStatus') + expect(auctionFields).not.to.contain('bidderRequests') + expect(auctionFields).not.to.contain('bidsReceived') + expect(auctionFields).not.to.contain('noBids') + expect(auctionFields).not.to.contain('winningBids') + expect(auctionFields).not.to.contain('timestamp') + expect(auctionFields).not.to.contain('config') + + expect(adUnitFields).not.to.contain('adUnitCoe') + expect(adUnitFields).not.to.contain('code') + expect(adUnitFields).not.to.contain('transactionId') + }) + }) + + describe('generatePageViewId', () => { + it('should generate a 19 digits number', () => { + expect(generatePageViewId()).length(19) + }) + }) +}); From 4662f060b42c55106d2099f25a4e5a480705b43e Mon Sep 17 00:00:00 2001 From: Paul Yang Date: Thu, 15 Oct 2020 09:12:34 -0700 Subject: [PATCH 077/190] Add sharedid support to pubcommon (#5850) * Add sharedid support to pubcommon * Add sharedid support to pubcommon - fix typos * Add sharedid support to pubcommon - delete sharedid cookie when opt-out * Add sharedid support to pubcommon - disable sharedid by default --- modules/pubCommonIdSystem.js | 235 ++++++++++++++++++++--- test/spec/modules/userId_spec.js | 311 ++++++++++++++++++++++++++++++- 2 files changed, 517 insertions(+), 29 deletions(-) diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index cb60a1b559c..51b4335fe60 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -8,13 +8,150 @@ import * as utils from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; +import {ajax} from '../src/ajax.js'; const PUB_COMMON_ID = 'PublisherCommonId'; - const MODULE_NAME = 'pubCommonId'; +const COOKIE = 'cookie'; +const LOCAL_STORAGE = 'html5'; +const SHAREDID_OPT_OUT_VALUE = '00000000000000000000000000'; +const SHAREDID_URL = 'https://id.sharedid.org/id'; +const SHAREDID_SUFFIX = '_sharedid'; +const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; +const SHAREDID_DEFAULT_STATE = false; + const storage = getStorageManager(null, 'pubCommonId'); +/** + * Store sharedid in either cookie or local storage + * @param {Object} config Need config.storage object to derive key, expiry time, and storage type. + * @param {string} value Shareid value to store + */ + +function storeData(config, value) { + try { + if (value) { + const key = config.storage.name + SHAREDID_SUFFIX; + const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString(); + + if (config.storage.type === COOKIE) { + if (storage.cookiesAreEnabled()) { + storage.setCookie(key, value, expiresStr, 'LAX', COOKIE_DOMAIN); + } + } else if (config.storage.type === LOCAL_STORAGE) { + if (storage.hasLocalStorage()) { + storage.setDataInLocalStorage(`${key}_exp`, expiresStr); + storage.setDataInLocalStorage(key, value); + } + } + } + } catch (error) { + utils.logError(error); + } +} + +/** + * Read sharedid from cookie or local storage + * @param config Need config.storage to derive key and storage type + * @return {string} + */ +function readData(config) { + try { + const key = config.storage.name + SHAREDID_SUFFIX; + if (config.storage.type === COOKIE) { + if (storage.cookiesAreEnabled()) { + return storage.getCookie(key); + } + } else if (config.storage.type === LOCAL_STORAGE) { + if (storage.hasLocalStorage()) { + const expValue = storage.getDataFromLocalStorage(`${key}_exp`); + if (!expValue) { + return storage.getDataFromLocalStorage(key); + } else if ((new Date(expValue)).getTime() - Date.now() > 0) { + return storage.getDataFromLocalStorage(key) + } + } + } + } catch (error) { + utils.logError(error); + } +} + +/** + * Delete sharedid from cookie or local storage + * @param config Need config.storage to derive key and storage type + */ +function delData(config) { + try { + const key = config.storage.name + SHAREDID_SUFFIX; + if (config.storage.type === COOKIE) { + if (storage.cookiesAreEnabled()) { + storage.setCookie(key, '', EXPIRED_COOKIE_DATE); + } + } else if (config.storage.type === LOCAL_STORAGE) { + storage.removeDataFromLocalStorage(`${key}_exp`); + storage.removeDataFromLocalStorage(key); + } + } catch (error) { + utils.logError(error); + } +} + +/** + * setup success and error handler for sharedid callback thru ajax + * @param {string} pubcid Current pubcommon id + * @param {function} callback userId module callback. + * @param {Object} config Need config.storage to derive sharedid storage params + * @return {{success: success, error: error}} + */ + +function handleResponse(pubcid, callback, config) { + return { + success: function (responseBody) { + if (responseBody) { + try { + let responseObj = JSON.parse(responseBody); + utils.logInfo('PubCommonId: Generated SharedId: ' + responseObj.sharedId); + if (responseObj.sharedId) { + if (responseObj.sharedId !== SHAREDID_OPT_OUT_VALUE) { + // Store sharedId locally + storeData(config, responseObj.sharedId); + } else { + // Delete local copy if the user has opted out + delData(config); + } + } + // Pass pubcid even though there is no change in order to trigger decode + callback(pubcid); + } catch (error) { + utils.logError(error); + } + } + }, + error: function (statusText, responseBody) { + utils.logInfo('PubCommonId: failed to get sharedid'); + } + } +} + +/** + * Wraps pixelCallback in order to call sharedid sync + * @param {string} pubcid Pubcommon id value + * @param {function|undefined} pixelCallback fires a pixel to first party server + * @param {Object} config Need config.storage to derive sharedid storage params. + * @return {function(...[*]=)} + */ + +function getIdCallback(pubcid, pixelCallback, config) { + return function (callback) { + if (typeof pixelCallback === 'function') { + pixelCallback(); + } + ajax(SHAREDID_URL, handleResponse(pubcid, callback, config), undefined, {method: 'GET', withCredentials: true}); + } +} + /** @type {Submodule} */ export const pubCommonIdSubmodule = { /** @@ -22,6 +159,7 @@ export const pubCommonIdSubmodule = { * @type {string} */ name: MODULE_NAME, + /** * Return a callback function that calls the pixelUrl with id as a query parameter * @param pixelUrl @@ -46,52 +184,93 @@ export const pubCommonIdSubmodule = { * decode the stored id value for passing to bid requests * @function * @param {string} value + * @param {SubmoduleConfig} config * @returns {{pubcid:string}} */ - decode(value) { - return { 'pubcid': value } + decode(value, config) { + const idObj = {'pubcid': value}; + const {params: {enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config; + + if (enableSharedId) { + const sharedId = readData(config); + if (sharedId) idObj['sharedid'] = {id: sharedId}; + } + + return idObj; }, /** * performs action to obtain id * @function - * @param {SubmoduleConfig} [config] + * @param {SubmoduleConfig} [config] Config object with params and storage properties + * @param {Object} consentData + * @param {string} storedId Existing pubcommon id * @returns {IdResponse} */ - getId: function ({params: {create = true, pixelUrl} = {}} = {}) { - try { - if (typeof window[PUB_COMMON_ID] === 'object') { - // If the page includes its own pubcid module, then save a copy of id. - return {id: window[PUB_COMMON_ID].getId()}; + getId: function (config = {}, consentData, storedId) { + const {params: {create = true, pixelUrl, enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config; + let newId = storedId; + if (!newId) { + try { + if (typeof window[PUB_COMMON_ID] === 'object') { + // If the page includes its own pubcid module, then save a copy of id. + newId = window[PUB_COMMON_ID].getId(); + } + } catch (e) { } - } catch (e) { - } - const newId = (create && utils.hasDeviceAccess()) ? utils.generateUUID() : undefined; - return { - id: newId, - callback: this.makeCallback(pixelUrl, newId) + if (!newId) newId = (create && utils.hasDeviceAccess()) ? utils.generateUUID() : undefined; } + + const pixelCallback = this.makeCallback(pixelUrl, newId); + const combinedCallback = enableSharedId ? getIdCallback(newId, pixelCallback, config) : pixelCallback; + + return {id: newId, callback: combinedCallback}; }, /** - * performs action to extend an id + * performs action to extend an id. There are generally two ways to extend the expiration time + * of stored id: using pixelUrl or return the id and let main user id module write it again with + * the new expiration time. + * + * PixelUrl, if defined, should point back to a first party domain endpoint. On the server + * side, there is either a plugin, or customized logic to read and write back the pubcid cookie. + * The extendId function itself should return only the callback, and not the id itself to avoid + * having the script-side overwriting server-side. This applies to both pubcid and sharedid. + * + * On the other hand, if there is no pixelUrl, then the extendId should return storedId so that + * its expiration time is updated. Sharedid, however, will have to be updated by this submodule + * separately. + * * @function * @param {SubmoduleParams} [config] * @param {Object} storedId existing id * @returns {IdResponse|undefined} */ - extendId: function({params: {extend = false, pixelUrl} = {}} = {}, storedId) { - try { - if (typeof window[PUB_COMMON_ID] === 'object') { - // If the page includes its onw pubcid module, then there is nothing to do. - return; - } - } catch (e) { - } + extendId: function(config = {}, storedId) { + const {params: {extend = false, pixelUrl, enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config; if (extend) { - // When extending, only one of response fields is needed - const callback = this.makeCallback(pixelUrl, storedId); - return callback ? {callback: callback} : {id: storedId}; + try { + if (typeof window[PUB_COMMON_ID] === 'object') { + if (enableSharedId) { + // If the page includes its own pubcid module, then there is nothing to do + // except to update sharedid's expiration time + storeData(config, readData(config)); + } + return; + } + } catch (e) { + } + + if (pixelUrl) { + const callback = this.makeCallback(pixelUrl, storedId); + return {callback: callback}; + } else { + if (enableSharedId) { + // Update with the same value to extend expiration time + storeData(config, readData(config)); + } + return {id: storedId}; + } } }, @@ -123,4 +302,6 @@ export const pubCommonIdSubmodule = { } }; +const COOKIE_DOMAIN = pubCommonIdSubmodule.domainOverride(); + submodule('userId', pubCommonIdSubmodule); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 5b1faa46979..df72e59e7e7 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -84,6 +84,12 @@ describe('User ID', function () { return cfg; } + function findEid(eids, source) { + return eids.find((eid) => { + if (eid.source === source) { return true; } + }); + } + before(function () { coreStorage.setCookie('_pubcid_optout', '', EXPIRED_COOKIE_DATE); localStorage.removeItem('_pbjs_id_optout'); @@ -825,6 +831,10 @@ describe('User ID', function () { source: 'pubcid.org', uids: [{id: 'testpubcid', atype: 1}] }); + + // verify no sharedid was added + expect(bid.userId).to.not.have.property('sharedid'); + expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.be.undefined; }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -832,6 +842,36 @@ describe('User ID', function () { }, {adUnits}); }); + it('test hook from pubcommonid html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('pubcid', 'testpubcid'); + localStorage.setItem('pubcid_exp', new Date(Date.now() + 100000).toUTCString()); + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'html5'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [{id: 'testpubcid', atype: 1}] + }); + + // verify no sharedid was added + expect(bid.userId).to.not.have.property('sharedid'); + expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.be.undefined; + }); + }); + localStorage.removeItem('pubcid'); + localStorage.removeItem('pubcid_exp'); + done(); + }, {adUnits}); + }); + it('test hook from pubcommonid config value object', function (done) { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); @@ -849,7 +889,7 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook from pubcommonid html5', function (done) { + it('test hook from UnifiedId html5', function (done) { // simulate existing browser local storage values localStorage.setItem('unifiedid_alt', JSON.stringify({'TDID': 'testunifiedid_alt'})); localStorage.setItem('unifiedid_alt_exp', ''); @@ -1644,6 +1684,192 @@ describe('User ID', function () { }, {adUnits}); }); + it('test sharedid enabled via pubcid cookie', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('pubcid_sharedid', 'testsharedid', (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + + const customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); + addConfig(customCfg, 'params', {enableSharedId: true}); + config.setConfig(customCfg); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // verify that the PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( + {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} + ); + // verify that the sharedid was also copied to bid + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid).to.deep.equal({id: 'testsharedid'}); + expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.deep.equal( + {'source': 'sharedid.org', 'uids': [{'id': 'testsharedid', 'atype': 1}]} + ); + }); + }); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); + + done(); + }, {adUnits}); + }); + + it('test sharedid disabled via pubcid cookie', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('pubcid_sharedid', 'testsharedid', (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + + // pubCommonId's support for sharedId is off by default + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // verify that the PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( + {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} + ); + // verify that the sharedid was not added to bid + expect(bid.userId).to.not.have.property('sharedid'); + expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.be.undefined; + }); + }); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); + + done(); + }, {adUnits}); + }); + + it('test sharedid enabled via pubcid html5', function (done) { + coreStorage.setDataInLocalStorage('pubcid', 'testpubcid'); + coreStorage.setDataInLocalStorage('pubcid_exp', new Date(Date.now() + 5000).toUTCString()); + coreStorage.setDataInLocalStorage('pubcid_sharedid', 'testsharedid'); + coreStorage.setDataInLocalStorage('pubcid_sharedid_exp', new Date(Date.now() + 5000).toUTCString()); + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + + const customCfg = getConfigMock(['pubCommonId', 'pubcid', 'html5']); + addConfig(customCfg, 'params', {enableSharedId: true}); + config.setConfig(customCfg); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // verify that the PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( + {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} + ); + // verify that the sharedid was also copied to bid + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid).to.deep.equal({id: 'testsharedid'}); + expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.deep.equal( + {'source': 'sharedid.org', 'uids': [{'id': 'testsharedid', 'atype': 1}]} + ); + }); + }); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); + + coreStorage.removeDataFromLocalStorage('pubcid'); + coreStorage.removeDataFromLocalStorage('pubcid_exp'); + coreStorage.removeDataFromLocalStorage('pubcid_sharedid'); + coreStorage.removeDataFromLocalStorage('pubcid_sharedid_exp'); + done(); + }, {adUnits}); + }); + + it('test expired sharedid via pubcid html5', function (done) { + coreStorage.setDataInLocalStorage('pubcid', 'testpubcid'); + coreStorage.setDataInLocalStorage('pubcid_exp', new Date(Date.now() + 5000).toUTCString()); + + // set sharedid to expired already + coreStorage.setDataInLocalStorage('pubcid_sharedid', 'testsharedid'); + coreStorage.setDataInLocalStorage('pubcid_sharedid_exp', new Date(Date.now() - 100).toUTCString()); + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + + const customCfg = getConfigMock(['pubCommonId', 'pubcid', 'html5']); + addConfig(customCfg, 'params', {enableSharedId: true}); + config.setConfig(customCfg); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // verify that the PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( + {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} + ); + // verify that the sharedid was not added + expect(bid.userId).to.not.have.property('sharedid'); + expect(findEid(bid.userIdAsEids, 'sharedid.org')).to.be.undefined; + }); + }); + + coreStorage.removeDataFromLocalStorage('pubcid'); + coreStorage.removeDataFromLocalStorage('pubcid_exp'); + coreStorage.removeDataFromLocalStorage('pubcid_sharedid'); + coreStorage.removeDataFromLocalStorage('pubcid_sharedid_exp'); + done(); + }, {adUnits}); + }); + + it('test pubcid coexisting with sharedid', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('pubcid_sharedid', 'test111', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test222', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); + + // When both pubcommon and sharedid are configured, pubcommon are invoked first due to + // module loading order. This mean the output from the primary sharedid module will overwrite + // the one in pubcommon. + + setSubmoduleRegistry([pubCommonIdSubmodule, sharedIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], + ['sharedId', 'sharedid', 'cookie'], + )); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // verify that the PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(findEid(bid.userIdAsEids, 'pubcid.org')).to.deep.equal( + {'source': 'pubcid.org', 'uids': [{'id': 'testpubcid', 'atype': 1}]} + ); + // verify that the sharedid was also copied to bid + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid.id).to.equal('test222'); + expect(findEid(bid.userIdAsEids, 'sharedid.org').uids[0].id).to.equal('test222'); + }); + }); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('sharedid', '', 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()); @@ -1769,6 +1995,7 @@ describe('User ID', function () { sinon.stub(events, 'getEvents').returns([]); sinon.stub(utils, 'triggerPixel'); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); }); @@ -1777,6 +2004,7 @@ describe('User ID', function () { events.getEvents.restore(); utils.triggerPixel.restore(); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); }); @@ -1784,7 +2012,7 @@ describe('User ID', function () { it('pubcid callback with url', function () { let adUnits = [getAdUnitMock()]; let innerAdUnits; - let customCfg = getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie']); + let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url'}); setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); @@ -1834,6 +2062,85 @@ describe('User ID', function () { events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); expect(server.requests[0].url).to.equal('https://match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); }); + + it('verify sharedid callback via pubcid when enabled', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); + customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url', enableSharedId: true}); + + server.respondWith('https://id.sharedid.org/id', function(xhr) { + xhr.respond(200, {}, '{"sharedId":"testsharedid"}'); + }); + server.respondImmediately = true; + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); + + expect(utils.triggerPixel.called).to.be.false; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + + expect(server.requests[0].url).to.equal('https://id.sharedid.org/id'); + expect(coreStorage.getCookie('pubcid_sharedid')).to.equal('testsharedid'); + }); + + it('verify no sharedid callback via pubcid when disabled', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); + customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url'}); + + server.respondWith('https://id.sharedid.org/id', function(xhr) { + xhr.respond(200, {}, '{"sharedId":"testsharedid"}'); + }); + server.respondImmediately = true; + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); + + expect(utils.triggerPixel.called).to.be.false; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + + expect(server.requests).to.have.lengthOf(0); + expect(coreStorage.getCookie('pubcid_sharedid')).to.be.null; + }); + + it('verify sharedid optout via pubcid when enabled', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); + customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url', enableSharedId: true}); + coreStorage.setCookie('pubcid_sharedid', 'testsharedid', (new Date(Date.now() + 5000).toUTCString())); + + server.respondWith('https://id.sharedid.org/id', function(xhr) { + xhr.respond(200, {}, '{"sharedId":"00000000000000000000000000"}'); + }); + server.respondImmediately = true; + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}); + + expect(utils.triggerPixel.called).to.be.false; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + + expect(server.requests[0].url).to.equal('https://id.sharedid.org/id'); + expect(coreStorage.getCookie('pubcid_sharedid')).to.be.null; + }); }); describe('Set cookie behavior', function () { From c168de611cff4c7fd7d9f2559677d71734b1dd12 Mon Sep 17 00:00:00 2001 From: pm-shashank-jain Date: Fri, 16 Oct 2020 12:54:10 +0530 Subject: [PATCH 078/190] Fix Typo --- modules/pubmaticBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 575be3bd836..0f3e2307b01 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -883,7 +883,7 @@ export const spec = { utils.logError(`${LOG_WARN_PREFIX}: no context specified in bid. Rejecting bid: `, bid); return false; } - if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU && !bid.hasOwnProperty('renderer'))) { + if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU) && !bid.hasOwnProperty('renderer')) { utils.logError(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting bid: `, bid); return false; } From aaa7dc98b9a6de4cf06f533089b812340371b47e Mon Sep 17 00:00:00 2001 From: bretg Date: Fri, 16 Oct 2020 07:29:43 -0400 Subject: [PATCH 079/190] PR Review process tweaks (#5862) Incorporating feedback --- PR_REVIEW.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 1dd8fbcec0d..a8b68c7ab45 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -53,6 +53,8 @@ Documentation they're supposed to be following is https://docs.prebid.org/dev-do No additional steps above the general review process and making sure it conforms to the [Module Rules](https://docs.prebid.org/dev-docs/module-rules.html). +Make sure there's a docs pull request + ### Reviewing a New or Updated User ID Sub-Module Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/modules/userId.html#id-providers @@ -79,6 +81,7 @@ Follow steps above for general review process. In addition: - in the eids.js file, the source should be the actual domain of the provider, not a made up domain. - in the eids.js file, the key in the array should be the same value as the key in the decode function - make sure all supported config params align in the submodule js file and the docs / examples +- make sure there's a docs pull request ### Reviewing a New or Updated Real-Time-Data Sub-Module Documentation they're supposed to be following is https://docs.prebid.org/dev-docs/add-rtd-submodule.html @@ -86,9 +89,9 @@ Documentation they're supposed to be following is https://docs.prebid.org/dev-do Follow steps above for general review process. In addition: - The RTD Provider must include a `providerRtdProvider.md` file. This file must have example parameters and document a sense of what to expect: what should change in the bidrequest, or what targeting data should be added? - Try running the new sub-module and confirm the provided test parameters. -- Make sure the sub-module is "phoning home" as early as possible, but not more often than needed. +- Make sure the sub-module is making HTTP requests as early as possible, but not more often than needed. - Consider whether the kind of data the module is obtaining could have privacy implications. If so, make sure they're utilizing the `consent` data passed to them. - +- make sure there's a docs pull request ## Ticket Coordinator From 368b6023edaa59ba2ec1ba253be8e841ee17d58a Mon Sep 17 00:00:00 2001 From: John Rosendahl Date: Fri, 16 Oct 2020 07:04:59 -0600 Subject: [PATCH 080/190] Added basic support for ID Module (#5835) Co-authored-by: John Rosendahl --- modules/sovrnBidAdapter.js | 37 +++++++++++++---------- test/spec/modules/sovrnBidAdapter_spec.js | 23 +++++++++----- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index f3260668b74..935b0ceb489 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -1,10 +1,12 @@ import * as utils from '../src/utils.js' import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER } from '../src/mediaTypes.js' +import { createEidsArray } from './userId/eids.js'; export const spec = { code: 'sovrn', supportedMediaTypes: [BANNER], + gvlid: 13, /** * Check if the bid is a valid zone ID in either number or string form @@ -25,11 +27,21 @@ export const spec = { let sovrnImps = []; let iv; let schain; - let unifiedID; + let eids; + let tpid = [] + let criteoId; utils._each(bidReqs, function (bid) { - if (!unifiedID) { - unifiedID = utils.deepAccess(bid, 'userId.tdid'); + if (!eids && bid.userId) { + eids = createEidsArray(bid.userId) + eids.forEach(function (id) { + if (id.uids && id.uids[0]) { + if (id.source === 'criteo.com') { + criteoId = id.uids[0].id + } + tpid.push({source: id.source, uid: id.uids[0].id}) + } + }) } if (bid.schain) { @@ -84,19 +96,12 @@ export const spec = { utils.deepSetValue(sovrnBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - if (unifiedID) { - const idArray = [{ - source: 'adserver.org', - uids: [ - { - id: unifiedID, - ext: { - rtiPartner: 'TDID' - } - } - ] - }] - utils.deepSetValue(sovrnBidReq, 'user.ext.eids', idArray) + if (eids) { + utils.deepSetValue(sovrnBidReq, 'user.ext.eids', eids) + utils.deepSetValue(sovrnBidReq, 'user.ext.tpid', tpid) + if (criteoId) { + utils.deepSetValue(sovrnBidReq, 'user.ext.prebid_criteoid', criteoId) + } } let url = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 54ccff870eb..321fed40d83 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -239,8 +239,8 @@ describe('sovrnBidAdapter', function() { expect(data.source.ext.schain.nodes.length).to.equal(1) }); - it('should add the unifiedID if present', function() { - const digitrustRequests = [{ + it('should add ids to the bid request', function() { + const criteoIdRequest = [{ 'bidder': 'sovrn', 'params': { 'tagid': 403370 @@ -254,6 +254,7 @@ describe('sovrnBidAdapter', function() { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', 'userId': { + 'criteoId': 'A_CRITEO_ID', 'tdid': 'SOMESORTOFID', } }].concat(bidRequests); @@ -263,12 +264,20 @@ describe('sovrnBidAdapter', function() { } }; - const data = JSON.parse(spec.buildRequests(digitrustRequests, bidderRequest).data); - expect(data.user.ext.eids[0].source).to.equal('adserver.org') - expect(data.user.ext.eids[0].uids[0].id).to.equal('SOMESORTOFID') - expect(data.user.ext.eids[0].uids[0].ext.rtiPartner).to.equal('TDID') - }) + const data = JSON.parse(spec.buildRequests(criteoIdRequest, bidderRequest).data); + expect(data.user.ext.eids[0].source).to.equal('criteo.com') + expect(data.user.ext.eids[0].uids[0].id).to.equal('A_CRITEO_ID') + expect(data.user.ext.eids[0].uids[0].atype).to.equal(1) + expect(data.user.ext.eids[1].source).to.equal('adserver.org') + expect(data.user.ext.eids[1].uids[0].id).to.equal('SOMESORTOFID') + expect(data.user.ext.eids[1].uids[0].ext.rtiPartner).to.equal('TDID') + expect(data.user.ext.eids[1].uids[0].atype).to.equal(1) + expect(data.user.ext.tpid[0].source).to.equal('criteo.com') + expect(data.user.ext.tpid[0].uid).to.equal('A_CRITEO_ID') + expect(data.user.ext.prebid_criteoid).to.equal('A_CRITEO_ID') + }); }); + describe('interpretResponse', function () { let response; beforeEach(function () { From 926ccc6ae96057bf64517ed4b8428a0dddb72a37 Mon Sep 17 00:00:00 2001 From: Stephen Johnston Date: Fri, 16 Oct 2020 10:05:12 -0400 Subject: [PATCH 081/190] Rename pubProvidedSystem.js to pubProvidedIdSystem.js (#5861) * Rename pubProvidedSystem.js to pubProvidedIdSystem.js * Update userId_spec.js --- modules/{pubProvidedSystem.js => pubProvidedIdSystem.js} | 0 test/spec/modules/userId_spec.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename modules/{pubProvidedSystem.js => pubProvidedIdSystem.js} (100%) diff --git a/modules/pubProvidedSystem.js b/modules/pubProvidedIdSystem.js similarity index 100% rename from modules/pubProvidedSystem.js rename to modules/pubProvidedIdSystem.js diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index df72e59e7e7..c5ab2e249fc 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -33,7 +33,7 @@ import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {haloIdSubmodule} from 'modules/haloIdSystem.js'; -import {pubProvidedIdSubmodule} from 'modules/pubProvidedSystem.js'; +import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; let assert = require('chai').assert; From d8f43de3f8b1a3d74a90e43ebf143e0e397ce06f Mon Sep 17 00:00:00 2001 From: Rahul Shandilya <67756716+c3p-0@users.noreply.github.com> Date: Tue, 20 Oct 2020 17:54:37 +0530 Subject: [PATCH 082/190] Adding Medianet outstream renderer support (#5854) --- modules/medianetBidAdapter.js | 45 +++++++++++++++++++- test/spec/modules/medianetBidAdapter_spec.js | 41 ++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 49e2bdc225f..a30f6fc2627 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -3,9 +3,11 @@ import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'medianet'; const BID_URL = 'https://prebid.media.net/rtb/prebid'; +const PLAYER_URL = 'https://prebid.media.net/video/bundle.js'; const SLOT_VISIBILITY = { NOT_DETERMINED: 0, ABOVE_THE_FOLD: 1, @@ -16,10 +18,14 @@ const EVENTS = { BID_WON_EVENT_NAME: 'client_bid_won' }; const EVENT_PIXEL_URL = 'qsearch-a.akamaihd.net/log'; - +const OUTSTREAM = 'outstream'; let refererInfo = getRefererInfo(); let mnData = {}; + +window.mnet = window.mnet || {}; +window.mnet.queue = window.mnet.queue || []; + mnData.urlData = { domain: utils.parseUrl(refererInfo.referer).hostname, page: refererInfo.referer, @@ -321,6 +327,40 @@ function clearMnData() { mnData = {}; } +function addRenderer(bid) { + const videoContext = utils.deepAccess(bid, 'context') || ''; + const vastTimeout = utils.deepAccess(bid, 'vto'); + /* Adding renderer only when the context is Outstream + and the provider has responded with a renderer. + */ + if (videoContext == OUTSTREAM && vastTimeout) { + bid.renderer = newVideoRenderer(bid); + } +} + +function newVideoRenderer(bid) { + const renderer = Renderer.install({ + url: PLAYER_URL, + }); + renderer.setRender(function (bid) { + window.mnet.queue.push(function () { + const obj = { + width: bid.width, + height: bid.height, + vastTimeout: bid.vto, + maxAllowedVastTagRedirects: bid.mavtr, + allowVpaid: bid.avp, + autoPlay: bid.ap, + preload: bid.pl, + mute: bid.mt + } + const adUnitCode = bid.dfp_id; + const divId = utils.getGptSlotInfoForAdUnitCode(adUnitCode).divId || adUnitCode; + window.mnet.mediaNetoutstreamPlayer(bid, divId, obj); + }); + }); + return renderer; +} export const spec = { code: BIDDER_CODE, @@ -387,9 +427,10 @@ export const spec = { } validBids = bids.filter(bid => isValidBid(bid)); + validBids.forEach(addRenderer); + return validBids; }, - getUserSyncs: function(syncOptions, serverResponses) { let cookieSyncUrls = fetchCookieSyncUrls(serverResponses); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 1b2207de842..1eeb167601e 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -952,6 +952,42 @@ let VALID_BID_REQUEST = [{ } } }, + SERVER_VIDEO_OUTSTREAM_RESPONSE_VALID_BID = { + body: { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'cpm': 12.00, + 'width': 640, + 'height': 480, + 'ttl': 180, + 'creativeId': '370637746', + 'netRevenue': true, + 'vastXml': '', + 'currency': 'USD', + 'dfp_id': 'video1', + 'mediaType': 'video', + 'vto': 5000, + 'mavtr': 10, + 'avp': true, + 'ap': true, + 'pl': true, + 'mt': true, + 'jslt': 3000, + 'context': 'outstream' + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + }, SERVER_VALID_BIDS = [{ 'no_bid': false, 'requestId': '27210feac00e96', @@ -1405,4 +1441,9 @@ describe('Media.net bid adapter', function () { expect(response).to.deep.equal(undefined); }); }); + + it('context should be outstream', function () { + let bids = spec.interpretResponse(SERVER_VIDEO_OUTSTREAM_RESPONSE_VALID_BID, []); + expect(bids[0].context).to.equal('outstream'); + }); }); From 449a9fa33d910e07a6c26e63e505e47afaea7a89 Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 20 Oct 2020 10:45:31 -0400 Subject: [PATCH 083/190] PR-review: fixed getFloor function name (#5876) --- PR_REVIEW.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PR_REVIEW.md b/PR_REVIEW.md index a8b68c7ab45..a4bcc43b11a 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -31,7 +31,7 @@ Follow steps above for general review process. In addition, please verify the fo - All bidder parameter conventions must be followed: - Video params must be read from AdUnit.mediaTypes.video when available; however bidder config can override the ad unit. - First party data must be read from [`fpd.context` and `fpd.user`](https://docs.prebid.org/dev-docs/publisher-api-reference.html#setConfig-fpd). - - Adapters that accept a floor parameter must also support the [floors module](https://docs.prebid.org/dev-docs/modules/floors.html) -- look for a call to the `getFloors()` function. + - Adapters that accept a floor parameter must also support the [floors module](https://docs.prebid.org/dev-docs/modules/floors.html) -- look for a call to the `getFloor()` function. - Adapters cannot accept an schain parameter. Rather, they must look for the schain parameter at bidRequest.schain. - The bidRequest page referrer must checked in addition to any bidder-specific parameter. - If they're getting the COPPA flag, it must come from config.getConfig('coppa'); From 5d9a0e62da6af5546d4dd3f33742de8e0dca28ce Mon Sep 17 00:00:00 2001 From: omerBrowsi <54346241+omerBrowsi@users.noreply.github.com> Date: Tue, 20 Oct 2020 18:24:52 +0300 Subject: [PATCH 084/190] Real Time Data Module - Phase3 (#5783) * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * RTD module extend #4610 * add hook for submodule init variables naming * RTD bug fix * remove auction delay and related hooks * RTD phase 3 * design changes * fix loop continuation * proper fix this time * linter * reduce loops Co-authored-by: bretg --- modules/browsiRtdProvider.js | 122 +++----- modules/rtdModule/index.js | 306 +++++++++---------- src/targeting.js | 24 +- test/spec/modules/browsiRtdProvider_spec.js | 83 +++++ test/spec/modules/realTimeDataModule_spec.js | 160 ++++++++++ test/spec/modules/realTimeModule_spec.js | 274 ----------------- 6 files changed, 453 insertions(+), 516 deletions(-) create mode 100644 test/spec/modules/browsiRtdProvider_spec.js create mode 100644 test/spec/modules/realTimeDataModule_spec.js delete mode 100644 test/spec/modules/realTimeModule_spec.js diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 3aff3c6aac6..4ee338e94cc 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -13,30 +13,21 @@ * @property {string} pubKey * @property {string} url * @property {?string} keyName - * @property {?number} auctionDelay - * @property {?number} timeout */ -import {config} from '../src/config.js'; import * as utils from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajaxBuilder} from '../src/ajax.js'; import {loadExternalScript} from '../src/adloader.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import find from 'core-js-pure/features/array/find.js'; const storage = getStorageManager(); -/** @type {string} */ -const MODULE_NAME = 'realTimeData'; -/** @type {number} */ -const DEF_TIMEOUT = 1000; /** @type {ModuleParams} */ let _moduleParams = {}; /** @type {null|Object} */ -let _data = null; -/** @type {null | function} */ -let _dataReadyCallback = null; +let _predictionsData = null; /** @type {string} */ const DEF_KEYNAME = 'browsiViewability'; @@ -63,7 +54,7 @@ export function addBrowsiTag(data) { * collect required data from page * send data to browsi server to get predictions */ -function collectData() { +export function collectData() { const win = window.top; const doc = win.document; let browsiData = null; @@ -88,59 +79,33 @@ function collectData() { } export function setData(data) { - _data = data; - - if (typeof _dataReadyCallback === 'function') { - _dataReadyCallback(_data); - _dataReadyCallback = null; - } -} - -/** - * wait for data from server - * call callback when data is ready - * @param {function} callback - */ -function waitForData(callback) { - if (_data) { - _dataReadyCallback = null; - callback(_data); - } else { - _dataReadyCallback = callback; - } + _predictionsData = data; } -/** - * filter server data according to adUnits received - * call callback (onDone) when data is ready - * @param {adUnit[]} adUnits - * @param {function} onDone callback function - */ -function sendDataToModule(adUnits, onDone) { +function sendDataToModule(adUnitsCodes) { try { - waitForData(_predictionsData => { - const _predictions = _predictionsData.p || {}; - let dataToReturn = adUnits.reduce((rp, cau) => { - const adUnitCode = cau && cau.code; - if (!adUnitCode) { return rp } - const adSlot = getSlotByCode(adUnitCode); - const identifier = adSlot ? getMacroId(_predictionsData.pmd, adSlot) : adUnitCode; - const predictionData = _predictions[identifier]; - rp[adUnitCode] = getKVObject(-1, _predictionsData.kn); - if (!predictionData) { return rp } - - if (predictionData.p) { - if (!isIdMatchingAdUnit(adSlot, predictionData.w)) { - return rp; - } - rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn); + const _predictions = (_predictionsData && _predictionsData.p) || {}; + return adUnitsCodes.reduce((rp, adUnitCode) => { + if (!adUnitCode) { + return rp + } + const adSlot = getSlotByCode(adUnitCode); + const identifier = adSlot ? getMacroId(_predictionsData['pmd'], adSlot) : adUnitCode; + const predictionData = _predictions[identifier]; + rp[adUnitCode] = getKVObject(-1, _predictionsData['kn']); + if (!predictionData) { + return rp + } + if (predictionData.p) { + if (!isIdMatchingAdUnit(adSlot, predictionData.w)) { + return rp; } - return rp; - }, {}); - return onDone(dataToReturn); - }); + rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn); + } + return rp; + }, {}); } catch (e) { - onDone({}); + return {}; } } @@ -231,7 +196,7 @@ function evaluate(macro, divId, adUnit, replacer) { * @param {string} url server url with query params */ function getPredictionsFromServer(url) { - let ajax = ajaxBuilder(_moduleParams.auctionDelay || _moduleParams.timeout); + let ajax = ajaxBuilder(); ajax(url, { @@ -283,38 +248,23 @@ export const browsiSubmodule = { /** * get data and send back to realTimeData module * @function - * @param {adUnit[]} adUnits - * @param {function} onDone + * @param {string[]} adUnitsCodes */ - getData: sendDataToModule, - init: init + getTargetingData: sendDataToModule, + init: init, }; -function init(config, gdpr, usp) { +function init(moduleConfig) { + _moduleParams = moduleConfig.params; + if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { + collectData(); + } else { + utils.logError('missing params for Browsi provider'); + } return true; } -export function beforeInit(config) { - const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { - try { - _moduleParams = realTimeData.dataProviders && realTimeData.dataProviders.filter( - pr => pr.name && pr.name.toLowerCase() === 'browsi')[0].params; - confListener(); - _moduleParams.auctionDelay = realTimeData.auctionDelay; - _moduleParams.timeout = realTimeData.timeout || DEF_TIMEOUT; - } catch (e) { - _moduleParams = {}; - } - if (_moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { - collectData(); - } else { - utils.logError('missing params for Browsi provider'); - } - }); -} - function registerSubModule() { submodule('realTimeData', browsiSubmodule); } registerSubModule(); -beforeInit(config); diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 9acd484cec8..e235868f791 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -3,16 +3,49 @@ * @module modules/realTimeData */ +/** + * @interface UserConsentData + */ +/** + * @property + * @summary gdpr consent + * @name UserConsentData#gdpr + * @type {Object} + */ +/** + * @property + * @summary usp consent + * @name UserConsentData#usp + * @type {Object} + */ +/** + * @property + * @summary coppa + * @name UserConsentData#coppa + * @type {boolean} + */ + /** * @interface RtdSubmodule */ /** - * @function + * @function? * @summary return real time data - * @name RtdSubmodule#getData - * @param {AdUnit[]} adUnits - * @param {function} onDone + * @name RtdSubmodule#getTargetingData + * @param {string[]} adUnitsCodes + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + +/** + * @function? + * @summary modify bid request data + * @name RtdSubmodule#getBidRequestData + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + * @param {Object} reqBidsConfigObj + * @param {function} callback */ /** @@ -33,42 +66,36 @@ * @function * @summary init sub module * @name RtdSubmodule#init - * @param {Object} config - * @param {Object} gdpr settings - * @param {Object} usp settings + * @param {SubmoduleConfig} config + * @param {UserConsentData} user consent * @return {boolean} false to remove sub module */ /** * @function? * @summary on auction init event - * @name RtdSubmodule#auctionInit + * @name RtdSubmodule#onAuctionInitEvent * @param {Object} data * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent */ /** * @function? * @summary on auction end event - * @name RtdSubmodule#auctionEnd - * @param {Object} data - * @param {SubmoduleConfig} config - */ - -/** - * @function? - * @summary on bid request event - * @name RtdSubmodule#updateBidRequest + * @name RtdSubmodule#onAuctionEndEvent * @param {Object} data * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent */ /** * @function? * @summary on bid response event - * @name RtdSubmodule#updateBidResponse + * @name RtdSubmodule#onBidResponseEvent * @param {Object} data * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent */ /** @@ -82,13 +109,6 @@ * @type {number} */ -/** - * @property - * @summary timeout (if no auction dealy) - * @name ModuleConfig#timeout - * @type {number} - */ - /** * @property * @summary list of sub modules @@ -121,33 +141,34 @@ * @type {boolean} */ -import {getGlobal} from '../../src/prebidGlobal.js'; import {config} from '../../src/config.js'; -import {targeting} from '../../src/targeting.js'; -import {getHook, module} from '../../src/hook.js'; +import {module} from '../../src/hook.js'; import * as utils from '../../src/utils.js'; import events from '../../src/events.js'; import CONSTANTS from '../../src/constants.json'; import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; import find from 'core-js-pure/features/array/find.js'; +import {getGlobal} from '../../src/prebidGlobal.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; -/** @type {number} */ -const DEF_TIMEOUT = 1000; +/** @type {RtdSubmodule[]} */ +let registeredSubModules = []; /** @type {RtdSubmodule[]} */ export let subModules = []; /** @type {ModuleConfig} */ let _moduleConfig; /** @type {SubmoduleConfig[]} */ let _dataProviders = []; +/** @type {UserConsentData} */ +let _userConsent; /** * enable submodule in User ID * @param {RtdSubmodule} submodule */ export function attachRealTimeDataProvider(submodule) { - subModules.push(submodule); + registeredSubModules.push(submodule); } export function init(config) { @@ -159,35 +180,35 @@ export function init(config) { confListener(); // unsubscribe config listener _moduleConfig = realTimeData; _dataProviders = realTimeData.dataProviders; - getHook('makeBidRequests').before(initSubModules); setEventsListeners(); - if (typeof (_moduleConfig.auctionDelay) === 'undefined') { - _moduleConfig.auctionDelay = 0; - } - // delay bidding process only if auctionDelay > 0 - if (!_moduleConfig.auctionDelay > 0) { - getHook('bidsBackCallback').before(setTargetsAfterRequestBids); - } else { - getGlobal().requestBids.before(requestBidsHook); - } + getGlobal().requestBids.before(setBidRequestsData, 40); + initSubModules(); }); } +function getConsentData() { + return { + gdpr: gdprDataHandler.getConsentData(), + usp: uspDataHandler.getConsentData(), + coppa: !!(config.getConfig('coppa')) + } +} + /** * call each sub module init function by config order * if no init function / init return failure / module not configured - remove it from submodules list */ -export function initSubModules(next, adUnits, auctionStart, auctionId, cbTimeout, labels) { +function initSubModules() { + _userConsent = getConsentData(); let subModulesByOrder = []; _dataProviders.forEach(provider => { - const sm = find(subModules, s => s.name === provider.name); - const initResponse = sm && sm.init && sm.init(provider, gdprDataHandler.getConsentData(), uspDataHandler.getConsentData()); + const sm = find(registeredSubModules, s => s.name === provider.name); + const initResponse = sm && sm.init && sm.init(provider, _userConsent); if (initResponse) { subModulesByOrder.push(Object.assign(sm, {config: provider})); } }); subModules = subModulesByOrder; - next(adUnits, auctionStart, auctionId, cbTimeout, labels) } /** @@ -195,94 +216,117 @@ export function initSubModules(next, adUnits, auctionStart, auctionId, cbTimeout */ function setEventsListeners() { events.on(CONSTANTS.EVENTS.AUCTION_INIT, (args) => { - subModules.forEach(sm => { sm.auctionInit && sm.auctionInit(args, sm.config) }) + subModules.forEach(sm => { sm.onAuctionInitEvent && sm.onAuctionInitEvent(args, sm.config, _userConsent) }) }); events.on(CONSTANTS.EVENTS.AUCTION_END, (args) => { - subModules.forEach(sm => { sm.auctionEnd && sm.auctionEnd(args, sm.config) }) - }); - events.on(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, (args) => { - subModules.forEach(sm => { sm.updateBidRequest && sm.updateBidRequest(args, sm.config) }) + getAdUnitTargeting(args); + subModules.forEach(sm => { sm.onAuctionEndEvent && sm.onAuctionEndEvent(args, sm.config, _userConsent) }) }); events.on(CONSTANTS.EVENTS.BID_RESPONSE, (args) => { - subModules.forEach(sm => { sm.updateBidResponse && sm.updateBidResponse(args, sm.config) }) + subModules.forEach(sm => { sm.onBidResponseEvent && sm.onBidResponseEvent(args, sm.config, _userConsent) }) }); } /** - * get data from sub module - * @param {AdUnit[]} adUnits received from auction - * @param {function} callback callback function on data received + * loop through configured data providers If the data provider has registered getBidRequestData, + * call it, providing reqBidsConfigObj, consent data and module params + * this allows submodules to modify bidders + * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js */ -export function getProviderData(adUnits, callback) { - /** - * invoke callback if one of the conditions met: - * timeout reached - * all submodules answered - * all sub modules configured "waitForIt:true" answered (as long as there is at least one configured) - */ - - const waitForSubModulesLength = subModules.filter(sm => sm.config && sm.config.waitForIt).length; - let callbacksExpected = waitForSubModulesLength || subModules.length; - const shouldWaitForAllSubModules = waitForSubModulesLength === 0; - let dataReceived = {}; - let processDone = false; - const dataWaitTimeout = setTimeout(done, _moduleConfig.auctionDelay || _moduleConfig.timeout || DEF_TIMEOUT); +export function setBidRequestsData(fn, reqBidsConfigObj) { + _userConsent = getConsentData(); + + const relevantSubModules = []; + const prioritySubModules = []; subModules.forEach(sm => { - sm.getData(adUnits, onDataReceived.bind(sm)); + if (typeof sm.getBidRequestData !== 'function') { + return; + } + relevantSubModules.push(sm); + const config = sm.config; + if (config && config.waitForIt) { + prioritySubModules.push(sm); + } }); - function onDataReceived(data) { - if (processDone) { - return + const shouldDelayAuction = prioritySubModules.length && _moduleConfig.auctionDelay && _moduleConfig.auctionDelay > 0; + let callbacksExpected = prioritySubModules.length; + let isDone = false; + let waitTimeout; + + if (!relevantSubModules.length) { + return exitHook(); + } + + if (shouldDelayAuction) { + waitTimeout = setTimeout(exitHook, _moduleConfig.auctionDelay); + } + + relevantSubModules.forEach(sm => { + sm.getBidRequestData(reqBidsConfigObj, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent) + }); + + if (!shouldDelayAuction) { + return exitHook(); + } + + function onGetBidRequestDataCallback() { + if (isDone) { + return; } - dataReceived[this.name] = data; - if (shouldWaitForAllSubModules || (this.config && this.config.waitForIt)) { - callbacksExpected-- + if (this.config && this.config.waitForIt) { + callbacksExpected--; } if (callbacksExpected <= 0) { - clearTimeout(dataWaitTimeout); - done(); + return exitHook(); } } - function done() { - processDone = true; - callback(dataReceived); + function exitHook() { + isDone = true; + clearTimeout(waitTimeout); + fn.call(this, reqBidsConfigObj); } } /** - * run hook after bids request and before callback - * get data from provider and set key values to primary ad server - * @param {function} next - next hook function - * @param {AdUnit[]} adUnits received from auction + * loop through configured data providers If the data provider has registered getTargetingData, + * call it, providing ad unit codes, consent data and module params + * the sub mlodle will return data to set on the ad unit + * this function used to place key values on primary ad server per ad unit + * @param {Object} auction object received on auction end event */ -export function setTargetsAfterRequestBids(next, adUnits) { - getProviderData(adUnits, (data) => { - if (data && Object.keys(data).length) { - const _mergedData = deepMerge(setDataOrderByProvider(subModules, data)); - if (Object.keys(_mergedData).length) { - setDataForPrimaryAdServer(_mergedData); - } - } - next(adUnits); - }); -} +export function getAdUnitTargeting(auction) { + const relevantSubModules = subModules.filter(sm => typeof sm.getTargetingData === 'function'); + if (!relevantSubModules.length) { + return; + } -/** - * return an array providers data in reverse order,so the data merge will be according to correct config order - * @param {Submodule[]} modules - * @param {Object} data - data retrieved from providers - * @return {array} reversed order ready for merge - */ -function setDataOrderByProvider(modules, data) { - let rd = []; - for (let i = modules.length; i--; i > 0) { - if (data[modules[i].name]) { - rd.push(data[modules[i].name]) + // get data + const adUnitCodes = auction.adUnitCodes; + if (!adUnitCodes) { + return; + } + let targeting = []; + for (let i = relevantSubModules.length - 1; i >= 0; i--) { + const smTargeting = relevantSubModules[i].getTargetingData(adUnitCodes, relevantSubModules[i].config, _userConsent); + if (smTargeting && typeof smTargeting === 'object') { + targeting.push(smTargeting); + } else { + utils.logWarn('invalid getTargetingData response for sub module', relevantSubModules[i].name); } } - return rd; + // place data on auction adUnits + const mergedTargeting = deepMerge(targeting); + auction.adUnits.forEach(adUnit => { + const kv = adUnit.code && mergedTargeting[adUnit.code]; + if (!kv) { + return + } + adUnit[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = Object.assign(adUnit[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] || {}, kv); + }); + return auction.adUnits; } /** @@ -311,53 +355,5 @@ export function deepMerge(arr) { }, {}); } -/** - * run hook before bids request - * get data from provider and set key values to primary ad server & bidders - * @param {function} fn - hook function - * @param {Object} reqBidsConfigObj - request bids object - */ -export function requestBidsHook(fn, reqBidsConfigObj) { - getProviderData(reqBidsConfigObj.adUnits || getGlobal().adUnits, (data) => { - if (data && Object.keys(data).length) { - const _mergedData = deepMerge(setDataOrderByProvider(subModules, data)); - if (Object.keys(_mergedData).length) { - setDataForPrimaryAdServer(_mergedData); - addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, _mergedData); - } - } - return fn.call(this, reqBidsConfigObj); - }); -} - -/** - * set data to primary ad server - * @param {Object} data - key values to set - */ -function setDataForPrimaryAdServer(data) { - if (utils.isGptPubadsDefined()) { - targeting.setTargetingForGPT(data, null) - } else { - window.googletag = window.googletag || {}; - window.googletag.cmd = window.googletag.cmd || []; - window.googletag.cmd.push(() => { - targeting.setTargetingForGPT(data, null); - }); - } -} - -/** - * @param {AdUnit[]} adUnits - * @param {Object} data - key values to set - */ -function addIdDataToAdUnitBids(adUnits, data) { - adUnits.forEach(adUnit => { - adUnit.bids = adUnit.bids.map(bid => { - const rd = data[adUnit.code] || {}; - return Object.assign(bid, {realTimeData: rd}); - }) - }); -} - module('realTimeData', attachRealTimeDataProvider); init(config); diff --git a/src/targeting.js b/src/targeting.js index 8176bc9caff..b6a38bdbb61 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -235,7 +235,8 @@ export function newTargeting(auctionManager) { // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. var targeting = getWinningBidTargeting(adUnitCodes, bidsReceived) .concat(getCustomBidTargeting(adUnitCodes, bidsReceived)) - .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived) : getDealBids(adUnitCodes, bidsReceived)); + .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived) : getDealBids(adUnitCodes, bidsReceived)) + .concat(getAdUnitTargeting(adUnitCodes)); // store a reference of the targeting keys targeting.map(adUnitCode => { @@ -609,6 +610,27 @@ export function newTargeting(auctionManager) { }); } + function getAdUnitTargeting(adUnitCodes) { + function getTargetingObj(adUnit) { + return deepAccess(adUnit, CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING); + } + + function getTargetingValues(adUnit) { + const aut = getTargetingObj(adUnit); + + return Object.keys(aut) + .map(function(key) { + return {[key]: utils.isArray(aut[key]) ? aut[key] : aut[key].split(',')}; + }); + } + + return auctionManager.getAdUnits() + .filter(adUnit => includes(adUnitCodes, adUnit.code) && getTargetingObj(adUnit)) + .map(adUnit => { + return {[adUnit.code]: getTargetingValues(adUnit)} + }); + } + targeting.isApntagDefined = function() { if (window.apntag && utils.isFn(window.apntag.setKeywords)) { return true; diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js new file mode 100644 index 00000000000..ee37d16905b --- /dev/null +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -0,0 +1,83 @@ +import * as browsiRTD from '../../../modules/browsiRtdProvider.js'; +import {makeSlot} from '../integration/faker/googletag.js'; + +describe('browsi Real time data sub module', function () { + const conf = { + 'auctionDelay': 250, + dataProviders: [{ + 'name': 'browsi', + 'params': { + 'url': 'testUrl.com', + 'siteKey': 'testKey', + 'pubKey': 'testPub', + 'keyName': 'bv' + } + }] + }; + + it('should init and return true', function () { + browsiRTD.collectData(); + expect(browsiRTD.browsiSubmodule.init(conf.dataProviders[0])).to.equal(true) + }); + + it('should create browsi script', function () { + const script = browsiRTD.addBrowsiTag('scriptUrl.com'); + expect(script.getAttribute('data-sitekey')).to.equal('testKey'); + expect(script.getAttribute('data-pubkey')).to.equal('testPub'); + expect(script.async).to.equal(true); + expect(script.prebidData.kn).to.equal(conf.dataProviders[0].params.keyName); + }); + + it('should match placement with ad unit', function () { + const slot = makeSlot({code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1'}); + + const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_300x250']); // true + const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_300x250', '/57778053/Browsi']); // true + const test3 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_Low']); // false + const test4 = browsiRTD.isIdMatchingAdUnit(slot, []); // true + + expect(test1).to.equal(true); + expect(test2).to.equal(true); + expect(test3).to.equal(false); + expect(test4).to.equal(true); + }); + + it('should return correct macro values', function () { + const slot = makeSlot({code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1'}); + + slot.setTargeting('test', ['test', 'value']); + // slot getTargeting doesn't act like GPT so we can't expect real value + const macroResult = browsiRTD.getMacroId({p: '/'}, slot); + expect(macroResult).to.equal('/57778053/Browsi_Demo_300x250/NA'); + + const macroResultB = browsiRTD.getMacroId({}, slot); + expect(macroResultB).to.equal('browsiAd_1'); + + const macroResultC = browsiRTD.getMacroId({p: '', s: {s: 0, e: 1}}, slot); + expect(macroResultC).to.equal('/'); + }); + + describe('should return data to RTD module', function () { + it('should return empty if no ad units defined', function () { + browsiRTD.setData({}); + expect(browsiRTD.browsiSubmodule.getTargetingData([])).to.eql({}); + }); + + it('should return NA if no prediction for ad unit', function () { + makeSlot({code: 'adMock', divId: 'browsiAd_2'}); + browsiRTD.setData({}); + expect(browsiRTD.browsiSubmodule.getTargetingData(['adMock'])).to.eql({adMock: {bv: 'NA'}}); + }); + + it('should return prediction from server', function () { + makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); + const data = { + p: {'hasPrediction': {p: 0.234}}, + kn: 'bv', + pmd: undefined + }; + browsiRTD.setData(data); + expect(browsiRTD.browsiSubmodule.getTargetingData(['hasPrediction'])).to.eql({hasPrediction: {bv: '0.20'}}); + }) + }) +}); diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js new file mode 100644 index 00000000000..b84aef15feb --- /dev/null +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -0,0 +1,160 @@ +import * as rtdModule from 'modules/rtdModule/index.js'; +import { config } from 'src/config.js'; +import * as sinon from 'sinon'; + +const getBidRequestDataSpy = sinon.spy(); + +const validSM = { + name: 'validSM', + init: () => { return true }, + getTargetingData: (adUnitsCodes) => { + return {'ad2': {'key': 'validSM'}} + }, + getBidRequestData: getBidRequestDataSpy +}; + +const validSMWait = { + name: 'validSMWait', + init: () => { return true }, + getTargetingData: (adUnitsCodes) => { + return {'ad1': {'key': 'validSMWait'}} + }, + getBidRequestData: getBidRequestDataSpy +}; + +const invalidSM = { + name: 'invalidSM' +}; + +const failureSM = { + name: 'failureSM', + init: () => { return false } +}; + +const nonConfSM = { + name: 'nonConfSM', + init: () => { return true } +}; + +const conf = { + 'realTimeData': { + 'auctionDelay': 100, + dataProviders: [ + { + 'name': 'validSMWait', + 'waitForIt': true, + }, + { + 'name': 'validSM', + 'waitForIt': false, + }, + { + 'name': 'invalidSM' + }, + { + 'name': 'failureSM' + }] + } +}; + +describe('Real time module', function () { + before(function () { + rtdModule.attachRealTimeDataProvider(validSM); + rtdModule.attachRealTimeDataProvider(invalidSM); + rtdModule.attachRealTimeDataProvider(failureSM); + rtdModule.attachRealTimeDataProvider(nonConfSM); + rtdModule.attachRealTimeDataProvider(validSMWait); + }); + + after(function () { + config.resetConfig(); + }); + + beforeEach(function () { + config.setConfig(conf); + }); + + it('should use only valid modules', function () { + rtdModule.init(config); + expect(rtdModule.subModules).to.eql([validSMWait, validSM]); + }); + + it('should be able to modify bid request', function (done) { + rtdModule.setBidRequestsData(() => { + assert(getBidRequestDataSpy.calledTwice); + assert(getBidRequestDataSpy.calledWith({bidRequest: {}})); + done(); + }, {bidRequest: {}}) + }); + + it('deep merge object', function () { + const obj1 = { + id1: { + key: 'value', + key2: 'value2' + }, + id2: { + k: 'v' + } + }; + const obj2 = { + id1: { + key3: 'value3' + } + }; + const obj3 = { + id3: { + key: 'value' + } + }; + const expected = { + id1: { + key: 'value', + key2: 'value2', + key3: 'value3' + }, + id2: { + k: 'v' + }, + id3: { + key: 'value' + } + }; + + const merged = rtdModule.deepMerge([obj1, obj2, obj3]); + assert.deepEqual(expected, merged); + }); + + it('sould place targeting on adUnits', function (done) { + const auction = { + adUnitCodes: ['ad1', 'ad2'], + adUnits: [ + { + code: 'ad1' + }, + { + code: 'ad2', + adserverTargeting: {preKey: 'preValue'} + } + ] + }; + + const expectedAdUnits = [ + { + code: 'ad1', + adserverTargeting: {key: 'validSMWait'} + }, + { + code: 'ad2', + adserverTargeting: { + preKey: 'preValue', + key: 'validSM' + } + } + ]; + + const adUnits = rtdModule.getAdUnitTargeting(auction); + assert.deepEqual(expectedAdUnits, adUnits) + done(); + }) +}); diff --git a/test/spec/modules/realTimeModule_spec.js b/test/spec/modules/realTimeModule_spec.js deleted file mode 100644 index f47068724d1..00000000000 --- a/test/spec/modules/realTimeModule_spec.js +++ /dev/null @@ -1,274 +0,0 @@ -import * as rtdModule from 'modules/rtdModule/index.js'; -import { config } from 'src/config.js'; -import {makeSlot} from '../integration/faker/googletag.js'; -import * as browsiRTD from '../../../modules/browsiRtdProvider.js'; - -const validSM = { - name: 'validSM', - init: () => { return true }, - getData: (adUnits, onDone) => { - setTimeout(() => { - return onDone({'key': 'validSM'}) - }, 500) - } -}; - -const validSMWait = { - name: 'validSMWait', - init: () => { return true }, - getData: (adUnits, onDone) => { - setTimeout(() => { - return onDone({'ad1': {'key': 'validSMWait'}}) - }, 50) - } -}; - -const invalidSM = { - name: 'invalidSM' -}; - -const failureSM = { - name: 'failureSM', - init: () => { return false } -}; - -const nonConfSM = { - name: 'nonConfSM', - init: () => { return true } -}; - -const conf = { - 'realTimeData': { - 'auctionDelay': 250, - dataProviders: [ - { - 'name': 'validSMWait', - 'waitForIt': true, - }, - { - 'name': 'validSM', - 'waitForIt': false, - }, - { - 'name': 'invalidSM' - }, - { - 'name': 'failureSM' - }] - } -}; - -function getAdUnitMock(code = 'adUnit-code') { - return { - code, - mediaTypes: { banner: {}, native: {} }, - sizes: [[300, 200], [300, 600]], - bids: [{ bidder: 'sampleBidder', params: { placementId: 'banner-only-bidder' } }] - }; -} - -describe('Real time module', function () { - after(function () { - config.resetConfig(); - }); - - beforeEach(function () { - config.setConfig(conf); - }); - - it('should use only valid modules', function (done) { - rtdModule.attachRealTimeDataProvider(validSM); - rtdModule.attachRealTimeDataProvider(invalidSM); - rtdModule.attachRealTimeDataProvider(failureSM); - rtdModule.attachRealTimeDataProvider(nonConfSM); - rtdModule.attachRealTimeDataProvider(validSMWait); - rtdModule.initSubModules(afterInitSubModules); - function afterInitSubModules() { - expect(rtdModule.subModules).to.eql([validSMWait, validSM]); - done(); - } - rtdModule.init(config); - }); - - it('should only wait for must have sub modules', function (done) { - rtdModule.getProviderData([], (data) => { - expect(data).to.eql({validSMWait: {'ad1': {'key': 'validSMWait'}}}); - done(); - }) - }); - - it('deep merge object', function () { - const obj1 = { - id1: { - key: 'value', - key2: 'value2' - }, - id2: { - k: 'v' - } - }; - const obj2 = { - id1: { - key3: 'value3' - } - }; - const obj3 = { - id3: { - key: 'value' - } - }; - const expected = { - id1: { - key: 'value', - key2: 'value2', - key3: 'value3' - }, - id2: { - k: 'v' - }, - id3: { - key: 'value' - } - }; - - const merged = rtdModule.deepMerge([obj1, obj2, obj3]); - assert.deepEqual(expected, merged); - }); - - it('check module using bidsBackCallback', function (done) { - // set slot - const slot = makeSlot({ code: '/code1', divId: 'ad1' }); - window.googletag.pubads().setSlots([slot]); - - function afterBidHook() { - expect(slot.getTargeting().length).to.equal(1); - expect(slot.getTargeting()[0].key).to.equal('validSMWait'); - done(); - } - rtdModule.setTargetsAfterRequestBids(afterBidHook, []); - }); - - it('check module using requestBidsHook', function (done) { - // set slot - const slotsB = makeSlot({ code: '/code1', divId: 'ad1' }); - window.googletag.pubads().setSlots([slotsB]); - let adUnits = [getAdUnitMock('ad1')]; - - function afterBidHook(data) { - expect(slotsB.getTargeting().length).to.equal(1); - expect(slotsB.getTargeting()[0].key).to.equal('validSMWait'); - - data.adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid.realTimeData).to.have.property('key'); - expect(bid.realTimeData.key).to.equal('validSMWait'); - }); - }); - done(); - } - rtdModule.requestBidsHook(afterBidHook, { adUnits: adUnits }); - }); -}); - -describe('browsi Real time data sub module', function () { - const conf = { - 'realTimeData': { - 'auctionDelay': 250, - dataProviders: [{ - 'name': 'browsi', - 'params': { - 'url': 'testUrl.com', - 'siteKey': 'testKey', - 'pubKey': 'testPub', - 'keyName': 'bv' - } - }] - } - }; - - beforeEach(function () { - config.setConfig(conf); - }); - - after(function () { - config.resetConfig(); - }); - - it('should init and return true', function () { - browsiRTD.beforeInit(config); - expect(browsiRTD.browsiSubmodule.init()).to.equal(true) - }); - - it('should create browsi script', function () { - const script = browsiRTD.addBrowsiTag('scriptUrl.com'); - expect(script.getAttribute('data-sitekey')).to.equal('testKey'); - expect(script.getAttribute('data-pubkey')).to.equal('testPub'); - expect(script.async).to.equal(true); - expect(script.prebidData.kn).to.equal(conf.realTimeData.dataProviders[0].params.keyName); - }); - - it('should match placement with ad unit', function () { - const slot = makeSlot({ code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1' }); - - const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_300x250']); // true - const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_300x250', '/57778053/Browsi']); // true - const test3 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_Low']); // false - const test4 = browsiRTD.isIdMatchingAdUnit(slot, []); // true - - expect(test1).to.equal(true); - expect(test2).to.equal(true); - expect(test3).to.equal(false); - expect(test4).to.equal(true); - }); - - it('should return correct macro values', function () { - const slot = makeSlot({ code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1' }); - - slot.setTargeting('test', ['test', 'value']); - // slot getTargeting doesn't act like GPT so we can't expect real value - const macroResult = browsiRTD.getMacroId({p: '/'}, slot); - expect(macroResult).to.equal('/57778053/Browsi_Demo_300x250/NA'); - - const macroResultB = browsiRTD.getMacroId({}, slot); - expect(macroResultB).to.equal('browsiAd_1'); - - const macroResultC = browsiRTD.getMacroId({p: '', s: {s: 0, e: 1}}, slot); - expect(macroResultC).to.equal('/'); - }); - - describe('should return data to RTD module', function () { - it('should return empty if no ad units defined', function (done) { - browsiRTD.setData({}); - browsiRTD.browsiSubmodule.getData([], onDone); - function onDone(data) { - expect(data).to.eql({}); - done(); - } - }); - - it('should return NA if no prediction for ad unit', function (done) { - const adUnits = [getAdUnitMock('adMock')]; - browsiRTD.setData({}); - browsiRTD.browsiSubmodule.getData(adUnits, onDone); - function onDone(data) { - expect(data).to.eql({adMock: {bv: 'NA'}}); - done(); - } - }); - - it('should return prediction from server', function (done) { - const adUnits = [getAdUnitMock('hasPrediction')]; - const data = { - p: {'hasPrediction': {p: 0.234}}, - kn: 'bv', - pmd: undefined - }; - browsiRTD.setData(data); - browsiRTD.browsiSubmodule.getData(adUnits, onDone); - function onDone(data) { - expect(data).to.eql({hasPrediction: {bv: '0.20'}}); - done(); - } - }) - }) -}); From c4eb7428eec2ea10d12d1b3b8e637bdf9a313bfe Mon Sep 17 00:00:00 2001 From: Anthony Lauzon Date: Tue, 20 Oct 2020 10:25:46 -0500 Subject: [PATCH 085/190] Audigent RTD Provider HaloId Support & RTD Phase 3 Compliance (#5777) * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * RTD module extend #4610 * add hook for submodule init variables naming * RTD bug fix * remove auction delay and related hooks * update audigent rtd provider * style update * change onDone() logic * RTD phase 3 * return on data unavailable * api endpoint update * update audigent RTD provider for new spec * design changes * fix loop continuation * proper fix this time * linter * update rtd parameters, onDone semantics * reduce loops * documentation update * working update to rtd3 spec, update segment example, documentation * remove unused vars, reference module name * resolve haloid for segments * update documentation to markdown * update description in documentation * minify optimizations Co-authored-by: omerdotan Co-authored-by: bretg --- .../gpt/audigentSegments_example.html | 86 ++++++--- modules/audigentRtdProvider.js | 174 +++++++++--------- modules/audigentRtdProvider.md | 34 +++- package-lock.json | 47 ++--- 4 files changed, 202 insertions(+), 139 deletions(-) diff --git a/integrationExamples/gpt/audigentSegments_example.html b/integrationExamples/gpt/audigentSegments_example.html index 7739b558327..1536ece9ab7 100644 --- a/integrationExamples/gpt/audigentSegments_example.html +++ b/integrationExamples/gpt/audigentSegments_example.html @@ -84,11 +84,10 @@ { code: 'test-div', mediaTypes: { - banner: { - sizes: [[300,250],[300,600],[728,90]] - } + banner: { + sizes: [[300,250],[300,600],[728,90]] + } }, - bids: [ { bidder: 'rubicon', @@ -120,20 +119,9 @@ consentManagement: { cmpApi: 'iab', timeout: 1000, - allowAuctionWithoutConsent: true + defaultGdprScope: true }, - // consentManagement: { - // cmpApi: 'static', - // consentData: { - // consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA' - // vendorData: { - // purposeConsents: { - // '1': true - // } - // } - // } - // }, - usersync: { + userSync: { userIds: [{ name: "unifiedId", params: { @@ -145,6 +133,16 @@ name: "unifiedid", expires: 30 }, + }, { + name: "intentIqId", + params: { + partner: 0, //Set your real IntentIQ partner ID here for production. + }, + storage: { + type: "cookie", + name: "intentIqId", + expires: 30 + }, }, { name: "id5Id", params: { @@ -157,6 +155,18 @@ refreshInSeconds: 8*3600 // Refresh frequency of cookies, defaulting to 'expires' }, + }, { + name: "merkleId", + params: { + ptk: '12345678-aaaa-bbbb-cccc-123456789abc', //Set your real merkle partner key here + pubid: 'EXAMPLE' //Set your real merkle publisher id here + }, + storage: { + type: "html5", + name: "merkleId", + expires: 30 + }, + }, { name: "parrableId", params: { @@ -174,7 +184,7 @@ // foo: '9879878907987', // bar:'93939' // } - }, { + }, { name: 'identityLink', params: { pid: '14' // Set your real identityLink placement ID here @@ -184,13 +194,46 @@ name: 'idl_env', expires: 30 } + }, { + name: "sharedId", + params: { + syncTime: 60 // in seconds, default is 24 hours + }, + storage: { + type: "cookie", + name: "sharedid", + expires: 28 + } + }, { + name: 'lotamePanoramaId' + }, { + name: "liveIntentId", + params: { + publisherId: "9896876" + }, + storage: { + type: "cookie", + name: "_li_pbid", + expires: 28 + } + }, { + name: "zeotapIdPlus" + }, { + name: 'haloId', + storage: { + type: "cookie", + name: "haloId", + expires: 28 + } + }, { + name: "quantcastId" }], syncDelay: 5000, - auctionDelay: 1000 + auctionDelay: 1000 }, realTimeData: { auctionDelay: 1000, - dataProviders: [{name: "audigent"}] + dataProviders: [{name: "audigent", waitForIt: true}] } }); pbjs.addAdUnits(adUnits); @@ -199,7 +242,7 @@ function sendAdserverRequest() { document.getElementById('tdid').innerHTML = adUnits[0].bids[0].userId['tdid']; - document.getElementById('audigent_segments').innerHTML = JSON.stringify(adUnits[0].bids[0].realTimeData.audigent_segments); + document.getElementById('audigent_segments').innerHTML = JSON.stringify(adUnits[0].bids[0].audigent_segments); if (pbjs.adserverRequestSent) return; pbjs.adserverRequestSent = true; @@ -246,6 +289,7 @@

Audigent Segments Prebid

googletag.cmd.push(function() { googletag.display('test-div'); }); + TDID:
diff --git a/modules/audigentRtdProvider.js b/modules/audigentRtdProvider.js index 0f32c84962f..09b76cac0df 100644 --- a/modules/audigentRtdProvider.js +++ b/modules/audigentRtdProvider.js @@ -5,17 +5,6 @@ * @module modules/audigentRtdProvider * @requires module:modules/realTimeData */ - -/** - * @typedef {Object} ModuleParams - * @property {string} siteKey - * @property {string} pubKey - * @property {string} url - * @property {?string} keyName - * @property {number} auctionDelay - */ - -import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; import * as utils from '../src/utils.js'; import {submodule} from '../src/hook.js'; @@ -26,116 +15,131 @@ const storage = getStorageManager(); /** @type {string} */ const MODULE_NAME = 'realTimeData'; - -/** @type {ModuleParams} */ -let _moduleParams = {}; +const SUBMODULE_NAME = 'audigent'; +const HALOID_LOCAL_NAME = 'auHaloId'; +const SEG_LOCAL_NAME = '__adgntseg'; /** - * XMLHttpRequest to get data form audigent server - * @param {string} url server url with query params + * decorate adUnits with segment data + * @param {adUnit[]} adUnits + * @param {Object} data */ +function addSegmentData(adUnits, data) { + adUnits.forEach(adUnit => { + if (adUnit.hasOwnProperty('bids')) { + adUnit.bids.forEach(bid => { + bid.audigent_segments = data; + }) + } + }) -export function setData(data) { - storage.setDataInLocalStorage('__adgntseg', JSON.stringify(data)); + return adUnits; } -function getSegments(adUnits, onDone) { - try { - let jsonData = storage.getDataFromLocalStorage('__adgntseg'); - if (jsonData) { - let data = JSON.parse(jsonData); - if (data.audigent_segments) { - let dataToReturn = adUnits.reduce((rp, cau) => { - const adUnitCode = cau && cau.code; - if (!adUnitCode) { return rp } - rp[adUnitCode] = data; - return rp; - }, {}); - - onDone(dataToReturn); - return; - } +/** + * segment retrieval from audigent's backends + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} config + * @param {Object} userConsent + */ +function getSegments(reqBidsConfigObj, onDone, config, userConsent) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + + let jsonData = storage.getDataFromLocalStorage(SEG_LOCAL_NAME); + if (jsonData) { + let data = JSON.parse(jsonData); + if (data.audigent_segments) { + addSegmentData(adUnits, data.audigent_segments); + onDone(); + return; } - getSegmentsAsync(adUnits, onDone); - } catch (e) { - getSegmentsAsync(adUnits, onDone); } -} -function getSegmentsAsync(adUnits, onDone) { const userIds = (getGlobal()).getUserIds(); - let tdid = null; + if (typeof userIds == 'undefined' || userIds == null) { + onDone(); + return; + } - if (userIds && userIds['tdid']) { - tdid = userIds['tdid']; + let haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); + if (haloId) { + userIds.haloId = haloId; + getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); } else { - onDone({}); + var script = document.createElement('script') + script.type = 'text/javascript'; + + script.onload = function() { + userIds.haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); + getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); + } + + script.src = 'https://id.halo.ad.gt/api/v1/haloid'; + document.getElementsByTagName('head')[0].appendChild(script); } +} - const url = `https://seg.ad.gt/api/v1/rtb_segments?tdid=${tdid}`; +/** + * async segment retrieval from audigent's backends + * @param {adUnit[]} adUnits + * @param {function} onDone + * @param {Object} config + * @param {Object} userConsent + * @param {Object} userIds + */ +function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) { + let reqParams = {} + if (typeof config == 'object' && config != null && Object.keys(config).length > 0) { + reqParams = config.params + } + const url = `https://seg.halo.ad.gt/api/v1/rtb_segments`; ajax(url, { success: function (response, req) { if (req.status === 200) { try { const data = JSON.parse(response); if (data && data.audigent_segments) { - setData(data); - let dataToReturn = adUnits.reduce((rp, cau) => { - const adUnitCode = cau && cau.code; - if (!adUnitCode) { return rp } - rp[adUnitCode] = data; - return rp; - }, {}); - - onDone(dataToReturn); + addSegmentData(adUnits, data.audigent_segments); + onDone(); + storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); } else { - onDone({}); + onDone(); } } catch (err) { utils.logError('unable to parse audigent segment data'); - onDone({}) + onDone(); } } else if (req.status === 204) { - // unrecognized site key - onDone({}); + // unrecognized partner config + onDone(); } }, error: function () { - onDone({}); + onDone(); utils.logError('unable to get audigent segment data'); } - } + }, + JSON.stringify({'userIds': userIds, 'config': reqParams}), + {contentType: 'application/json'} ); } +/** + * module init + * @param {Object} config + * @return {boolean} + */ +export function init(config) { + return true; +} + /** @type {RtdSubmodule} */ export const audigentSubmodule = { - /** - * used to link submodule with realTimeData - * @type {string} - */ - name: 'audigent', - /** - * get data and send back to realTimeData module - * @function - * @param {adUnit[]} adUnits - * @param {function} onDone - */ - getData: getSegments + name: SUBMODULE_NAME, + getBidRequestData: getSegments, + init: init }; -export function init(config) { - const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { - try { - _moduleParams = realTimeData.dataProviders && realTimeData.dataProviders.filter(pr => pr.name && pr.name.toLowerCase() === 'audigent')[0].params; - _moduleParams.auctionDelay = realTimeData.auctionDelay; - } catch (e) { - _moduleParams = {}; - } - confListener(); - }); -} - -submodule('realTimeData', audigentSubmodule); -init(config); +submodule(MODULE_NAME, audigentSubmodule); diff --git a/modules/audigentRtdProvider.md b/modules/audigentRtdProvider.md index 47bcbbbf951..03e647f651d 100644 --- a/modules/audigentRtdProvider.md +++ b/modules/audigentRtdProvider.md @@ -1,18 +1,40 @@ +## Audigent Real-time Data Submodule + Audigent is a next-generation data management platform and a first-of-a-kind "data agency" containing some of the most exclusive content-consuming audiences across desktop, mobile and social platforms. -This real-time data module provides first-party Audigent segments that can be +This real-time data module provides quality user segmentation that can be attached to bid request objects destined for different SSPs in order to optimize targeting. Audigent maintains a large database of first-party Tradedesk Unified -ID to third party segment mappings that can now be queried at bid-time. +ID, Audigent Halo ID and other id provider mappings to various third-party +segment types that are utilizable across different SSPs. With this module, +these segments can be retrieved and supplied to the SSP in real-time during +the bid request cycle. -Usage: +### Usage Compile the audigent RTD module into your Prebid build: `gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` +Configure Prebid to add the Audigent RTD Segment Handler: +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "audigent", + waitForIt: true + } + ] + } + ... +} +``` + Audigent segments will then be attached to each bid request objects in `bid.realTimeData.audigent_segments` @@ -35,13 +57,15 @@ function addAudigentSegments() { for (i = 0; i < adUnits.length; i++) { let adUnit = adUnits[i]; for (j = 0; j < adUnit.bids.length; j++) { - adUnit.bids[j].userId.lipb.segments = adUnit.bids[j].realTimeData.audigent_segments['rubicon']; + adUnit.bids[j].userId.lipb.segments = adUnit.bids[j].audigent_segments['rubicon']; } } } ``` -To view an example of the segments returned by Audigent's backends: +### Testing + +To view an example of available segments returned by Audigent's backends: `gulp serve --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` diff --git a/package-lock.json b/package-lock.json index 1784b885be9..81ff16fdc5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.8.0-pre", + "version": "4.11.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -7796,13 +7796,22 @@ "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "object.assign": "^4.1.0" + }, + "dependencies": { + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } } }, "es-array-method-boxes-properly": { @@ -7838,6 +7847,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -12076,7 +12086,7 @@ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "dev": true, "requires": { - "has-symbols": "^1.0.1" + "has": "^1.0.3" } }, "is-relative": { @@ -17484,7 +17494,8 @@ "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true }, "object-is": { "version": "1.1.2", @@ -20696,26 +20707,6 @@ "es-abstract": "^1.17.0-next.1" } }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", From 291caa15ac86c2ac0bd5e327e0632b8aa89db731 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Tue, 20 Oct 2020 12:21:18 -0400 Subject: [PATCH 086/190] [AD-963] - Update JW Player RTD Provider for compliance with RTD Module Phase 3 (#5844) * updates grid adapter * adds response to bids * separates responsibilities * refactos success block * renames functions * tests getCache and formatting * tests data enrichment * adds tests for bid enhancement * updates documentation * adds clarification that sample params are placeholders * adds instructions to replace placeholder ids in example Co-authored-by: karimJWP --- .../gpt/jwplayerRtdProvider_example.html | 5 +- modules/gridBidAdapter.js | 12 +- modules/jwplayerRtdProvider.js | 217 +++++---- modules/jwplayerRtdProvider.md | 72 +-- test/spec/modules/gridBidAdapter_spec.js | 10 +- test/spec/modules/jwplayerRtdProvider_spec.js | 429 ++++++++++++------ 6 files changed, 479 insertions(+), 266 deletions(-) diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html index 3791ab42137..e47f3ca45ec 100644 --- a/integrationExamples/gpt/jwplayerRtdProvider_example.html +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -12,6 +12,7 @@ var adUnits = [{ code: 'div-gpt-ad-1460505748561-0', jwTargeting: { + // Note: the following Ids are placeholders and should be replaced with your Ids. playerID: '123', mediaID: 'abc' }, @@ -32,7 +33,6 @@ var pbjs = pbjs || {}; pbjs.que = pbjs.que || []; - + + + Reconciliation RTD Provider Example + + + + + + + + +
Div-1
+
+ +
+ + diff --git a/modules/.submodules.json b/modules/.submodules.json index 91cda9d95ad..78fd9376dd1 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -26,6 +26,7 @@ "rtdModule": [ "browsiRtdProvider", "audigentRtdProvider", - "jwplayerRtdProvider" + "jwplayerRtdProvider", + "reconciliationRtdProvider" ] } diff --git a/modules/reconciliationRtdProvider.js b/modules/reconciliationRtdProvider.js new file mode 100644 index 00000000000..20acb6a535a --- /dev/null +++ b/modules/reconciliationRtdProvider.js @@ -0,0 +1,318 @@ +/** + * This module adds reconciliation provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will add custom targetings to ad units + * The module will listen to post messages from rendered creatives with Reconciliation Tag + * The module will call tracking pixels to log info needed for reconciliation matching + * @module modules/reconciliationRtdProvider + * @requires module:modules/realTimeData + */ + +/** + * @typedef {Object} ModuleParams + * @property {string} publisherMemberId + * @property {?string} initUrl + * @property {?string} impressionUrl + * @property {?boolean} allowAccess + */ + +import { submodule } from '../src/hook.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import * as utils from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; + +/** @type {Object} */ +const MessageType = { + IMPRESSION_REQUEST: 'rsdk:impression:req', + IMPRESSION_RESPONSE: 'rsdk:impression:res', +}; +/** @type {ModuleParams} */ +const DEFAULT_PARAMS = { + initUrl: 'https://confirm.fiduciadlt.com/init', + impressionUrl: 'https://confirm.fiduciadlt.com/imp', + allowAccess: false, +}; +/** @type {ModuleParams} */ +let _moduleParams = {}; + +/** + * Handle postMesssage from ad creative, track impression + * and send response to reconciliation ad tag + * @param {Event} e + */ +function handleAdMessage(e) { + let data = {}; + let adUnitId = ''; + let adDeliveryId = ''; + + try { + data = JSON.parse(e.data); + } catch (e) { + return; + } + + if (data.type === MessageType.IMPRESSION_REQUEST) { + if (utils.isGptPubadsDefined()) { + // 1. Find the last iframed window before window.top where the tracker was injected + // (the tracker could be injected in nested iframes) + const adWin = getTopIFrameWin(e.source); + if (adWin && adWin !== window.top) { + // 2. Find the GPT slot for the iframed window + const adSlot = getSlotByWin(adWin); + // 3. Get AdUnit IDs for the selected slot + if (adSlot) { + adUnitId = adSlot.getAdUnitPath(); + adDeliveryId = adSlot.getTargeting('RSDK_ADID'); + adDeliveryId = adDeliveryId.length + ? adDeliveryId[0] + : utils.generateUUID(); + } + } + } + + // Call local impression callback + const args = Object.assign({}, data.args, { + publisherDomain: window.location.hostname, + publisherMemberId: _moduleParams.publisherMemberId, + adUnitId, + adDeliveryId, + }); + + track.trackGet(_moduleParams.impressionUrl, args); + + // Send response back to the Advertiser tag + let response = { + type: MessageType.IMPRESSION_RESPONSE, + id: data.id, + args: Object.assign( + { + publisherDomain: window.location.hostname, + }, + data.args + ), + }; + + // If access is allowed - add ad unit id to response + if (_moduleParams.allowAccess) { + Object.assign(response.args, { + adUnitId, + adDeliveryId, + }); + } + + e.source.postMessage(JSON.stringify(response), '*'); + } +} + +/** + * Get top iframe window for nested Window object + * - top + * -- iframe.window <-- top iframe window + * --- iframe.window + * ---- iframe.window <-- win + * + * @param {Window} win nested iframe window object + * @param {Window} topWin top window + */ +export function getTopIFrameWin(win, topWin) { + topWin = topWin || window; + + if (!win) { + return null; + } + + try { + while (win.parent !== topWin) { + win = win.parent; + } + return win; + } catch (e) { + return null; + } +} + +/** + * get all slots on page + * @return {Object[]} slot GoogleTag slots + */ +function getAllSlots() { + return utils.isGptPubadsDefined() && window.googletag.pubads().getSlots(); +} + +/** + * get GPT slot by placement id + * @param {string} code placement id + * @return {?Object} + */ +function getSlotByCode(code) { + const slots = getAllSlots(); + if (!slots || !slots.length) { + return null; + } + return ( + find( + slots, + (s) => s.getSlotElementId() === code || s.getAdUnitPath() === code + ) || null + ); +} + +/** + * get GPT slot by iframe window + * @param {Window} win + * @return {?Object} + */ +export function getSlotByWin(win) { + const slots = getAllSlots(); + + if (!slots || !slots.length) { + return null; + } + + return ( + find(slots, (s) => { + let slotElement = document.getElementById(s.getSlotElementId()); + + if (slotElement) { + let slotIframe = slotElement.querySelector('iframe'); + + if (slotIframe && slotIframe.contentWindow === win) { + return true; + } + } + + return false; + }) || null + ); +} + +/** + * serialize object and return query params string + * @param {Object} data + * @return {string} + */ +export function stringify(query) { + const parts = []; + + for (let key in query) { + if (query.hasOwnProperty(key)) { + let val = query[key]; + if (typeof query[key] !== 'object') { + parts.push(`${key}=${encodeURIComponent(val)}`); + } else { + parts.push(`${key}=${encodeURIComponent(stringify(val))}`); + } + } + } + return parts.join('&'); +} +/** + * Init Reconciliation post messages listeners to handle + * impressions messages from ad creative + */ +function initListeners() { + window.addEventListener('message', handleAdMessage, false); +} + +/** + * Send init event to log + * @param {Array} adUnits + */ +function trackInit(adUnits) { + track.trackPost( + _moduleParams.initUrl, + { + adUnits, + publisherDomain: window.location.hostname, + publisherMemberId: _moduleParams.publisherMemberId, + } + ); +} + +/** + * Track event via POST request + * wrap method to allow stubbing in tests + * @param {string} url + * @param {Object} data + */ +export const track = { + trackGet(url, data) { + utils.triggerPixel(`${url}?${stringify(data)}`); + }, + trackPost(url, data) { + const ajax = ajaxBuilder(); + + ajax( + url, + function() {}, + JSON.stringify(data), + { + method: 'POST', + } + ); + } +} + +/** + * Set custom targetings for provided adUnits + * @param {string[]} adUnitsCodes + * @return {Object} key-value object with custom targetings + */ +function getReconciliationData(adUnitsCodes) { + const dataToReturn = {}; + const adUnitsToTrack = []; + + adUnitsCodes.forEach((adUnitCode) => { + if (!adUnitCode) { + return; + } + + const adSlot = getSlotByCode(adUnitCode); + const adUnitId = adSlot ? adSlot.getAdUnitPath() : adUnitCode; + const adDeliveryId = utils.generateUUID(); + + dataToReturn[adUnitCode] = { + RSDK_AUID: adUnitId, + RSDK_ADID: adDeliveryId, + }; + + adUnitsToTrack.push({ + adUnitId, + adDeliveryId + }); + }, {}); + + // Track init event + trackInit(adUnitsToTrack); + + return dataToReturn; +} + +/** @type {RtdSubmodule} */ +export const reconciliationSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: 'reconciliation', + /** + * get data and send back to realTimeData module + * @function + * @param {string[]} adUnitsCodes + */ + getTargetingData: getReconciliationData, + init: init, +}; + +function init(moduleConfig) { + const params = moduleConfig.params; + if (params && params.publisherMemberId) { + _moduleParams = Object.assign({}, DEFAULT_PARAMS, params); + initListeners(); + } else { + utils.logError('missing params for Reconciliation provider'); + } + return true; +} + +submodule('realTimeData', reconciliationSubmodule); diff --git a/modules/reconciliationRtdProvider.md b/modules/reconciliationRtdProvider.md new file mode 100644 index 00000000000..53883ad99eb --- /dev/null +++ b/modules/reconciliationRtdProvider.md @@ -0,0 +1,49 @@ +The purpose of this Real Time Data Provider is to allow publishers to match impressions accross the supply chain. + +**Reconciliation SDK** +The purpose of Reconciliation SDK module is to collect supply chain structure information and vendor-specific impression IDs from suppliers participating in ad creative delivery and report it to the Reconciliation Service, allowing publishers, advertisers and other supply chain participants to match and reconcile ad server, SSP, DSP and veritifation system log file records. Reconciliation SDK was created as part of TAG DLT initiative ( https://www.tagtoday.net/pressreleases/dlt_9_7_2020 ). + +**Usage for Publishers:** + +Compile the Reconciliation Provider into your Prebid build: + +`gulp build --modules=reconciliationRtdProvider` + +Add Reconciliation real time data provider configuration by setting up a Prebid Config: + +```javascript +const reconciliationDataProvider = { + name: "reconciliation", + params: { + publisherMemberId: "test_prebid_publisher", // required + allowAccess: true, //optional + } +}; + +pbjs.setConfig({ + ..., + realTimeData: { + dataProviders: [ + reconciliationDataProvider + ] + } +}); +``` + +where: +- `publisherMemberId` (required) - ID associated with the publisher +- `access` (optional) true/false - Whether ad markup will recieve Ad Unit Id's via Reconciliation Tag + +**Example:** + +To view an example: + +- in your cli run: + +`gulp serve --modules=reconciliationRtdProvider,appnexusBidAdapter` + +Your could also change 'appnexusBidAdapter' to another one. + +- in your browser, navigate to: + +`http://localhost:9999/integrationExamples/gpt/reconciliationRtdProvider_example.html` diff --git a/test/spec/modules/reconciliationRtdProvider_spec.js b/test/spec/modules/reconciliationRtdProvider_spec.js new file mode 100644 index 00000000000..8adca28248c --- /dev/null +++ b/test/spec/modules/reconciliationRtdProvider_spec.js @@ -0,0 +1,229 @@ +import { + reconciliationSubmodule, + track, + stringify, + getTopIFrameWin, + getSlotByWin +} from 'modules/reconciliationRtdProvider.js'; +import { makeSlot } from '../integration/faker/googletag.js'; +import * as utils from 'src/utils.js'; + +describe('Reconciliation Real time data submodule', function () { + const conf = { + dataProviders: [{ + 'name': 'reconciliation', + 'params': { + 'publisherMemberId': 'test_prebid_publisher' + }, + }] + }; + + let trackPostStub, trackGetStub; + + beforeEach(function () { + trackPostStub = sinon.stub(track, 'trackPost'); + trackGetStub = sinon.stub(track, 'trackGet'); + }); + + afterEach(function () { + trackPostStub.restore(); + trackGetStub.restore(); + }); + + describe('reconciliationSubmodule', function () { + describe('initialization', function () { + let utilsLogErrorSpy; + + before(function () { + utilsLogErrorSpy = sinon.spy(utils, 'logError'); + }); + + after(function () { + utils.logError.restore(); + }); + + it('successfully instantiates', function () { + expect(reconciliationSubmodule.init(conf.dataProviders[0])).to.equal(true); + }); + + it('should log error if initializied without parameters', function () { + expect(reconciliationSubmodule.init({'name': 'reconciliation', 'params': {}})).to.equal(true); + expect(utilsLogErrorSpy.calledOnce).to.be.true; + }); + }); + + describe('getData', function () { + it('should return data in proper format', function () { + makeSlot({code: '/reconciliationAdunit1', divId: 'reconciliationAd1'}); + + const targetingData = reconciliationSubmodule.getTargetingData(['/reconciliationAdunit1']); + expect(targetingData['/reconciliationAdunit1'].RSDK_AUID).to.eql('/reconciliationAdunit1'); + expect(targetingData['/reconciliationAdunit1'].RSDK_ADID).to.be.a('string'); + }); + + it('should return unit path if called with divId', function () { + makeSlot({code: '/reconciliationAdunit2', divId: 'reconciliationAd2'}); + + const targetingData = reconciliationSubmodule.getTargetingData(['reconciliationAd2']); + expect(targetingData['reconciliationAd2'].RSDK_AUID).to.eql('/reconciliationAdunit2'); + expect(targetingData['reconciliationAd2'].RSDK_ADID).to.be.a('string'); + }); + + it('should skip empty adUnit id', function () { + makeSlot({code: '/reconciliationAdunit3', divId: 'reconciliationAd3'}); + + const targetingData = reconciliationSubmodule.getTargetingData(['reconciliationAd3', '']); + expect(targetingData).to.have.all.keys('reconciliationAd3'); + }); + }); + + describe('track events', function () { + it('should track init event with data', function () { + const adUnit = { + code: '/adunit' + }; + + reconciliationSubmodule.getTargetingData([adUnit.code]); + + expect(trackPostStub.calledOnce).to.be.true; + expect(trackPostStub.getCalls()[0].args[0]).to.eql('https://confirm.fiduciadlt.com/init'); + expect(trackPostStub.getCalls()[0].args[1].adUnits[0].adUnitId).to.eql(adUnit.code); + expect(trackPostStub.getCalls()[0].args[1].adUnits[0].adDeliveryId).be.a('string'); + expect(trackPostStub.getCalls()[0].args[1].publisherMemberId).to.eql('test_prebid_publisher'); + }); + }); + + describe('stringify parameters', function () { + it('should return query for flat object', function () { + const parameters = { + adUnitId: '/adunit', + adDeliveryId: '12345' + }; + + expect(stringify(parameters)).to.eql('adUnitId=%2Fadunit&adDeliveryId=12345'); + }); + + it('should return query with nested parameters', function () { + const parameters = { + adUnitId: '/adunit', + adDeliveryId: '12345', + ext: { + adSize: '300x250', + adType: 'banner' + } + }; + + expect(stringify(parameters)).to.eql('adUnitId=%2Fadunit&adDeliveryId=12345&ext=adSize%3D300x250%26adType%3Dbanner'); + }); + }); + + describe('get topmost iframe', function () { + /** + * - top + * -- iframe.window <-- top iframe window + * --- iframe.window + * ---- iframe.window <-- win + */ + const mockFrameWin = (topWin, parentWin) => { + return { + top: topWin, + parent: parentWin + } + } + + it('should return null if called with null', function() { + expect(getTopIFrameWin(null)).to.be.null; + }); + + it('should return null if there is an error in frames chain', function() { + const topWin = {}; + const iframe1Win = mockFrameWin(topWin, null); // break chain + const iframe2Win = mockFrameWin(topWin, iframe1Win); + + expect(getTopIFrameWin(iframe1Win, topWin)).to.be.null; + }); + + it('should get the topmost iframe', function () { + const topWin = {}; + const iframe1Win = mockFrameWin(topWin, topWin); + const iframe2Win = mockFrameWin(topWin, iframe1Win); + + expect(getTopIFrameWin(iframe2Win, topWin)).to.eql(iframe1Win); + }); + }); + + describe('get slot by nested iframe window', function () { + it('should return the slot', function () { + const adSlotElement = document.createElement('div'); + const adSlotIframe = document.createElement('iframe'); + + adSlotElement.id = 'reconciliationAd'; + adSlotElement.appendChild(adSlotIframe); + document.body.appendChild(adSlotElement); + + const adSlot = makeSlot({code: '/reconciliationAdunit', divId: adSlotElement.id}); + + expect(getSlotByWin(adSlotIframe.contentWindow)).to.eql(adSlot); + }); + + it('should return null if the slot is not found', function () { + const adSlotElement = document.createElement('div'); + const adSlotIframe = document.createElement('iframe'); + + adSlotElement.id = 'reconciliationAd'; + document.body.appendChild(adSlotElement); + document.body.appendChild(adSlotIframe); // iframe is not in ad slot + + const adSlot = makeSlot({code: '/reconciliationAdunit', divId: adSlotElement.id}); + + expect(getSlotByWin(adSlotIframe.contentWindow)).to.be.null; + }); + }); + + describe('handle postMessage from Reconciliation Tag in ad iframe', function () { + it('should track impression pixel with parameters', function (done) { + const adSlotElement = document.createElement('div'); + const adSlotIframe = document.createElement('iframe'); + + adSlotElement.id = 'reconciliationAdMessage'; + adSlotElement.appendChild(adSlotIframe); + document.body.appendChild(adSlotElement); + + const adSlot = makeSlot({code: '/reconciliationAdunit', divId: adSlotElement.id}); + // Fix targeting methods + adSlot.targeting = {}; + adSlot.setTargeting = function(key, value) { + this.targeting[key] = [value]; + }; + adSlot.getTargeting = function(key) { + return this.targeting[key]; + }; + + adSlot.setTargeting('RSDK_AUID', '/reconciliationAdunit'); + adSlot.setTargeting('RSDK_ADID', '12345'); + adSlotIframe.contentDocument.open(); + adSlotIframe.contentDocument.write(``); + adSlotIframe.contentDocument.close(); + + setTimeout(() => { + expect(trackGetStub.calledOnce).to.be.true; + expect(trackGetStub.getCalls()[0].args[0]).to.eql('https://confirm.fiduciadlt.com/imp'); + expect(trackGetStub.getCalls()[0].args[1].adUnitId).to.eql('/reconciliationAdunit'); + expect(trackGetStub.getCalls()[0].args[1].adDeliveryId).to.eql('12345'); + expect(trackGetStub.getCalls()[0].args[1].sourceMemberId).to.eql('test_member_id'); ; + expect(trackGetStub.getCalls()[0].args[1].sourceImpressionId).to.eql('123'); ; + expect(trackGetStub.getCalls()[0].args[1].publisherMemberId).to.eql('test_prebid_publisher'); + done(); + }, 100); + }); + }); + }); +}); From 01eb9534d2af678e129bd44d514bf859743638b6 Mon Sep 17 00:00:00 2001 From: sdao-tl <49252703+sdao-tl@users.noreply.github.com> Date: Wed, 21 Oct 2020 01:54:33 -0700 Subject: [PATCH 088/190] Update instream logic to account for multimp (#5872) * initial commit, instream poc done * push in poc changes * push in poc changes * restore instream.html * push in poc changes * restore instream.html * restore instream.html v2 * adding instream unit tests v1 * catch up to bidfloor changes * unit tests finalized! * update adapter md * add support for mediaTypes.video * merge in prebid master * add instream validation * add unit test for instream validation Co-authored-by: Sy Dao --- modules/tripleliftBidAdapter.js | 13 ++++++- .../spec/modules/tripleliftBidAdapter_spec.js | 37 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index b003de7785f..77a313a0f55 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -119,7 +119,8 @@ function _buildPostBody(bidRequests) { tagid: bidRequest.params.inventoryCode, floor: _getFloor(bidRequest) }; - if (bidRequest.mediaTypes.video) { + // remove the else to support multi-imp + if (_isInstreamBidRequest(bidRequest)) { imp.video = _getORTBVideo(bidRequest); } else if (bidRequest.mediaTypes.banner) { imp.banner = { format: _sizes(bidRequest.sizes) }; @@ -147,6 +148,16 @@ function _buildPostBody(bidRequests) { return data; } +function _isInstreamBidRequest(bidRequest) { + if (!bidRequest.mediaTypes.video) return false; + if (!bidRequest.mediaTypes.video.context) return false; + if (bidRequest.mediaTypes.video.context.toLowerCase() === 'instream') { + return true; + } else { + return false; + } +} + function _getORTBVideo(bidRequest) { // give precedent to mediaTypes.video let video = { ...bidRequest.params.video, ...bidRequest.mediaTypes.video }; diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 797b3fab0c1..96cab3d837c 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -164,6 +164,39 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + }, + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + }, + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, } ]; @@ -228,6 +261,10 @@ describe('triplelift adapter', function () { expect(payload.imp[1].tagid).to.equal('insteam_test'); expect(payload.imp[1].floor).to.equal(1.0); expect(payload.imp[1].video).to.exist.and.to.be.a('object'); + + expect(payload.imp[2]).to.not.have.property('video'); + expect(payload.imp[2]).to.have.property('banner'); + expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); }); it('should add tdid to the payload if included', function () { From 3423e7b12233a55e5b0382c03a657e86b6756932 Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Wed, 21 Oct 2020 10:24:27 +0100 Subject: [PATCH 089/190] Verizon Media user id module (#5786) * Initial work on Verizon Media User ID module * Submodule tests * Add sample eid object for Verizon Media * Documentation update * Switch to HTTP GET, update tests. * Remove single test restriction. * Documentation update * Addressing initial PR feedback. * Accept pixelId parameter to construct VMUID URL * Fix tests following API signature change * Add IAB vendor ID Co-authored-by: slimkrazy --- modules/.submodules.json | 3 +- modules/userId/eids.js | 6 + modules/userId/eids.md | 12 ++ modules/verizonMediaIdSystem.js | 103 ++++++++++ modules/verizonMediaSystemId.md | 33 ++++ .../spec/modules/verizonMediaIdSystem_spec.js | 182 ++++++++++++++++++ 6 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 modules/verizonMediaIdSystem.js create mode 100644 modules/verizonMediaSystemId.md create mode 100644 test/spec/modules/verizonMediaIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 78fd9376dd1..9b523a0c73a 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -17,7 +17,8 @@ "haloIdSystem", "quantcastIdSystem", "idxIdSystem", - "fabrickIdSystem" + "fabrickIdSystem", + "verizonMediaIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/userId/eids.js b/modules/userId/eids.js index f6c58a5a0bf..8118607fbde 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -157,6 +157,12 @@ const USER_IDS_CONFIG = { source: 'idx.lat', atype: 1 }, + + // Verizon Media + 'vmuid': { + source: 'verizonmedia.com', + atype: 1 + } }; // this function will create an eid object for the given UserId sub-module diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 7dc149cd47a..3e51eff3165 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -90,6 +90,7 @@ userIdAsEids = [ atype: 1 }] }, + { source: 'sharedid.org', uids: [{ @@ -100,6 +101,7 @@ userIdAsEids = [ } }] }, + { source: 'zeotap.com', uids: [{ @@ -107,6 +109,7 @@ userIdAsEids = [ atype: 1 }] }, + { source: 'audigent.com', uids: [{ @@ -114,12 +117,21 @@ userIdAsEids = [ atype: 1 }] }, + { source: 'quantcast.com', uids: [{ id: 'some-random-id-value', atype: 1 }] + }, + + { + source: 'verizonmedia.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] } ] ``` diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js new file mode 100644 index 00000000000..617561765cc --- /dev/null +++ b/modules/verizonMediaIdSystem.js @@ -0,0 +1,103 @@ +/** + * This module adds verizonMediaId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/verizonMediaIdSystem + * @requires module:modules/userId + */ + +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; +import * as utils from '../src/utils.js'; + +const MODULE_NAME = 'verizonMediaId'; +const VENDOR_ID = 25; +const PLACEHOLDER = '__PIXEL_ID__'; +const VMUID_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; + +function isEUConsentRequired(consentData) { + return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); +} + +/** @type {Submodule} */ +export const verizonMediaIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * Vendor id of Verizon Media EMEA Limited + * @type {Number} + */ + gvlid: VENDOR_ID, + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{vmuid: string} | undefined} + */ + decode(value) { + return (value && typeof value.vmuid === 'string') ? {vmuid: value.vmuid} : undefined; + }, + /** + * get the VerizonMedia Id + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @returns {IdResponse|undefined} + */ + getId(config, consentData) { + const params = config.params || {}; + if (!params || typeof params.he !== 'string' || + (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) { + utils.logError('The verizonMediaId submodule requires the \'he\' and \'pixelId\' parameters to be defined.'); + return; + } + + const data = { + '1p': [1, '1', true].includes(params['1p']) ? '1' : '0', + he: params.he, + gdpr: isEUConsentRequired(consentData) ? '1' : '0', + euconsent: isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', + us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' + }; + + if (params.pixelId) { + data.pixelId = params.pixelId + } + + const resp = function (callback) { + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + } + } + callback(responseObj); + }, + error: error => { + utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + const endpoint = VMUID_ENDPOINT.replace(PLACEHOLDER, params.pixelId); + let url = `${params.endpoint || endpoint}?${utils.formatQS(data)}`; + verizonMediaIdSubmodule.getAjaxFn()(url, callbacks, null, {method: 'GET', withCredentials: true}); + }; + return {callback: resp}; + }, + + /** + * Return the function used to perform XHR calls. + * Utilised for each of testing. + * @returns {Function} + */ + getAjaxFn() { + return ajax; + } +}; + +submodule('userId', verizonMediaIdSubmodule); diff --git a/modules/verizonMediaSystemId.md b/modules/verizonMediaSystemId.md new file mode 100644 index 00000000000..8d0e0bddaa9 --- /dev/null +++ b/modules/verizonMediaSystemId.md @@ -0,0 +1,33 @@ +## Verizon Media User ID Submodule + +Verizon Media User ID Module. + +### Prebid Params + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'verizonMediaId', + storage: { + name: 'vmuid', + type: 'html5', + expires: 30 + }, + params: { + pixelId: 58776, + he: '0bef996248d63cea1529cb86de31e9547a712d9f380146e98bbd39beec70355a' + } + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the Verizon Media User ID Module integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Verizon Media module - `"verizonMediaId"` | `"verizonMediaId"` | +| params | Required | Object | Data for Verizon Media ID initialization. | | +| params.pixelId | Required | Number | The Verizon Media supplied publisher specific pixel Id | `8976` | +| params.he | Required | String | The SHA-256 hashed user email address | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js new file mode 100644 index 00000000000..a30be5a2569 --- /dev/null +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -0,0 +1,182 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils.js'; +import {verizonMediaIdSubmodule} from 'modules/verizonMediaIdSystem.js'; + +describe('Verizon Media ID Submodule', () => { + const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; + const PIXEL_ID = '1234'; + const PROD_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PIXEL_ID}/fed`; + const OVERRIDE_ENDPOINT = 'https://foo/bar'; + + it('should have the correct module name declared', () => { + expect(verizonMediaIdSubmodule.name).to.equal('verizonMediaId'); + }); + + it('should have the correct TCFv2 Vendor ID declared', () => { + expect(verizonMediaIdSubmodule.gvlid).to.equal(25); + }); + + describe('getId()', () => { + let ajaxStub; + let getAjaxFnStub; + let consentData; + beforeEach(() => { + ajaxStub = sinon.stub(); + getAjaxFnStub = sinon.stub(verizonMediaIdSubmodule, 'getAjaxFn'); + getAjaxFnStub.returns(ajaxStub); + + consentData = { + gdpr: { + gdprApplies: 1, + consentString: 'GDPR_CONSENT_STRING' + }, + uspConsent: 'USP_CONSENT_STRING' + }; + }); + + afterEach(() => { + getAjaxFnStub.restore(); + }); + + function invokeGetIdAPI(configParams, consentData) { + let result = verizonMediaIdSubmodule.getId({ + params: configParams + }, consentData); + if (typeof result === 'object') { + result.callback(sinon.stub()); + } + return result; + } + + it('returns undefined if he and pixelId params are not passed', () => { + expect(invokeGetIdAPI({}, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); + + it('returns undefined if the pixelId param is not passed', () => { + expect(invokeGetIdAPI({ + he: HASHED_EMAIL + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); + + it('returns undefined if the he param is not passed', () => { + expect(invokeGetIdAPI({ + pixelId: PIXEL_ID + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); + + it('returns an object with the callback function if the correct params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('Makes an ajax GET request to the production API endpoint with query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + euconsent: consentData.gdpr.consentString, + us_privacy: consentData.uspConsent + }; + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the specified override API endpoint with query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + '1p': '0', + gdpr: '1', + euconsent: consentData.gdpr.consentString, + us_privacy: consentData.uspConsent + }; + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('sets the callbacks param of the ajax function call correctly', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); + }); + + it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('1'); + expect(requestQueryParams.euconsent).to.equal(consentData.gdpr.consentString); + }); + + it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { + consentData.gdpr.gdprApplies = false; + + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('0'); + expect(requestQueryParams.euconsent).to.equal(''); + }); + + [1, '1', true].forEach(firstPartyParamValue => { + it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { + invokeGetIdAPI({ + '1p': firstPartyParamValue, + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams['1p']).to.equal('1'); + }); + }); + }); + + describe('decode()', () => { + const VALID_API_RESPONSE = { + vmuid: '1234' + }; + it('should return a newly constructed object with the vmuid property', () => { + expect(verizonMediaIdSubmodule.decode(VALID_API_RESPONSE)).to.deep.equal(VALID_API_RESPONSE); + expect(verizonMediaIdSubmodule.decode(VALID_API_RESPONSE)).to.not.equal(VALID_API_RESPONSE); + }); + + [{}, '', {foo: 'bar'}].forEach((response) => { + it(`should return undefined for an invalid response "${JSON.stringify(response)}"`, () => { + expect(verizonMediaIdSubmodule.decode(response)).to.be.undefined; + }); + }); + }); +}); From 820ec1d80fa1592e6e4ceab42efb2db92a9bc0f2 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Wed, 21 Oct 2020 17:11:34 +0300 Subject: [PATCH 090/190] Use new ad request format by default in TheMediaGrid Bid Adapter (#5840) * 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 --- modules/gridBidAdapter.js | 583 ++++++++++------------- modules/gridBidAdapter.md | 3 +- test/spec/modules/gridBidAdapter_spec.js | 500 +++++++------------ 3 files changed, 413 insertions(+), 673 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index a3cd5b3cf56..5436a18c6cb 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -5,8 +5,7 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; const BIDDER_CODE = 'grid'; -const ENDPOINT_URL = 'https://grid.bidswitch.net/hb'; -const NEW_ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; +const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -44,19 +43,231 @@ export const spec = { * @return {ServerRequest[]} Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const oldFormatBids = []; - const newFormatBids = []; + if (!validBidRequests.length) { + return null; + } + let pageKeywords = null; + let jwpseg = null; + let content = null; + let schain = null; + let userId = null; + let user = null; + let userExt = null; + let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; + + const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + const imp = []; + const bidsMap = {}; + validBidRequests.forEach((bid) => { - bid.params.useNewFormat ? newFormatBids.push(bid) : oldFormatBids.push(bid); + if (!bidderRequestId) { + bidderRequestId = bid.bidderRequestId; + } + if (!auctionId) { + auctionId = bid.auctionId; + } + if (!schain) { + schain = bid.schain; + } + if (!userId) { + userId = bid.userId; + } + const {params: {uid, keywords, bidFloor}, mediaTypes, bidId, adUnitCode, jwTargeting} = bid; + bidsMap[bidId] = bid; + if (!pageKeywords && !utils.isEmpty(keywords)) { + pageKeywords = utils.transformBidderParamKeywords(keywords); + } + if (jwTargeting) { + if (!jwpseg && jwTargeting.segments) { + jwpseg = jwTargeting.segments; + } + if (!content && jwTargeting.content) { + content = jwTargeting.content; + } + } + let impObj = { + id: bidId, + tagid: uid.toString(), + ext: { + divid: adUnitCode + }, + bidfloor: _getFloor(mediaTypes || {}, bidFloor, bid) + }; + + if (!mediaTypes || mediaTypes[BANNER]) { + const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); + if (banner) { + impObj.banner = banner; + } + } + if (mediaTypes && mediaTypes[VIDEO]) { + const video = createVideoRequest(bid, mediaTypes[VIDEO]); + if (video) { + impObj.video = video; + } + } + + if (impObj.banner || impObj.video) { + imp.push(impObj); + } }); - const requests = []; - if (newFormatBids.length) { - requests.push(buildNewRequest(newFormatBids, bidderRequest)); + + const source = { + tid: auctionId, + ext: { + wrapper: 'Prebid_js', + wrapper_version: '$prebid.version$' + } + }; + + if (schain) { + source.ext.schain = schain; } - if (oldFormatBids.length) { - requests.push(buildOldRequest(oldFormatBids, bidderRequest)); + + const bidderTimeout = config.getConfig('bidderTimeout') || timeout; + const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; + + let request = { + id: bidderRequestId, + site: { + page: referer + }, + tmax, + source, + imp + }; + + if (content) { + request.site.content = content; + } + + if (jwpseg && jwpseg.length) { + user = { + data: [{ + name: 'iow_labs_pub_data', + segment: jwpseg.map((seg) => { + return {name: 'jwpseg', value: seg}; + }) + }] + }; + } + + if (gdprConsent && gdprConsent.consentString) { + userExt = {consent: gdprConsent.consentString}; + } + + if (userId) { + if (userId.tdid) { + userExt = userExt || {}; + userExt.eids = userExt.eids || []; + userExt.eids.push({ + source: 'adserver.org', // Unified ID + uids: [{ + id: userId.tdid, + ext: { + rtiPartner: 'TDID' + } + }] + }); + } + if (userId.id5id && userId.id5id.uid) { + userExt = userExt || {}; + userExt.eids = userExt.eids || []; + userExt.eids.push({ + source: 'id5-sync.com', + uids: [{ + id: userId.id5id.uid + }], + ext: userId.id5id.ext + }); + } + if (userId.lipb && userId.lipb.lipbid) { + userExt = userExt || {}; + userExt.eids = userExt.eids || []; + userExt.eids.push({ + source: 'liveintent.com', + uids: [{ + id: userId.lipb.lipbid + }] + }); + } + if (userId.idl_env) { + userExt = userExt || {}; + userExt.eids = userExt.eids || []; + userExt.eids.push({ + source: 'identityLink', + uids: [{ + id: userId.idl_env + }] + }); + } + if (userId.criteoId) { + userExt = userExt || {}; + userExt.eids = userExt.eids || []; + userExt.eids.push({ + source: 'criteo.com', + uids: [{ + id: userId.criteoId + }] + }); + } + + if (userId.digitrustid && userId.digitrustid.data && userId.digitrustid.data.id) { + userExt = userExt || {}; + userExt.digitrust = Object.assign({}, userId.digitrustid.data); + } } - return requests; + + if (userExt && Object.keys(userExt).length) { + user = user || {}; + user.ext = userExt; + } + + if (user) { + request.user = user; + } + + const configKeywords = utils.transformBidderParamKeywords({ + 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + }); + + if (configKeywords.length) { + pageKeywords = (pageKeywords || []).concat(configKeywords); + } + + if (pageKeywords && pageKeywords.length > 0) { + pageKeywords.forEach(deleteValues); + } + + if (pageKeywords) { + request.ext = { + keywords: pageKeywords + }; + } + + if (gdprConsent && gdprConsent.gdprApplies) { + request.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 + } + } + } + + if (uspConsent) { + if (!request.regs) { + request.regs = {ext: {}}; + } + request.regs.ext.us_privacy = uspConsent; + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(request), + newFormat: true, + bidsMap + }; }, /** * Unpack the response from the server into a list of bids. @@ -108,6 +319,34 @@ export const spec = { } }; +/** + * Gets bidfloor + * @param {Object} mediaTypes + * @param {Number} bidfloor + * @param {Object} bid + * @returns {Number} floor + */ +function _getFloor (mediaTypes, bidfloor, bid) { + const curMediaType = mediaTypes.video ? 'video' : 'banner'; + let floor = bidfloor || 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: bid.sizes.map(([w, h]) => ({w, h})) + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + function isPopulatedArray(arr) { return !!(utils.isArray(arr) && arr.length > 0); } @@ -135,24 +374,7 @@ function _addBidResponse(serverBid, bidRequest, bidResponses) { if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { - let bid = null; - let slot = null; - const bidsMap = bidRequest.bidsMap; - if (bidRequest.newFormat) { - bid = bidsMap[serverBid.impid]; - } else { - const awaitingBids = bidsMap[serverBid.auid]; - if (awaitingBids) { - const sizeId = `${serverBid.w}x${serverBid.h}`; - if (awaitingBids[sizeId]) { - slot = awaitingBids[sizeId][0]; - bid = slot.bids.shift(); - } - } else { - errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid; - } - } - + const bid = bidRequest.bidsMap[serverBid.impid]; if (bid) { const bidResponse = { requestId: bid.bidId, // bid.bidderRequestId, @@ -184,21 +406,6 @@ function _addBidResponse(serverBid, bidRequest, bidResponses) { bidResponse.mediaType = BANNER; } bidResponses.push(bidResponse); - - if (slot && !slot.bids.length) { - slot.parents.forEach(({parent, key, uid}) => { - const index = parent[key].indexOf(slot); - if (index > -1) { - parent[key].splice(index, 1); - } - if (!parent[key].length) { - delete parent[key]; - if (!utils.getKeys(parent).length) { - delete bidsMap[uid]; - } - } - }); - } } } if (errorMessage) { @@ -206,294 +413,6 @@ function _addBidResponse(serverBid, bidRequest, bidResponses) { } } -function buildOldRequest(validBidRequests, bidderRequest) { - const auids = []; - const bidsMap = {}; - const slotsMapByUid = {}; - const sizeMap = {}; - const bids = validBidRequests || []; - let pageKeywords = null; - let reqId; - - bids.forEach(bid => { - reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode, mediaTypes} = bid; - auids.push(uid); - const sizesId = utils.parseSizesInput(bid.sizes); - - if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - pageKeywords = utils.transformBidderParamKeywords(bid.params.keywords); - } - - const addedSizes = {}; - sizesId.forEach((sizeId) => { - addedSizes[sizeId] = true; - }); - const bannerSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'banner.sizes')); - const videoSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'video.playerSize')); - bannerSizesId.concat(videoSizesId).forEach((sizeId) => { - if (!addedSizes[sizeId]) { - addedSizes[sizeId] = true; - sizesId.push(sizeId); - } - }); - - if (!slotsMapByUid[uid]) { - slotsMapByUid[uid] = {}; - } - const slotsMap = slotsMapByUid[uid]; - if (!slotsMap[adUnitCode]) { - slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []}; - } else { - slotsMap[adUnitCode].bids.push(bid); - } - const slot = slotsMap[adUnitCode]; - - sizesId.forEach((sizeId) => { - sizeMap[sizeId] = true; - if (!bidsMap[uid]) { - bidsMap[uid] = {}; - } - - if (!bidsMap[uid][sizeId]) { - bidsMap[uid][sizeId] = [slot]; - } else { - bidsMap[uid][sizeId].push(slot); - } - slot.parents.push({parent: bidsMap[uid], key: sizeId, uid}); - }); - }); - - const configKeywords = utils.transformBidderParamKeywords({ - 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, - 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null - }); - - if (configKeywords.length) { - pageKeywords = (pageKeywords || []).concat(configKeywords); - } - - if (pageKeywords && pageKeywords.length > 0) { - pageKeywords.forEach(deleteValues); - } - - const payload = { - auids: auids.join(','), - sizes: utils.getKeys(sizeMap).join(','), - r: reqId, - wrapperType: 'Prebid_js', - wrapperVersion: '$prebid.version$' - }; - - if (pageKeywords) { - payload.keywords = JSON.stringify(pageKeywords); - } - - if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; - } - if (bidderRequest.timeout) { - payload.wtimeout = bidderRequest.timeout; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - payload.gdpr_applies = - (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') - ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; - } - if (bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; - } - } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''), - bidsMap: bidsMap - } -} - -function buildNewRequest(validBidRequests, bidderRequest) { - let pageKeywords = null; - let jwpseg = null; - let content = null; - let schain = null; - let userId = null; - let user = null; - let userExt = null; - let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest; - - const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; - const imp = []; - const bidsMap = {}; - - validBidRequests.forEach((bid) => { - if (!bidderRequestId) { - bidderRequestId = bid.bidderRequestId; - } - if (!auctionId) { - auctionId = bid.auctionId; - } - if (!schain) { - schain = bid.schain; - } - if (!userId) { - userId = bid.userId; - } - const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, jwTargeting} = bid; - bidsMap[bidId] = bid; - if (!pageKeywords && !utils.isEmpty(keywords)) { - pageKeywords = utils.transformBidderParamKeywords(keywords); - } - if (jwTargeting) { - if (!jwpseg && jwTargeting.segments) { - jwpseg = jwTargeting.segments; - } - if (!content && jwTargeting.content) { - content = jwTargeting.content; - } - } - let impObj = { - id: bidId, - tagid: uid.toString(), - ext: { - divid: adUnitCode - } - }; - - if (!mediaTypes || mediaTypes[BANNER]) { - const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); - if (banner) { - impObj.banner = banner; - } - } - if (mediaTypes && mediaTypes[VIDEO]) { - const video = createVideoRequest(bid, mediaTypes[VIDEO]); - if (video) { - impObj.video = video; - } - } - - if (impObj.banner || impObj.video) { - imp.push(impObj); - } - }); - - const source = { - tid: auctionId, - ext: { - wrapper: 'Prebid_js', - wrapper_version: '$prebid.version$' - } - }; - - if (schain) { - source.ext.schain = schain; - } - - const tmax = config.getConfig('bidderTimeout') || timeout; - - let request = { - id: bidderRequestId, - site: { - page: referer - }, - tmax, - source, - imp - }; - - if (content) { - request.site.content = content; - } - - if (jwpseg && jwpseg.length) { - user = { - data: [{ - name: 'iow_labs_pub_data', - segment: jwpseg.map((seg) => { - return {name: 'jwpseg', value: seg}; - }) - }] - }; - } - - if (gdprConsent && gdprConsent.consentString) { - userExt = {consent: gdprConsent.consentString}; - } - - if (userId) { - userExt = userExt || {}; - if (userId.tdid) { - userExt.unifiedid = userId.tdid; - } - if (userId.id5id && userId.id5id.uid) { - userExt.id5id = userId.id5id.uid; - } - if (userId.digitrustid && userId.digitrustid.data && userId.digitrustid.data.id) { - userExt.digitrustid = userId.digitrustid.data.id; - } - if (userId.lipb && userId.lipb.lipbid) { - userExt.liveintentid = userId.lipb.lipbid; - } - } - - if (userExt && Object.keys(userExt).length) { - user = user || {}; - user.ext = userExt; - } - - if (user) { - request.user = user; - } - - const configKeywords = utils.transformBidderParamKeywords({ - 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, - 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null - }); - - if (configKeywords.length) { - pageKeywords = (pageKeywords || []).concat(configKeywords); - } - - if (pageKeywords && pageKeywords.length > 0) { - pageKeywords.forEach(deleteValues); - } - - if (pageKeywords) { - request.ext = { - keywords: pageKeywords - }; - } - - if (gdprConsent && gdprConsent.gdprApplies) { - request.regs = { - ext: { - gdpr: gdprConsent.gdprApplies ? 1 : 0 - } - } - } - - if (uspConsent) { - if (!request.regs) { - request.regs = {ext: {}}; - } - request.regs.ext.us_privacy = uspConsent; - } - - return { - method: 'POST', - url: NEW_ENDPOINT_URL, - data: JSON.stringify(request), - newFormat: true, - bidsMap - }; -} - function createVideoRequest(bid, mediaType) { const {playerSize, mimes, durationRangeSec} = mediaType; const size = (playerSize || bid.sizes || [])[0]; diff --git a/modules/gridBidAdapter.md b/modules/gridBidAdapter.md index 77b9bbf0f36..6a7075ccb00 100644 --- a/modules/gridBidAdapter.md +++ b/modules/gridBidAdapter.md @@ -20,7 +20,7 @@ Grid bid adapter supports Banner and Video (instream and outstream). bidder: "grid", params: { uid: '1', - priceType: 'gross' // by default is 'net' + bidFloor: 0.5 } } ] @@ -32,7 +32,6 @@ Grid bid adapter supports Banner and Video (instream and outstream). bidder: "grid", params: { uid: 2, - priceType: 'gross', keywords: { brandsafety: ['disaster'], topic: ['stress', 'fear'] diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 72e49e5dbdf..9cd7d100318 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -40,229 +40,6 @@ describe('TheMediaGrid Adapter', function () { }); describe('buildRequests', function () { - function parseRequest(url) { - const res = {}; - url.split('&').forEach((it) => { - const couple = it.split('='); - res[couple[0]] = decodeURIComponent(couple[1]); - }); - return res; - } - const bidderRequest = {refererInfo: {referer: 'https://example.com'}}; - const referrer = bidderRequest.refererInfo.referer; - let bidRequests = [ - { - 'bidder': 'grid', - 'params': { - 'uid': '1' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'grid', - 'params': { - 'uid': '1' - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'mediaTypes': { - 'video': { - 'playerSize': [400, 600] - }, - 'banner': { - 'sizes': [[728, 90]] - } - }, - 'bidId': '3150ccb55da321', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'grid', - 'params': { - 'uid': '2' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'mediaTypes': { - 'video': { - 'playerSize': [400, 600] - }, - 'banner': { - 'sizes': [[300, 250], [300, 600]] - } - }, - 'bidId': '42dbe3a7168a6a', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('should attach valid params to the tag', function () { - const [request] = spec.buildRequests([bidRequests[0]], bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('auids', '1'); - expect(payload).to.have.property('sizes', '300x250,300x600'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - expect(payload).to.have.property('wrapperType', 'Prebid_js'); - expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); - }); - - it('sizes must be added from mediaTypes', function () { - const [request] = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('auids', '1,1'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90,400x600'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - }); - - it('sizes must not be duplicated', function () { - const [request] = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('auids', '1,1,2'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90,400x600'); - expect(payload).to.have.property('r', '22edbae2733bf6'); - }); - - it('if gdprConsent is present payload must have gdpr params', function () { - const [request] = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('u', referrer); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '1'); - }); - - it('if gdprApplies is false gdpr_applies must be 0', function () { - const [request] = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '0'); - }); - - it('if gdprApplies is undefined gdpr_applies must be 1', function () { - const [request] = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('gdpr_consent', 'AAA'); - expect(payload).to.have.property('gdpr_applies', '1'); - }); - - it('if usPrivacy is present payload must have us_privacy param', function () { - const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const [request] = spec.buildRequests(bidRequests, bidderRequestWithUSP); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('us_privacy', '1YNN'); - }); - - it('should convert keyword params to proper form and attaches to request', function () { - const bidRequestWithKeywords = [].concat(bidRequests); - bidRequestWithKeywords[1] = Object.assign({}, - bidRequests[1], - { - params: { - uid: '1', - keywords: { - single: 'val', - singleArr: ['val'], - singleArrNum: [3], - multiValMixed: ['value1', 2, 'value3'], - singleValNum: 123, - emptyStr: '', - emptyArr: [''], - badValue: {'foo': 'bar'} // should be dropped - } - } - } - ); - - const [request] = spec.buildRequests(bidRequestWithKeywords, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload.keywords).to.be.an('string'); - payload.keywords = JSON.parse(payload.keywords); - - expect(payload.keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['3'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }, { - 'key': 'emptyStr' - }, { - 'key': 'emptyArr' - }]); - }); - - it('should mix keyword param with keywords from config', function () { - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'fpd.user' ? {'keywords': ['a', 'b']} : arg === 'fpd.context' ? {'keywords': ['any words']} : null); - - const bidRequestWithKeywords = [].concat(bidRequests); - bidRequestWithKeywords[1] = Object.assign({}, - bidRequests[1], - { - params: { - uid: '1', - keywords: { - single: 'val', - singleArr: ['val'], - multiValMixed: ['value1', 2, 'value3'] - } - } - } - ); - - const [request] = spec.buildRequests(bidRequestWithKeywords, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload.keywords).to.be.an('string'); - payload.keywords = JSON.parse(payload.keywords); - - expect(payload.keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'user', - 'value': ['a', 'b'] - }, { - 'key': 'context', - 'value': ['any words'] - }]); - - getConfigStub.restore(); - }); - }); - - describe('buildRequests in new format', function () { function parseRequest(data) { return JSON.parse(data); } @@ -278,7 +55,7 @@ describe('TheMediaGrid Adapter', function () { 'bidder': 'grid', 'params': { 'uid': '1', - 'useNewFormat': true + 'bidFloor': 1.25 }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], @@ -294,8 +71,7 @@ describe('TheMediaGrid Adapter', function () { { 'bidder': 'grid', 'params': { - 'uid': '2', - 'useNewFormat': true + 'uid': '2' }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], @@ -306,8 +82,7 @@ describe('TheMediaGrid Adapter', function () { { 'bidder': 'grid', 'params': { - 'uid': '11', - 'useNewFormat': true + 'uid': '11' }, 'adUnitCode': 'adunit-code-2', 'sizes': [[728, 90]], @@ -324,8 +99,7 @@ describe('TheMediaGrid Adapter', function () { { 'bidder': 'grid', 'params': { - 'uid': '3', - 'useNewFormat': true + 'uid': '3' }, 'adUnitCode': 'adunit-code-2', 'sizes': [[728, 90]], @@ -344,7 +118,7 @@ describe('TheMediaGrid Adapter', function () { ]; it('should attach valid params to the tag', function () { - const [request] = spec.buildRequests([bidRequests[0]], bidderRequest); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.deep.equal({ @@ -361,6 +135,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[0].bidId, 'tagid': bidRequests[0].params.uid, 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, 'banner': { 'w': 300, 'h': 250, @@ -371,7 +146,7 @@ describe('TheMediaGrid Adapter', function () { }); it('make possible to process request without mediaTypes', function () { - const [request] = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); + const request = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.deep.equal({ @@ -388,6 +163,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[0].bidId, 'tagid': bidRequests[0].params.uid, 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, 'banner': { 'w': 300, 'h': 250, @@ -397,6 +173,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[1].bidId, 'tagid': bidRequests[1].params.uid, 'ext': {'divid': bidRequests[1].adUnitCode}, + 'bidfloor': 0, 'banner': { 'w': 300, 'h': 250, @@ -407,7 +184,7 @@ describe('TheMediaGrid Adapter', function () { }); it('should attach valid params to the video tag', function () { - const [request] = spec.buildRequests(bidRequests.slice(0, 3), bidderRequest); + const request = spec.buildRequests(bidRequests.slice(0, 3), bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.deep.equal({ @@ -424,6 +201,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[0].bidId, 'tagid': bidRequests[0].params.uid, 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, 'banner': { 'w': 300, 'h': 250, @@ -433,6 +211,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[1].bidId, 'tagid': bidRequests[1].params.uid, 'ext': {'divid': bidRequests[1].adUnitCode}, + 'bidfloor': 0, 'banner': { 'w': 300, 'h': 250, @@ -442,6 +221,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[2].bidId, 'tagid': bidRequests[2].params.uid, 'ext': {'divid': bidRequests[2].adUnitCode}, + 'bidfloor': 0, 'video': { 'w': 400, 'h': 600, @@ -452,7 +232,7 @@ describe('TheMediaGrid Adapter', function () { }); it('should support mixed mediaTypes', function () { - const [request] = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.deep.equal({ @@ -469,6 +249,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[0].bidId, 'tagid': bidRequests[0].params.uid, 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, 'banner': { 'w': 300, 'h': 250, @@ -478,6 +259,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[1].bidId, 'tagid': bidRequests[1].params.uid, 'ext': {'divid': bidRequests[1].adUnitCode}, + 'bidfloor': 0, 'banner': { 'w': 300, 'h': 250, @@ -487,6 +269,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[2].bidId, 'tagid': bidRequests[2].params.uid, 'ext': {'divid': bidRequests[2].adUnitCode}, + 'bidfloor': 0, 'video': { 'w': 400, 'h': 600, @@ -496,6 +279,7 @@ describe('TheMediaGrid Adapter', function () { 'id': bidRequests[3].bidId, 'tagid': bidRequests[3].params.uid, 'ext': {'divid': bidRequests[3].adUnitCode}, + 'bidfloor': 0, 'banner': { 'w': 728, 'h': 90, @@ -511,7 +295,7 @@ describe('TheMediaGrid Adapter', function () { it('if gdprConsent is present payload must have gdpr params', function () { const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); - const [request] = spec.buildRequests(bidRequests, gdprBidderRequest); + const request = spec.buildRequests(bidRequests, gdprBidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('user'); @@ -524,7 +308,7 @@ describe('TheMediaGrid Adapter', function () { it('if usPrivacy is present payload must have us_privacy param', function () { const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const [request] = spec.buildRequests(bidRequests, bidderRequestWithUSP); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('regs'); @@ -536,22 +320,65 @@ describe('TheMediaGrid Adapter', function () { const bidRequestsWithUserIds = bidRequests.map((bid) => { return Object.assign({ userId: { - id5id: { uid: 'id5id_1' }, + id5id: { uid: 'id5id_1', ext: { linkType: 2 } }, tdid: 'tdid_1', digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - lipb: {lipbid: 'lipb_1'} + lipb: {lipbid: 'lipb_1'}, + idl_env: 'idl_env_1', + criteoId: 'criteoId_1' } }, bid); }); - const [request] = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); + const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('user'); expect(payload.user).to.have.property('ext'); - expect(payload.user.ext).to.have.property('unifiedid', 'tdid_1'); - expect(payload.user.ext).to.have.property('id5id', 'id5id_1'); - expect(payload.user.ext).to.have.property('digitrustid', 'DTID'); - expect(payload.user.ext).to.have.property('liveintentid', 'lipb_1'); + expect(payload.user.ext.digitrust).to.deep.equal({ + id: 'DTID', + keyv: 4, + privacy: { + optout: false + }, + producer: 'ABC', + version: 2 + }); + expect(payload.user.ext.eids).to.deep.equal([ + { + source: 'adserver.org', + uids: [{ + id: 'tdid_1', + ext: { + rtiPartner: 'TDID' + } + }] + }, + { + source: 'id5-sync.com', + uids: [{ + id: 'id5id_1' + }], + ext: { linkType: 2 } + }, + { + source: 'liveintent.com', + uids: [{ + id: 'lipb_1' + }] + }, + { + source: 'identityLink', + uids: [{ + id: 'idl_env_1' + }] + }, + { + source: 'criteo.com', + uids: [{ + id: 'criteoId_1' + }] + } + ]); }); it('if schain is present payload must have source.ext.schain param', function () { @@ -570,7 +397,7 @@ describe('TheMediaGrid Adapter', function () { schain: schain }, bid); }); - const [request] = spec.buildRequests(bidRequestsWithSChain, bidderRequest); + const request = spec.buildRequests(bidRequestsWithSChain, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('source'); @@ -590,7 +417,7 @@ describe('TheMediaGrid Adapter', function () { } }, bid); }); - const [request] = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); + const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('user'); @@ -604,14 +431,68 @@ describe('TheMediaGrid Adapter', function () { expect(payload).to.have.property('site'); expect(payload.site.content).to.deep.equal(jsContent); }); + + 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); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.tmax).to.equal(2000); + getConfigStub.restore(); + }); + it('shold be right tmax when timeout in bidderRequest is less then timeout in config', function() { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'bidderTimeout' ? 5000 : null); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.tmax).to.equal(3000); + getConfigStub.restore(); + }); + describe('floorModule', function () { + const floorTestData = { + 'currency': 'USD', + 'floor': 1.50 + }; + const bidRequest = Object.assign({ + getFloor: _ => { + return floorTestData; + } + }, bidRequests[1]); + it('should return the value from getFloor if present', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].bidfloor).to.equal(floorTestData.floor); + }); + it('should return the getFloor.floor value if it is greater than bidfloor', function () { + const bidfloor = 0.80; + const bidRequestsWithFloor = { ...bidRequest }; + bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); + const request = spec.buildRequests([bidRequestsWithFloor], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].bidfloor).to.equal(floorTestData.floor); + }); + it('should return the bidfloor value if it is greater than getFloor.floor', function () { + const bidfloor = 1.80; + const bidRequestsWithFloor = { ...bidRequest }; + bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); + const request = spec.buildRequests([bidRequestsWithFloor], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].bidfloor).to.equal(bidfloor); + }); + }); }); describe('interpretResponse', function () { const responses = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '659423fff799cb', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '1'}, + {'bid': [{'impid': '4dff80cc4ee346', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '5703af74d0472a', 'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'impid': '2344da98f78b42', 'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '1'}, {'bid': [{'price': 0, 'adm': '
test content 5
', 'h': 250, 'w': 300}], 'seat': '1'}, undefined, {'bid': [], 'seat': '1'}, @@ -632,7 +513,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '1cbd2feafe5e8b', } ]; - const [request] = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '659423fff799cb', @@ -663,7 +544,7 @@ describe('TheMediaGrid Adapter', function () { }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], - 'bidId': '300bfeb0d71a5b', + 'bidId': '659423fff799cb', 'bidderRequestId': '2c2bb1972df9a', 'auctionId': '1fa09aee5c8c99', }, @@ -690,10 +571,10 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '1fa09aee5c8c99', } ]; - const [request] = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { - 'requestId': '300bfeb0d71a5b', + 'requestId': '659423fff799cb', 'cpm': 1.15, 'creativeId': 1, 'dealId': 11, @@ -776,10 +657,10 @@ describe('TheMediaGrid Adapter', function () { } ]; const response = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 11, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 12, content_type: 'video'}], 'seat': '2'} + {'bid': [{'impid': '659423fff799cb', 'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 11, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'impid': '2bc598e42b6a', 'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 12, content_type: 'video'}], 'seat': '2'} ]; - const [request] = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '659423fff799cb', @@ -797,6 +678,23 @@ describe('TheMediaGrid Adapter', function () { 'adResponse': { 'content': '\n<\/Ad>\n<\/VAST>' } + }, + { + 'requestId': '2bc598e42b6a', + 'cpm': 1.00, + 'creativeId': 12, + 'dealId': undefined, + 'width': undefined, + 'height': undefined, + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': false, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } } ]; @@ -840,18 +738,18 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '1fa09aee5c84d34', } ]; - const [request] = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests); const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); expect(result.length).to.equal(0); }); it('complicated case', function () { const fullResponse = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0.15, 'adm': '
test content 4
', 'auid': 1, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 5
', 'auid': 2, 'h': 600, 'w': 350}], 'seat': '1'}, + {'bid': [{'impid': '2164be6358b9', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, + {'bid': [{'impid': '4e111f1b66e4', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, + {'bid': [{'impid': '26d6f897b516', 'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'impid': '326bde7fbf69', 'price': 0.15, 'adm': '
test content 4
', 'auid': 1, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '2234f233b22a', 'price': 0.5, 'adm': '
test content 5
', 'auid': 2, 'h': 600, 'w': 350}], 'seat': '1'}, ]; const bidRequests = [ { @@ -910,7 +808,7 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': '32a1f276cb87cb8', } ]; - const [request] = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '2164be6358b9', @@ -973,82 +871,6 @@ describe('TheMediaGrid Adapter', function () { const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); expect(result).to.deep.equal(expectedResponse); }); - - it('dublicate uids and sizes in one slot', function () { - const fullResponse = [ - {'bid': [{'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
test content 2
', 'auid': 1, 'h': 250, 'w': 300}], 'seat': '1'}, - ]; - const bidRequests = [ - { - 'bidder': 'grid', - 'params': { - 'uid': '1' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '5126e301f4be', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - }, - { - 'bidder': 'grid', - 'params': { - 'uid': '1' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '57b2ebe70e16', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - }, - { - 'bidder': 'grid', - 'params': { - 'uid': '1' - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '225fcd44b18c', - 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', - } - ]; - const [request] = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '5126e301f4be', - 'cpm': 1.15, - 'creativeId': 1, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'bidderCode': 'grid', - 'currency': 'USD', - 'mediaType': 'banner', - 'netRevenue': false, - 'ttl': 360, - }, - { - 'requestId': '57b2ebe70e16', - 'cpm': 0.5, - 'creativeId': 1, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '
test content 2
', - 'bidderCode': 'grid', - 'currency': 'USD', - 'mediaType': 'banner', - 'netRevenue': false, - 'ttl': 360, - } - ]; - - const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); - expect(result).to.deep.equal(expectedResponse); - }); }); describe('user sync', function () { From 73b4fc741478b20540c8317543891a98ffae583e Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Wed, 21 Oct 2020 13:48:20 -0400 Subject: [PATCH 091/190] Floors Module update to include floorMin (#5805) * Update to floors module to allow floorMin definition using setConfig({floors:...}); 1) If floorMin exists, set floorValue to new property floorRuleValue. 2) If floorMin is greater than floorValue, set floorValue to floorMin. Update to Rubicon Analytics Adapter to pass floorMin under auction.floors.floorMin if exists. Also includes update to pass floorRuleValue for each bid if floorMin exists Update to floorsModule roundup functionality to fix to one decimal place prior to roundup. This will fix issues in which JS evalutates a whole number to include a very small decimal value that forces a roundup to the next whole number. * Remove extra spaces * Package Lock revert * Updates to commit * Remove comment * Remove excess spaces * Update to priceFloor and rubiconAnalytics adapters --- modules/priceFloors.js | 15 +- modules/rubiconAnalyticsAdapter.js | 3 + test/spec/modules/priceFloors_spec.js | 156 ++++++++++++++++++ .../modules/rubiconAnalyticsAdapter_spec.js | 111 ++++++++++++- 4 files changed, 277 insertions(+), 8 deletions(-) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 1b865e05c0a..fd8a46b172f 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -55,7 +55,7 @@ export let _floorDataForAuction = {}; * @summary Simple function to round up to a certain decimal degree */ function roundUp(number, precision) { - return Math.ceil(parseFloat(number) * Math.pow(10, precision)) / Math.pow(10, precision); + return Math.ceil((parseFloat(number) * Math.pow(10, precision)).toFixed(1)) / Math.pow(10, precision); } let referrerHostname; @@ -98,7 +98,7 @@ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) let fieldValues = enumeratePossibleFieldValues(utils.deepAccess(floorData, 'schema.fields') || [], bidObject, responseObject); if (!fieldValues.length) return { matchingFloor: floorData.default }; - // look to see iof a request for this context was made already + // look to see if a request for this context was made already let matchingInput = fieldValues.map(field => field[0]).join('-'); // if we already have gotten the matching rule from this matching input then use it! No need to look again let previousMatch = utils.deepAccess(floorData, `matchingInputs.${matchingInput}`); @@ -109,10 +109,12 @@ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) let matchingRule = find(allPossibleMatches, hashValue => floorData.values.hasOwnProperty(hashValue)); let matchingData = { - matchingFloor: floorData.values[matchingRule] || floorData.default, + floorMin: floorData.floorMin || 0, + floorRuleValue: floorData.values[matchingRule] || floorData.default, matchingData: allPossibleMatches[0], // the first possible match is an "exact" so contains all data relevant for anlaytics adapters matchingRule }; + matchingData.matchingFloor = Math.max(matchingData.floorMin, matchingData.floorRuleValue); // save for later lookup if needed utils.deepSetValue(floorData, `matchingInputs.${matchingInput}`, {...matchingData}); return matchingData; @@ -287,11 +289,12 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { bid.floorData = { skipped: floorData.skipped, skipRate: floorData.skipRate, + floorMin: floorData.floorMin, modelVersion: utils.deepAccess(floorData, 'data.modelVersion'), location: utils.deepAccess(floorData, 'data.location', 'noData'), floorProvider: floorData.floorProvider, fetchStatus: _floorsConfig.fetchStatus - } + }; }); }); } @@ -336,6 +339,8 @@ export function createFloorsDataForAuction(adUnits, auctionId) { const isSkipped = Math.random() * 100 < parseFloat(auctionSkipRate); resolvedFloorsData.skipped = isSkipped; } + // copy FloorMin to floorData.data + if (resolvedFloorsData.hasOwnProperty('floorMin')) resolvedFloorsData.data.floorMin = resolvedFloorsData.floorMin; // add floorData to bids updateAdUnitsForAuction(adUnits, resolvedFloorsData, auctionId); return resolvedFloorsData; @@ -568,6 +573,7 @@ function addFieldOverrides(overrides) { */ export function handleSetFloorsConfig(config) { _floorsConfig = utils.pick(config, [ + 'floorMin', 'enabled', enabled => enabled !== false, // defaults to true 'auctionDelay', auctionDelay => auctionDelay || 0, 'floorProvider', floorProvider => utils.deepAccess(config, 'data.floorProvider', floorProvider), @@ -623,6 +629,7 @@ function addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm) { bid.floorData = { floorValue: floorInfo.matchingFloor, floorRule: floorInfo.matchingRule, + floorRuleValue: floorInfo.floorRuleValue, floorCurrency: floorData.data.currency, cpmAfterAdjustments: adjustedCpm, enforcements: {...floorData.enforcement}, diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index f6d30e06e9a..4011232ae3b 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -129,6 +129,7 @@ function sendMessage(auctionId, bidWonId) { 'dimensions', 'mediaType', 'floorValue', + 'floorRuleValue', 'floorRule' ]) : undefined ]); @@ -233,6 +234,7 @@ function sendMessage(auctionId, bidWonId) { 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'), 'skipRate', 'fetchStatus', + 'floorMin', 'floorProvider as provider' ]); } @@ -344,6 +346,7 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { }, 'seatBidId', 'floorValue', () => utils.deepAccess(bid, 'floorData.floorValue'), + 'floorRuleValue', () => utils.deepAccess(bid, 'floorData.floorRuleValue'), 'floorRule', () => utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined ]); } diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index ae45244f03d..8c673d29701 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -34,6 +34,34 @@ describe('the price floors module', function () { '*': 2.5 } }; + const basicFloorDataHigh = { + floorMin: 7.0, + modelVersion: 'basic model', + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType'] + }, + values: { + 'banner': 1.0, + 'video': 5.0, + '*': 2.5 + } + }; + const basicFloorDataLow = { + floorMin: 2.3, + modelVersion: 'basic model', + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType'] + }, + values: { + 'banner': 1.0, + 'video': 5.0, + '*': 2.5 + } + }; const basicFloorConfig = { enabled: true, auctionDelay: 0, @@ -46,6 +74,32 @@ describe('the price floors module', function () { }, data: basicFloorData } + const minFloorConfigHigh = { + enabled: true, + auctionDelay: 0, + floorMin: 7, + endpoint: {}, + enforcement: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + data: basicFloorDataHigh + } + const minFloorConfigLow = { + enabled: true, + auctionDelay: 0, + floorMin: 2.3, + endpoint: {}, + enforcement: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + data: basicFloorDataLow + } const basicBidRequest = { bidder: 'rubicon', adUnitCode: 'test_div_1', @@ -165,22 +219,50 @@ describe('the price floors module', function () { it('selects the right floor for different mediaTypes', function () { // banner with * size (not in rule file so does not do anything) expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.0, matchingFloor: 1.0, matchingData: 'banner', matchingRule: 'banner' }); // video with * size (not in rule file so does not do anything) expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'video', size: '*'})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 5.0, matchingFloor: 5.0, matchingData: 'video', matchingRule: 'video' }); // native (not in the rule list) with * size (not in rule file so does not do anything) expect(getFirstMatchingFloor({...basicFloorData}, basicBidRequest, {mediaType: 'native', size: '*'})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 2.5, matchingFloor: 2.5, matchingData: 'native', matchingRule: '*' }); + // banner with floorMin higher than matching rule + handleSetFloorsConfig({ + ...minFloorConfigHigh + }); + expect(getFirstMatchingFloor({...basicFloorDataHigh}, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ + floorMin: 7, + floorRuleValue: 1.0, + matchingFloor: 7, + matchingData: 'banner', + matchingRule: 'banner' + }); + // banner with floorMin higher than matching rule + handleSetFloorsConfig({ + ...minFloorConfigLow + }); + expect(getFirstMatchingFloor({...basicFloorDataLow}, basicBidRequest, {mediaType: 'video', size: '*'})).to.deep.equal({ + floorMin: 2.3, + floorRuleValue: 5, + matchingFloor: 5, + matchingData: 'video', + matchingRule: 'video' + }); }); it('does not alter cached matched input if conversion occurs', function () { let inputData = {...basicFloorData}; @@ -188,6 +270,8 @@ describe('the price floors module', function () { let result = getFirstMatchingFloor(inputData, basicBidRequest, {mediaType: 'banner', size: '*'}); // result should always be the same expect(result).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.0, matchingFloor: 1.0, matchingData: 'banner', matchingRule: 'banner' @@ -213,24 +297,32 @@ describe('the price floors module', function () { } // banner with 300x250 size expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.1, matchingFloor: 1.1, matchingData: '300x250', matchingRule: '300x250' }); // video with 300x250 size expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'video', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.1, matchingFloor: 1.1, matchingData: '300x250', matchingRule: '300x250' }); // native (not in the rule list) with 300x600 size expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'native', size: [600, 300]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 4.4, matchingFloor: 4.4, matchingData: '600x300', matchingRule: '600x300' }); // n/a mediaType with a size not in file should go to catch all expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: undefined, size: [1, 1]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 5.5, matchingFloor: 5.5, matchingData: '1x1', matchingRule: '*' @@ -254,12 +346,16 @@ describe('the price floors module', function () { }; // banner with 300x250 size expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 1.1, matchingFloor: 1.1, matchingData: 'test_div_1^banner^300x250', matchingRule: 'test_div_1^banner^300x250' }); // video with 300x250 size -> No matching rule so should use default expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'video', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 0.5, matchingFloor: 0.5, matchingData: 'test_div_1^video^300x250', matchingRule: undefined @@ -267,6 +363,8 @@ describe('the price floors module', function () { // remove default and should still return the same floor as above since matches are cached delete inputFloorData.default; expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'video', size: [300, 250]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 0.5, matchingFloor: 0.5, matchingData: 'test_div_1^video^300x250', matchingRule: undefined @@ -274,6 +372,8 @@ describe('the price floors module', function () { // update adUnitCode to test_div_2 with weird other params let newBidRequest = { ...basicBidRequest, adUnitCode: 'test_div_2' } expect(getFirstMatchingFloor(inputFloorData, newBidRequest, {mediaType: 'badmediatype', size: [900, 900]})).to.deep.equal({ + floorMin: 0, + floorRuleValue: 3.3, matchingFloor: 3.3, matchingData: 'test_div_2^badmediatype^900x900', matchingRule: 'test_div_2^*^*' @@ -327,6 +427,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(false, { skipped: true, + floorMin: undefined, modelVersion: undefined, location: 'noData', skipRate: 0, @@ -358,11 +459,46 @@ describe('the price floors module', function () { } }; runStandardAuction([adUnitWithFloors1, adUnitWithFloors2]); + validateBidRequests(true, { + skipped: false, + floorMin: undefined, + modelVersion: 'adUnit Model Version', + location: 'adUnit', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined + }); + }); + it('should use adUnit level data and minFloor should be set', function () { + handleSetFloorsConfig({ + ...minFloorConfigHigh, + data: undefined + }); + // attach floor data onto an adUnit and run an auction + let adUnitWithFloors1 = { + ...getAdUnitMock('adUnit-Div-1'), + floors: { + ...basicFloorData, + modelVersion: 'adUnit Model Version', // change the model name + } + }; + let adUnitWithFloors2 = { + ...getAdUnitMock('adUnit-Div-2'), + floors: { + ...basicFloorData, + values: { + 'banner': 5.0, + '*': 10.4 + } + } + }; + runStandardAuction([adUnitWithFloors1, adUnitWithFloors2]); validateBidRequests(true, { skipped: false, modelVersion: 'adUnit Model Version', location: 'adUnit', skipRate: 0, + floorMin: 7, fetchStatus: undefined, floorProvider: undefined }); @@ -372,6 +508,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, @@ -392,6 +529,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, @@ -405,6 +543,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, @@ -418,6 +557,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, @@ -440,6 +580,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 50, @@ -453,6 +594,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 10, @@ -466,6 +608,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, @@ -529,6 +672,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'model-1', location: 'setConfig', skipRate: 0, @@ -541,6 +685,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'model-2', location: 'setConfig', skipRate: 0, @@ -553,6 +698,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'model-3', location: 'setConfig', skipRate: 0, @@ -581,6 +727,7 @@ describe('the price floors module', function () { runStandardAuction(); validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, @@ -659,6 +806,7 @@ describe('the price floors module', function () { // the exposedAdUnits should be from the fetch not setConfig level data validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, @@ -696,6 +844,7 @@ describe('the price floors module', function () { // and fetchStatus is success since fetch worked validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'fetch model name', location: 'fetch', skipRate: 0, @@ -732,6 +881,7 @@ describe('the price floors module', function () { // and fetchStatus is success since fetch worked validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'fetch model name', location: 'fetch', skipRate: 0, @@ -771,6 +921,7 @@ describe('the price floors module', function () { // and fetchStatus is success since fetch worked validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'fetch model name', location: 'fetch', skipRate: 95, @@ -792,6 +943,7 @@ describe('the price floors module', function () { // and fetch failed is true validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, @@ -815,6 +967,7 @@ describe('the price floors module', function () { // and fetchStatus is 'success' but location is setConfig since it had bad data validateBidRequests(true, { skipped: false, + floorMin: undefined, modelVersion: 'basic model', location: 'setConfig', skipRate: 0, @@ -1303,6 +1456,7 @@ describe('the price floors module', function () { runBidResponse(); expect(returnedBidResponse).to.haveOwnProperty('floorData'); expect(returnedBidResponse.floorData).to.deep.equal({ + floorRuleValue: 0.3, floorValue: 0.3, floorCurrency: 'USD', floorRule: 'banner', @@ -1340,6 +1494,7 @@ describe('the price floors module', function () { expect(returnedBidResponse).to.haveOwnProperty('floorData'); expect(returnedBidResponse.floorData).to.deep.equal({ floorValue: 0.5, + floorRuleValue: 0.5, floorCurrency: 'USD', floorRule: 'banner|300x250', cpmAfterAdjustments: 0.5, @@ -1366,6 +1521,7 @@ describe('the price floors module', function () { }); expect(returnedBidResponse).to.haveOwnProperty('floorData'); expect(returnedBidResponse.floorData).to.deep.equal({ + floorRuleValue: 5.5, floorValue: 5.5, floorCurrency: 'USD', floorRule: 'video|*', diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 2bbab506b34..7c552570da6 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -111,10 +111,60 @@ const BID2 = Object.assign({}, BID, { } }); +const BID3 = Object.assign({}, BID, { + adUnitCode: '/19968336/siderail-tag1', + bidId: '5fg6hyy4r879f0', + adId: 'fake_ad_id', + requestId: '5fg6hyy4r879f0', + width: 300, + height: 250, + mediaType: 'banner', + cpm: 2.01, + source: 'server', + seatBidId: 'aaaa-bbbb-cccc-dddd', + rubiconTargeting: { + 'rpfl_elemid': '/19968336/siderail-tag1', + 'rpfl_14062': '15_tier0200' + }, + adserverTargeting: { + 'hb_bidder': 'rubicon', + 'hb_adid': '5fg6hyy4r879f0', + 'hb_pb': '2.00', + 'hb_size': '300x250', + 'hb_source': 'server' + } +}); + +const floorMinRequest = { + 'bidder': 'rubicon', + 'params': { + 'accountId': '14062', + 'siteId': '70608', + 'zoneId': '335918', + 'userId': '12346', + 'keywords': ['a', 'b', 'c'], + 'inventory': {'rating': '4-star', 'prodtype': 'tech'}, + 'visitor': {'ucat': 'new', 'lastsearch': 'iphone'}, + 'position': 'atf' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': '/19968336/siderail-tag1', + 'transactionId': 'c435626g-9e3f-401a-bee1-d56aec29a1d4', + 'sizes': [[300, 250]], + 'bidId': '5fg6hyy4r879f0', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' +}; + const MOCK = { SET_TARGETING: { [BID.adUnitCode]: BID.adserverTargeting, - [BID2.adUnitCode]: BID2.adserverTargeting + [BID2.adUnitCode]: BID2.adserverTargeting, + [BID3.adUnitCode]: BID3.adserverTargeting }, AUCTION_INIT: { 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', @@ -241,7 +291,8 @@ const MOCK = { }, BID_RESPONSE: [ BID, - BID2 + BID2, + BID3 ], AUCTION_END: { 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' @@ -252,6 +303,9 @@ const MOCK = { }), Object.assign({}, BID2, { 'status': 'rendered' + }), + Object.assign({}, BID3, { + 'status': 'rendered' }) ], BIDDER_DONE: { @@ -260,6 +314,9 @@ const MOCK = { BID, Object.assign({}, BID2, { 'serverResponseTimeMs': 42, + }), + Object.assign({}, BID3, { + 'serverResponseTimeMs': 55, }) ] }, @@ -842,14 +899,40 @@ describe('rubicon analytics adapter', function () { } }; + let floorMinResponse = { + ...BID3, + floorData: { + floorValue: 1.5, + floorRuleValue: 1, + floorRule: '12345/entertainment|banner', + floorCurrency: 'USD', + cpmAfterAdjustments: 2.00, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + matchedFields: { + gptSlot: '12345/entertainment', + mediaType: 'banner' + } + } + }; + + let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); + bidRequest.bids.push(floorMinRequest) + // spoof the auction with just our duplicates events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_REQUESTED, bidRequest); events.emit(BID_RESPONSE, flooredResponse); events.emit(BID_RESPONSE, notFlooredResponse); + events.emit(BID_RESPONSE, floorMinResponse); events.emit(AUCTION_END, MOCK.AUCTION_END); events.emit(SET_TARGETING, MOCK.SET_TARGETING); events.emit(BID_WON, MOCK.BID_WON[1]); + events.emit(BID_WON, MOCK.BID_WON[2]); clock.tick(SEND_TIMEOUT + 1000); expect(server.requests.length).to.equal(1); @@ -860,7 +943,7 @@ describe('rubicon analytics adapter', function () { } it('should capture price floor information correctly', function () { - let message = performFloorAuction('rubicon') + let message = performFloorAuction('rubicon'); // verify our floor stuff is passed // top level floor info @@ -892,6 +975,16 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); + + // second adUnit's adSlot + expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); + // top level adUnit status is success + expect(message.auctions[0].adUnits[2].status).to.equal('success'); + // second adUnits bid is success + expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); }); it('should still send floor info if provider is not rubicon', function () { @@ -927,6 +1020,16 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); + + // second adUnit's adSlot + expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); + // top level adUnit status is success + expect(message.auctions[0].adUnits[2].status).to.equal('success'); + // second adUnits bid is success + expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); + expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); }); describe('with session handling', function () { From 372fbbded6fbb2fa14c9f7a03d2d8388d8bfc87f Mon Sep 17 00:00:00 2001 From: Mike Chowla Date: Wed, 21 Oct 2020 11:45:04 -0700 Subject: [PATCH 092/190] Prebid 4.13.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6273d680e19..740e905053b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.13.0-pre", + "version": "4.13.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 73d880f962c7f1550d55d5decd9ae3b38d74918f Mon Sep 17 00:00:00 2001 From: Mike Chowla Date: Wed, 21 Oct 2020 12:19:18 -0700 Subject: [PATCH 093/190] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 740e905053b..cf82e7a9e95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.13.0", + "version": "4.14.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From d3c0dedca0390835dded96fb968740fe063d0437 Mon Sep 17 00:00:00 2001 From: John Rosendahl Date: Wed, 21 Oct 2020 16:24:32 -0600 Subject: [PATCH 094/190] configurable TTL for impressions (#5880) --- modules/sovrnBidAdapter.js | 2 +- test/spec/modules/sovrnBidAdapter_spec.js | 35 ++++++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 935b0ceb489..62f5e85779e 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -143,7 +143,7 @@ export const spec = { netRevenue: true, mediaType: BANNER, ad: decodeURIComponent(`${sovrnBid.adm}`), - ttl: 60 + ttl: sovrnBid.ttl || 90 }); }); } diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 321fed40d83..983ade4dd14 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -331,16 +331,16 @@ describe('sovrnBidAdapter', function() { 'currency': 'USD', 'netRevenue': true, 'mediaType': 'banner', - 'ad': decodeURIComponent(`>`), - 'ttl': 60000 + 'ad': decodeURIComponent(``), + 'ttl': 90 }]; let result = spec.interpretResponse(response); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0]).to.deep.equal(expectedResponse[0]); }); it('should get correct bid response when dealId is passed', function () { - response.body.dealid = 'baking'; + response.body.seatbid[0].bid[0].dealid = 'baking'; let expectedResponse = [{ 'requestId': '263c448586f5a1', @@ -352,12 +352,33 @@ describe('sovrnBidAdapter', function() { 'currency': 'USD', 'netRevenue': true, 'mediaType': 'banner', - 'ad': decodeURIComponent(`>`), - 'ttl': 60000 + 'ad': decodeURIComponent(``), + 'ttl': 90 }]; let result = spec.interpretResponse(response); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('should get correct bid response when ttl is set', function () { + response.body.seatbid[0].bid[0].ttl = 480; + + let expectedResponse = [{ + 'requestId': '263c448586f5a1', + 'cpm': 0.45882675, + 'width': 728, + 'height': 90, + 'creativeId': 'creativelycreatedcreativecreative', + 'dealId': null, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': decodeURIComponent(``), + 'ttl': 480 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); }); it('handles empty bid response', function () { From 05606c66e1f8eb824c5ccea2e945164c3e0b8b33 Mon Sep 17 00:00:00 2001 From: Anand Venkatraman Date: Thu, 22 Oct 2020 05:07:51 -0400 Subject: [PATCH 095/190] PulsePoint Adapter: Fix on multi-format support (#5857) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * APPS-3774 --- modules/pulsepointBidAdapter.js | 4 +-- .../spec/modules/pulsepointBidAdapter_spec.js | 25 ++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 12937dbec9d..005eadaa390 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -165,12 +165,12 @@ function impression(slot) { function banner(slot) { const sizes = parseSizes(slot); const size = adSize(slot, sizes); - return (slot.nativeParams || slot.params.video) ? null : { + return (slot.mediaTypes && slot.mediaTypes.banner) ? { w: size[0], h: size[1], battr: slot.params.battr, format: sizes - }; + } : null; } /** diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index a6f5ff2a0dc..cf81a26eebb 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -19,6 +19,11 @@ describe('PulsePoint Adapter Tests', function () { } }, { placementCode: '/DfpAccount2/slot2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, bidId: 'bid23456', params: { cp: 'p10000', @@ -72,6 +77,11 @@ describe('PulsePoint Adapter Tests', function () { }]; const additionalParamsConfig = [{ placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, bidId: 'bid12345', params: { cp: 'p10000', @@ -89,6 +99,11 @@ describe('PulsePoint Adapter Tests', function () { const ortbParamsSlotConfig = [{ placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, bidId: 'bid12345', params: { cp: 'p10000', @@ -146,6 +161,11 @@ describe('PulsePoint Adapter Tests', function () { const schainParamsSlotConfig = [{ placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, bidId: 'bid12345', params: { cp: 'p10000', @@ -681,7 +701,10 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[1].banner).to.not.be.null; expect(ortbRequest.imp[1].banner.w).to.equal(728); expect(ortbRequest.imp[1].banner.h).to.equal(90); - expect(ortbRequest.imp[1].banner.format).to.be.null; + expect(ortbRequest.imp[1].banner.format).to.not.be.null; + expect(ortbRequest.imp[1].banner.format).to.have.lengthOf(1); + expect(ortbRequest.imp[1].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[1].banner.format[0].h).to.equal(90); // adsize on response const ortbResponse = { seatbid: [{ From 0576ee211d64fb6011e0f1de55a3cae86c07db24 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Thu, 22 Oct 2020 11:51:38 +0200 Subject: [PATCH 096/190] ID5 user id module: migrate publishers to use local storage instead of 1p cookies (#5874) * change storage name * id5 user id module will now prefer localstorage over cookies with a specific name. - for now, the requirement is a warning, but in a future release it will be a strict requirement and the module will not work if it's not configured properly by the publisher - remove code to support legacy endpoint / storage since all publishers using ID5 have upgraded past v3.25.0 - once a publisher is using localstorage, remove any legacy cookies that are not longer needed * add id5 markdown file * update example docs to use html5 and new storage name * add todo * code review updates * update version * doc tweaks * doc tweaks * address PR feedback - fix bug in storage expiration dates - remove unnecessary check --- modules/id5IdSystem.js | 147 +++++++++++++---- modules/id5IdSystem.md | 54 +++++++ modules/userId/userId.md | 10 +- test/spec/modules/id5IdSystem_spec.js | 220 ++++++++++++-------------- 4 files changed, 271 insertions(+), 160 deletions(-) create mode 100644 modules/id5IdSystem.md diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index f8ff50f52a3..9a1dcafc138 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -13,8 +13,13 @@ import { getStorageManager } from '../src/storageManager.js'; const MODULE_NAME = 'id5Id'; const GVLID = 131; -const BASE_NB_COOKIE_NAME = 'id5id.1st'; -const NB_COOKIE_EXP_DAYS = (30 * 24 * 60 * 60 * 1000); // 30 days +const NB_EXP_DAYS = 30; +export const ID5_STORAGE_NAME = 'id5id'; +const LOCAL_STORAGE = 'html5'; + +// order the legacy cookie names in reverse priority order so the last +// cookie in the array is the most preferred to use +const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st' ]; const storage = getStorageManager(GVLID, MODULE_NAME); @@ -42,10 +47,7 @@ export const id5IdSubmodule = { let uid; let linkType = 0; - if (value && typeof value.ID5ID === 'string') { - // don't lose our legacy value from cache - uid = value.ID5ID; - } else if (value && typeof value.universal_uid === 'string') { + if (value && typeof value.universal_uid === 'string') { uid = value.universal_uid; linkType = value.link_type || linkType; } else { @@ -71,22 +73,20 @@ export const id5IdSubmodule = { * @returns {IdResponse|undefined} */ getId(config, consentData, cacheIdObj) { - const configParams = (config && config.params) || {}; - if (!hasRequiredParams(configParams)) { + if (!hasRequiredConfig(config)) { return undefined; } + const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; const gdprConsentString = hasGdpr ? consentData.consentString : ''; - const url = `https://id5-sync.com/g/v2/${configParams.partner}.json?gdpr_consent=${gdprConsentString}&gdpr=${hasGdpr}`; + const url = `https://id5-sync.com/g/v2/${config.params.partner}.json?gdpr_consent=${gdprConsentString}&gdpr=${hasGdpr}`; const referer = getRefererInfo(); - const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : ''; - const pubId = (cacheIdObj && cacheIdObj.ID5ID) ? cacheIdObj.ID5ID : ''; // TODO: remove when 1puid isn't needed + const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : getLegacyCookieSignature(); const data = { - 'partner': configParams.partner, - '1puid': pubId, // TODO: remove when 1puid isn't needed - 'nbPage': incrementNb(configParams), + 'partner': config.params.partner, + 'nbPage': incrementNb(config.params.partner), 'o': 'pbjs', - 'pd': configParams.pd || '', + 'pd': config.params.pd || '', 'rf': referer.referer, 's': signature, 'top': referer.reachedTop ? 1 : 0, @@ -101,7 +101,13 @@ export const id5IdSubmodule = { if (response) { try { responseObj = JSON.parse(response); - resetNb(configParams); + resetNb(config.params.partner); + + // TODO: remove after requiring publishers to use localstorage and + // all publishers have upgraded + if (config.storage.type === LOCAL_STORAGE) { + removeLegacyCookies(config.params.partner); + } } catch (error) { utils.logError(error); } @@ -109,7 +115,7 @@ export const id5IdSubmodule = { callback(responseObj); }, error: error => { - utils.logError(`id5Id: ID fetch encountered an error`, error); + utils.logError(`User ID - ID5 submodule getId fetch encountered an error`, error); callback(); } }; @@ -129,39 +135,112 @@ export const id5IdSubmodule = { * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ extendId(config, cacheIdObj) { - const configParams = (config && config.params) || {}; - incrementNb(configParams); + const partnerId = (config && config.params && config.params.partner) || 0; + incrementNb(partnerId); return cacheIdObj; } }; -function hasRequiredParams(configParams) { - if (!configParams || typeof configParams.partner !== 'number') { +function hasRequiredConfig(config) { + if (!config || !config.params || !config.params.partner || typeof config.params.partner !== 'number') { utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); return false; } + + if (!config.storage || !config.storage.type || !config.storage.name) { + utils.logError(`User ID - ID5 submodule requires storage to be set`); + return false; + } + + // TODO: in a future release, return false if storage type or name are not set as required + if (config.storage.type !== LOCAL_STORAGE) { + utils.logWarn(`User ID - ID5 submodule recommends storage type to be '${LOCAL_STORAGE}'. In a future release this will become a strict requirement`); + } + // TODO: in a future release, return false if storage type or name are not set as required + if (config.storage.name !== ID5_STORAGE_NAME) { + utils.logWarn(`User ID - ID5 submodule recommends storage name to be '${ID5_STORAGE_NAME}'. In a future release this will become a strict requirement`); + } + return true; } -function nbCookieName(configParams) { - return hasRequiredParams(configParams) ? `${BASE_NB_COOKIE_NAME}_${configParams.partner}_nb` : undefined; + +export function expDaysStr(expDays) { + return (new Date(Date.now() + (1000 * 60 * 60 * 24 * expDays))).toUTCString(); } -function nbCookieExpStr(expDays) { - return (new Date(Date.now() + expDays)).toUTCString(); + +export function nbCacheName(partnerId) { + return `${ID5_STORAGE_NAME}_${partnerId}_nb`; } -function storeNbInCookie(configParams, nb) { - storage.setCookie(nbCookieName(configParams), nb, nbCookieExpStr(NB_COOKIE_EXP_DAYS), 'Lax'); +export function storeNbInCache(partnerId, nb) { + storeInLocalStorage(nbCacheName(partnerId), nb, NB_EXP_DAYS); } -function getNbFromCookie(configParams) { - const cacheNb = storage.getCookie(nbCookieName(configParams)); +export function getNbFromCache(partnerId) { + let cacheNb = getFromLocalStorage(nbCacheName(partnerId)); return (cacheNb) ? parseInt(cacheNb) : 0; } -function incrementNb(configParams) { - const nb = (getNbFromCookie(configParams) + 1); - storeNbInCookie(configParams, nb); +function incrementNb(partnerId) { + const nb = (getNbFromCache(partnerId) + 1); + storeNbInCache(partnerId, nb); return nb; } -function resetNb(configParams) { - storeNbInCookie(configParams, 0); +function resetNb(partnerId) { + storeNbInCache(partnerId, 0); +} + +function getLegacyCookieSignature() { + let legacyStoredValue; + LEGACY_COOKIE_NAMES.forEach(function(cookie) { + if (storage.getCookie(cookie)) { + legacyStoredValue = JSON.parse(storage.getCookie(cookie)) || legacyStoredValue; + } + }); + return (legacyStoredValue && legacyStoredValue.signature) || ''; +} + +/** + * Remove our legacy cookie values. Needed until we move all publishers + * to html5 storage in a future release + * @param {integer} partnerId + */ +function removeLegacyCookies(partnerId) { + LEGACY_COOKIE_NAMES.forEach(function(cookie) { + storage.setCookie(`${cookie}`, '', expDaysStr(-1)); + storage.setCookie(`${cookie}_nb`, '', expDaysStr(-1)); + storage.setCookie(`${cookie}_${partnerId}_nb`, '', expDaysStr(-1)); + storage.setCookie(`${cookie}_last`, '', expDaysStr(-1)); + }); +} + +/** + * This will make sure we check for expiration before accessing local storage + * @param {string} key + */ +export function getFromLocalStorage(key) { + const storedValueExp = storage.getDataFromLocalStorage(`${key}_exp`); + // empty string means no expiration set + if (storedValueExp === '') { + return storage.getDataFromLocalStorage(key); + } else if (storedValueExp) { + if ((new Date(storedValueExp)).getTime() - Date.now() > 0) { + return storage.getDataFromLocalStorage(key); + } + } + // if we got here, then we have an expired item or we didn't set an + // expiration initially somehow, so we need to remove the item from the + // local storage + storage.removeDataFromLocalStorage(key); + return null; +} +/** + * Ensure that we always set an expiration in local storage since + * by default it's not required + * @param {string} key + * @param {any} value + * @param {integer} expDays + */ +export function storeInLocalStorage(key, value, expDays) { + storage.setDataInLocalStorage(`${key}_exp`, expDaysStr(expDays)); + storage.setDataInLocalStorage(`${key}`, value); } submodule('userId', id5IdSubmodule); diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md new file mode 100644 index 00000000000..b2369dba07e --- /dev/null +++ b/modules/id5IdSystem.md @@ -0,0 +1,54 @@ +# ID5 Universal ID + +The ID5 Universal ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 Universal ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 Universal ID and detailed integration docs, please visit [our documentation](https://console.id5.io/docs/public/prebid). We also recommend that you sign up for our [release notes](https://id5.io/universal-id/release-notes) to stay up-to-date with any changes to the implementation of the ID5 Universal ID in Prebid. + +## ID5 Universal ID Registration + +The ID5 Universal ID is free to use, but requires a simple registration with ID5. Please visit [id5.io/universal-id](https://id5.io/universal-id) to sign up and request your ID5 Partner Number to get started. + +The ID5 privacy policy is at [https://www.id5.io/platform-privacy-policy](https://www.id5.io/platform-privacy-policy). + +## ID5 Universal ID Configuration + +First, make sure to add the ID5 submodule to your Prebid.js package with: + +``` +gulp build --modules=id5IdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: "id5Id", + params: { + partner: 173, // change to the Partner Number you received from ID5 + pd: "MT1iNTBjY..." // optional, see table below for a link to how to generate this + }, + storage: { + type: "html5", // "html5" is the required storage type + name: "id5id", // "id5id" is the required storage name + expires: 90, // storage lasts for 90 days + refreshInSeconds: 8*3600 // refresh ID every 8 hours to ensure it's fresh + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | +| params | Required | Object | Details for the ID5 Universal ID. | | +| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | +| params.pd | Optional | String | Publisher-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/x/BIAZ) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | +| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | +| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | +| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | +| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | + +**ATTENTION:** As of Prebid.js v4.13.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). \ No newline at end of file diff --git a/modules/userId/userId.md b/modules/userId/userId.md index c46f67f9a9a..267b3a60cea 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -29,9 +29,9 @@ pbjs.setConfig({ pd: "some-pd-string" // See https://wiki.id5.io/x/BIAZ for details }, storage: { - type: "cookie", - name: "id5id.1st", - expires: 90, // Expiration of cookies in days + type: "html5", // ID5 requires html5 + name: "id5id", + expires: 90, // Expiration in days refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' }, }, { @@ -144,8 +144,8 @@ pbjs.setConfig({ }, storage: { type: 'html5', - name: 'id5id.1st', - expires: 90, // Expiration of cookies in days + name: 'id5id', + expires: 90, // Expiration in days refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' }, }, { diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index ac000c1e6dd..bfc41e5f5e8 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1,9 +1,19 @@ +import { + id5IdSubmodule, + ID5_STORAGE_NAME, + getFromLocalStorage, + storeInLocalStorage, + expDaysStr, + nbCacheName, + getNbFromCache, + storeNbInCache +} from 'modules/id5IdSystem.js'; import { init, requestBidsHook, setSubmoduleRegistry, coreStorage } from 'modules/userId/index.js'; import { config } from 'src/config.js'; -import { id5IdSubmodule } from 'modules/id5IdSystem.js'; import { server } from 'test/mocks/xhr.js'; import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; +import * as utils from 'src/utils.js'; let expect = require('chai').expect; @@ -11,20 +21,15 @@ describe('ID5 ID System', function() { const ID5_MODULE_NAME = 'id5Id'; const ID5_EIDS_NAME = ID5_MODULE_NAME.toLowerCase(); const ID5_SOURCE = 'id5-sync.com'; - const ID5_PARTNER = 173; - const ID5_ENDPOINT = `https://id5-sync.com/g/v2/${ID5_PARTNER}.json`; - const ID5_COOKIE_NAME = 'id5idcookie'; - const ID5_NB_COOKIE_NAME = `id5id.1st_${ID5_PARTNER}_nb`; - const ID5_EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; + const ID5_TEST_PARTNER_ID = 173; + const ID5_ENDPOINT = `https://id5-sync.com/g/v2/${ID5_TEST_PARTNER_ID}.json`; + const ID5_NB_STORAGE_NAME = nbCacheName(ID5_TEST_PARTNER_ID); const ID5_STORED_ID = 'storedid5id'; const ID5_STORED_SIGNATURE = '123456'; const ID5_STORED_OBJ = { 'universal_uid': ID5_STORED_ID, 'signature': ID5_STORED_SIGNATURE }; - const ID5_LEGACY_STORED_OBJ = { - 'ID5ID': ID5_STORED_ID - } const ID5_RESPONSE_ID = 'newid5id'; const ID5_RESPONSE_SIGNATURE = 'abcdef'; const ID5_JSON_RESPONSE = { @@ -33,11 +38,11 @@ describe('ID5 ID System', function() { 'link_type': 0 }; - function getId5FetchConfig(storageName = ID5_COOKIE_NAME, storageType = 'cookie') { + function getId5FetchConfig(storageName = ID5_STORAGE_NAME, storageType = 'html5') { return { name: ID5_MODULE_NAME, params: { - partner: ID5_PARTNER + partner: ID5_TEST_PARTNER_ID }, storage: { name: storageName, @@ -65,10 +70,10 @@ describe('ID5 ID System', function() { } } function getFetchCookieConfig() { - return getUserSyncConfig([getId5FetchConfig()]); + return getUserSyncConfig([getId5FetchConfig(ID5_STORAGE_NAME, 'cookie')]); } function getFetchLocalStorageConfig() { - return getUserSyncConfig([getId5FetchConfig(ID5_COOKIE_NAME, 'html5')]); + return getUserSyncConfig([getId5FetchConfig(ID5_STORAGE_NAME, 'html5')]); } function getValueConfig(value) { return getUserSyncConfig([getId5ValueConfig(value)]); @@ -82,6 +87,37 @@ describe('ID5 ID System', function() { }; } + describe('Check for valid publisher config', function() { + it('should fail with invalid config', function() { + // no config + expect(id5IdSubmodule.getId()).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ })).to.be.eq(undefined); + + // valid params, invalid storage + expect(id5IdSubmodule.getId({ params: { partner: 123 } })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ params: { partner: 123 }, storage: {} })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ params: { partner: 123 }, storage: { name: '' } })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ params: { partner: 123 }, storage: { type: '' } })).to.be.eq(undefined); + + // valid storage, invalid params + expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { } })).to.be.eq(undefined); + expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 'abc' } })).to.be.eq(undefined); + }); + + it('should warn with non-recommended storage params', function() { + let logWarnStub = sinon.stub(utils, 'logWarn'); + + id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 123 } }); + expect(logWarnStub.calledOnce).to.be.true; + logWarnStub.restore(); + + id5IdSubmodule.getId({ storage: { name: ID5_STORAGE_NAME, type: 'cookie', }, params: { partner: 123 } }); + expect(logWarnStub.calledOnce).to.be.true; + logWarnStub.restore(); + }); + }); + describe('Xhr Requests from getId()', function() { const responseHeader = { 'Content-Type': 'application/json' }; let callbackSpy = sinon.spy(); @@ -93,44 +129,45 @@ describe('ID5 ID System', function() { }); - it('should fail if no partner is provided in the config', function() { - expect(id5IdSubmodule.getId()).to.be.eq(undefined); - expect(id5IdSubmodule.getId({ })).to.be.eq(undefined); - expect(id5IdSubmodule.getId({ params: { } })).to.be.eq(undefined); - }); - - it('should call the ID5 server with 1puid field for legacy storedObj format', function () { - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_LEGACY_STORED_OBJ).callback; + it('should call the ID5 server and handle a valid response', function () { + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, undefined).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); expect(request.url).to.contain(ID5_ENDPOINT); expect(request.withCredentials).to.be.true; + expect(requestBody.partner).to.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).to.eq('pbjs'); + expect(requestBody.pd).to.eq(''); expect(requestBody.s).to.eq(''); - expect(requestBody.partner).to.eq(ID5_PARTNER); - expect(requestBody['1puid']).to.eq(ID5_STORED_ID); + expect(requestBody.v).to.eq('$prebid.version$'); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); expect(callbackSpy.calledOnce).to.be.true; expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with signature field for new storedObj format', function () { + it('should call the ID5 server with empty signature field when no stored object', function () { + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, undefined).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.s).to.eq(''); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should call the ID5 server with signature field from stored object', function () { let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; expect(requestBody.s).to.eq(ID5_STORED_SIGNATURE); - expect(requestBody.partner).to.eq(ID5_PARTNER); - expect(requestBody['1puid']).to.eq(''); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); it('should call the ID5 server with pd field when pd config is set', function () { @@ -144,15 +181,9 @@ describe('ID5 ID System', function() { let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; - expect(requestBody.s).to.eq(ID5_STORED_SIGNATURE); expect(requestBody.pd).to.eq(pubData); - expect(requestBody['1puid']).to.eq(''); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); it('should call the ID5 server with empty pd field when pd config is not set', function () { @@ -164,52 +195,39 @@ describe('ID5 ID System', function() { let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; expect(requestBody.pd).to.eq(''); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with nb=1 when no stored value exists', function () { - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + it('should call the ID5 server with nb=1 when no stored value exists and reset after', function () { + coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; expect(requestBody.nbPage).to.eq(1); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); }); - it('should call the ID5 server with incremented nb when stored value exists', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); + it('should call the ID5 server with incremented nb when stored value exists and reset after', function () { + storeNbInCache(ID5_TEST_PARTNER_ID, 1); let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; expect(requestBody.nbPage).to.eq(2); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); }); }); @@ -218,25 +236,24 @@ describe('ID5 ID System', function() { beforeEach(function() { sinon.stub(events, 'getEvents').returns([]); - coreStorage.setCookie(ID5_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); - coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, '', ID5_EXPIRED_COOKIE_DATE); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + coreStorage.removeDataFromLocalStorage(ID5_STORAGE_NAME); + coreStorage.removeDataFromLocalStorage(`${ID5_STORAGE_NAME}_last`); + coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); adUnits = [getAdUnitMock()]; }); afterEach(function() { events.getEvents.restore(); - coreStorage.setCookie(ID5_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); - coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, '', ID5_EXPIRED_COOKIE_DATE); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + coreStorage.removeDataFromLocalStorage(ID5_STORAGE_NAME); + coreStorage.removeDataFromLocalStorage(`${ID5_STORAGE_NAME}_last`); + coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); }); - it('should add stored ID from cookie to bids', function (done) { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); + it('should add stored ID from cache to bids', function (done) { + storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); setSubmoduleRegistry([id5IdSubmodule]); init(config); - config.setConfig(getFetchCookieConfig()); + config.setConfig(getFetchLocalStorageConfig()); requestBidsHook(function () { adUnits.forEach(unit => { @@ -276,43 +293,40 @@ describe('ID5 ID System', function() { }, { adUnits }); }); - it('should set nb=1 in cookie when no stored value exists', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '', ID5_EXPIRED_COOKIE_DATE); + it('should set nb=1 in cache when no stored nb value exists and cached ID', function () { + storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); setSubmoduleRegistry([id5IdSubmodule]); init(config); - config.setConfig(getFetchCookieConfig()); + config.setConfig(getFetchLocalStorageConfig()); let innerAdUnits; requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('1'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(1); }); - it('should increment nb in cookie when stored value exists', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); + it('should increment nb in cache when stored nb value exists and cached ID', function () { + storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + storeNbInCache(ID5_TEST_PARTNER_ID, 1); setSubmoduleRegistry([id5IdSubmodule]); init(config); - config.setConfig(getFetchCookieConfig()); + config.setConfig(getFetchLocalStorageConfig()); let innerAdUnits; requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('2'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(2); }); it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_STORED_OBJ), expStr); - coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, (new Date(Date.now() - 50000).toUTCString()), expStr); - coreStorage.setCookie(ID5_NB_COOKIE_NAME, '1', expStr); + storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + storeInLocalStorage(`${ID5_STORAGE_NAME}_last`, expDaysStr(-1), 1); + storeNbInCache(ID5_TEST_PARTNER_ID, 1); - let id5Config = getFetchCookieConfig(); + let id5Config = getFetchLocalStorageConfig(); id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; setSubmoduleRegistry([id5IdSubmodule]); @@ -322,7 +336,7 @@ describe('ID5 ID System', function() { let innerAdUnits; requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('2'); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(2); expect(server.requests).to.be.empty; events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); @@ -336,41 +350,8 @@ describe('ID5 ID System', function() { const responseHeader = { 'Content-Type': 'application/json' }; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(coreStorage.getCookie(ID5_COOKIE_NAME)).to.be.eq(JSON.stringify(ID5_JSON_RESPONSE)); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); - }); - - it('should call ID5 servers with 1puid and nb=1 post auction if refresh needed for legacy stored object', function () { - let expStr = (new Date(Date.now() + 25000).toUTCString()); - coreStorage.setCookie(ID5_COOKIE_NAME, JSON.stringify(ID5_LEGACY_STORED_OBJ), expStr); - coreStorage.setCookie(`${ID5_COOKIE_NAME}_last`, (new Date(Date.now() - 50000).toUTCString()), expStr); - - let id5Config = getFetchCookieConfig(); - id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; - - setSubmoduleRegistry([id5IdSubmodule]); - init(config); - config.setConfig(id5Config); - - let innerAdUnits; - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('1'); - - expect(server.requests).to.be.empty; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(requestBody['1puid']).to.eq(ID5_STORED_ID); - expect(requestBody.nbPage).to.eq(1); - - const responseHeader = { 'Content-Type': 'application/json' }; - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - - expect(coreStorage.getCookie(ID5_COOKIE_NAME)).to.be.eq(JSON.stringify(ID5_JSON_RESPONSE)); - expect(coreStorage.getCookie(ID5_NB_COOKIE_NAME)).to.be.eq('0'); + expect(decodeURIComponent(getFromLocalStorage(ID5_STORAGE_NAME))).to.be.eq(JSON.stringify(ID5_JSON_RESPONSE)); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); }); }); @@ -380,9 +361,6 @@ describe('ID5 ID System', function() { it('should properly decode from a stored object', function() { expect(id5IdSubmodule.decode(ID5_STORED_OBJ)).to.deep.equal(expectedDecodedObject); }); - it('should properly decode from a legacy stored object', function() { - expect(id5IdSubmodule.decode(ID5_LEGACY_STORED_OBJ)).to.deep.equal(expectedDecodedObject); - }); it('should return undefined if passed a string', function() { expect(id5IdSubmodule.decode('somestring')).to.eq(undefined); }); From b2ecc7eae50b91818b974d417fff614c5de94321 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Thu, 22 Oct 2020 22:15:35 +0200 Subject: [PATCH 097/190] add us_privacy to id5 id module (#5858) --- modules/id5IdSystem.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 9a1dcafc138..5a1fc69a758 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -10,6 +10,7 @@ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { getStorageManager } from '../src/storageManager.js'; +import { uspDataHandler } from '../src/adapterManager.js'; const MODULE_NAME = 'id5Id'; const GVLID = 131; @@ -79,7 +80,8 @@ export const id5IdSubmodule = { const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; const gdprConsentString = hasGdpr ? consentData.consentString : ''; - const url = `https://id5-sync.com/g/v2/${config.params.partner}.json?gdpr_consent=${gdprConsentString}&gdpr=${hasGdpr}`; + const usp = uspDataHandler.getConsentData() || ''; + const url = `https://id5-sync.com/g/v2/${config.params.partner}.json?gdpr_consent=${gdprConsentString}&gdpr=${hasGdpr}&us_privacy=${usp}`; const referer = getRefererInfo(); const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : getLegacyCookieSignature(); const data = { From 081a9f231cedbfd6cde529f0c8c556913e51258e Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Thu, 22 Oct 2020 18:39:35 -0400 Subject: [PATCH 098/190] Rubicon Bid Adapter - Interpret response adds new meta values (#5864) --- modules/rubiconBidAdapter.js | 15 ++++++++++++++- test/spec/modules/rubiconBidAdapter_spec.js | 11 ++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 069a7e7ead5..5d631d2b08e 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -685,6 +685,14 @@ export const spec = { bidObject.dealId = bid.dealid; } + if (bid.adomain) { + utils.deepSetValue(bidObject, 'meta.advertiserDomains', Array.isArray(bid.adomain) ? bid.adomain : [bid.adomain]); + } + + if (utils.deepAccess(bid, 'ext.bidder.rp.advid')) { + utils.deepSetValue(bidObject, 'meta.advertiserId', bid.ext.bidder.rp.advid); + } + let serverResponseTimeMs = utils.deepAccess(responseObj, 'ext.responsetimemillis.rubicon'); if (bidRequest && serverResponseTimeMs) { bidRequest.serverResponseTimeMs = serverResponseTimeMs; @@ -692,6 +700,7 @@ export const spec = { if (utils.deepAccess(bid, 'ext.prebid.type') === VIDEO) { bidObject.mediaType = VIDEO; + utils.deepSetValue(bidObject, 'meta.mediaType', VIDEO); const extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting'); // If ext.prebid.targeting exists, add it as a property value named 'adserverTargeting' @@ -757,7 +766,7 @@ export const spec = { advertiserId: ad.advertiser, networkId: ad.network }, meta: { - advertiserId: ad.advertiser, networkId: ad.network + advertiserId: ad.advertiser, networkId: ad.network, mediaType: BANNER } }; @@ -765,6 +774,10 @@ export const spec = { bid.mediaType = ad.creative_type; } + if (ad.adomain) { + bid.meta.advertiserDomains = Array.isArray(ad.adomain) ? ad.adomain : [ad.adomain]; + } + if (ad.creative_type === VIDEO) { bid.width = associatedBidRequest.params.video.playerWidth; bid.height = associatedBidRequest.params.video.playerHeight; diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index a38743d634a..920a2fb2502 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2127,6 +2127,7 @@ describe('the rubicon adapter', function () { 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', 'size_id': '15', 'ad_id': '6', + 'adomain': ['test.com'], 'advertiser': 7, 'network': 8, 'creative_id': 'crid-9', @@ -2148,6 +2149,7 @@ describe('the rubicon adapter', function () { 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', 'size_id': '43', 'ad_id': '7', + 'adomain': ['test.com'], 'advertiser': 7, 'network': 8, 'creative_id': 'crid-9', @@ -2182,6 +2184,8 @@ describe('the rubicon adapter', function () { expect(bids[0].rubicon.networkId).to.equal(8); expect(bids[0].creativeId).to.equal('crid-9'); expect(bids[0].currency).to.equal('USD'); + expect(bids[0].meta.mediaType).to.equal('banner'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); expect(bids[0].ad).to.contain(`alert('foo')`) .and.to.contain(``) .and.to.contain(`
`); @@ -2715,13 +2719,15 @@ describe('the rubicon adapter', function () { bid: [{ id: '0', impid: 'instream_video1', + adomain: ['test.com'], price: 2, crid: '4259970', ext: { bidder: { rp: { mime: 'application/javascript', - size_id: 201 + size_id: 201, + advid: 12345 } }, prebid: { @@ -2750,6 +2756,9 @@ describe('the rubicon adapter', function () { expect(bids[0].netRevenue).to.equal(true); expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); expect(bids[0].bidderCode).to.equal('rubicon'); expect(bids[0].currency).to.equal('USD'); expect(bids[0].width).to.equal(640); From 22677c17f628170aad2dbf0979a0d3f92727eb28 Mon Sep 17 00:00:00 2001 From: Scott Laufer Date: Thu, 22 Oct 2020 22:47:02 -0400 Subject: [PATCH 099/190] [Synacormedia] Config override for site.domain property (#5885) * CAP-1992 - use get config for site.domain --- modules/synacormediaBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index e0d017a6c51..6608107c93f 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -37,7 +37,7 @@ export const spec = { const openRtbBidRequest = { id: bidderRequest.auctionId, site: { - domain: location.hostname, + domain: config.getConfig('publisherDomain') || location.hostname, page: refererInfo.referer, ref: document.referrer }, From da480f207e8d767017d71bab1bfa607279421b3f Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Fri, 23 Oct 2020 04:01:02 +0100 Subject: [PATCH 100/190] AOL Adapter: User ID Support (#5886) * Added support for passing VMUID to SSP endpoints * Remove 'only' command * Do not create user.ext object unless required * Add support for passing Liveramp envelope to VM SSP * WIP * Updated tests * Remove trailing comma Co-authored-by: slimkrazy --- modules/aolBidAdapter.js | 34 +++++++++++++++ test/spec/modules/aolBidAdapter_spec.js | 57 ++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index 1f43231e495..93c56846639 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -30,6 +30,17 @@ const SYNC_TYPES = { } }; +const SUPPORTED_USER_ID_SOURCES = [ + 'adserver.org', + 'criteo.com', + 'id5-sync.com', + 'intentiq.com', + 'liveintent.com', + 'quantcast.com', + 'verizonmedia.com', + 'liveramp.com' +]; + const pubapiTemplate = template`${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'};misc=${'misc'};${'dynamicParams'}`; const nexageBaseApiTemplate = template`${'host'}/bidRequest?`; const nexageGetApiTemplate = template`dcn=${'dcn'}&pos=${'pos'}&cmd=bid${'dynamicParams'}`; @@ -103,6 +114,12 @@ function resolveEndpointCode(bid) { } } +function getSupportedEids(bid) { + return bid.userIdAsEids.filter(eid => { + return SUPPORTED_USER_ID_SOURCES.includes(eid.source) + }); +} + export const spec = { code: AOL_BIDDERS_CODES.AOL, gvlid: 25, @@ -226,6 +243,13 @@ export const spec = { }, buildOneMobileGetUrl(bid, consentData) { let { dcn, pos, ext } = bid.params; + if (typeof bid.userId === 'object') { + ext = ext || {}; + let eids = getSupportedEids(bid); + eids.forEach(eid => { + ext['eid' + eid.source] = eid.uids[0].id; + }); + } let nexageApi = this.buildOneMobileBaseUrl(bid); if (dcn && pos) { let dynamicParams = this.formatOneMobileDynamicParams(ext, consentData); @@ -292,6 +316,16 @@ export const spec = { utils.deepSetValue(openRtbObject, 'regs.ext.us_privacy', consentData.uspConsent); } + if (typeof bid.userId === 'object') { + openRtbObject.user = openRtbObject.user || {}; + openRtbObject.user.ext = openRtbObject.user.ext || {}; + + let eids = getSupportedEids(bid); + if (eids.length > 0) { + openRtbObject.user.ext.eids = eids + } + } + return openRtbObject; }, isEUConsentRequired(consentData) { diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index dd10a57bbfe..2276e32ece7 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import * as utils from 'src/utils.js'; import {spec} from 'modules/aolBidAdapter.js'; -import {config} from 'src/config.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; const DEFAULT_AD_CONTENT = ''; @@ -80,6 +80,33 @@ describe('AolAdapter', function () { const NEXAGE_URL = 'https://c2shb.ssp.yahoo.com/bidRequest?'; const ONE_DISPLAY_TTL = 60; const ONE_MOBILE_TTL = 3600; + const SUPPORTED_USER_ID_SOURCES = { + 'adserver.org': '100', + 'criteo.com': '200', + 'id5-sync.com': '300', + 'intentiq.com': '400', + 'liveintent.com': '500', + 'quantcast.com': '600', + 'verizonmedia.com': '700', + 'liveramp.com': '800' + }; + + const USER_ID_DATA = { + criteoId: SUPPORTED_USER_ID_SOURCES['criteo.com'], + vmuid: SUPPORTED_USER_ID_SOURCES['verizonmedia.com'], + idl_env: SUPPORTED_USER_ID_SOURCES['liveramp.com'], + lipb: { + lipbid: SUPPORTED_USER_ID_SOURCES['liveintent.com'], + segments: ['100', '200'] + }, + tdid: SUPPORTED_USER_ID_SOURCES['adserver.org'], + id5id: { + uid: SUPPORTED_USER_ID_SOURCES['id5-sync.com'], + ext: {foo: 'bar'} + }, + intentIqId: SUPPORTED_USER_ID_SOURCES['intentiq.com'], + quantcastId: SUPPORTED_USER_ID_SOURCES['quantcast.com'] + }; function createCustomBidRequest({bids, params} = {}) { var bidderRequest = getDefaultBidRequest(); @@ -463,6 +490,18 @@ describe('AolAdapter', function () { '¶m1=val1¶m2=val2¶m3=val3¶m4=val4'); }); + for (const [source, idValue] of Object.entries(SUPPORTED_USER_ID_SOURCES)) { + it(`should set the user ID query param for ${source}`, function () { + let bidRequest = createCustomBidRequest({ + params: getNexageGetBidParams() + }); + bidRequest.bids[0].userId = {}; + bidRequest.bids[0].userIdAsEids = createEidsArray(USER_ID_DATA); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url).to.contain(`&eid${source}=${encodeURIComponent(idValue)}`); + }); + } + it('should return request object for One Mobile POST endpoint when POST configuration is present', function () { let bidConfig = getNexagePostBidParams(); let bidRequest = createCustomBidRequest({ @@ -581,6 +620,22 @@ describe('AolAdapter', function () { } }); }); + + it('returns the bid object with eid array populated with PB set eids', () => { + let userIdBid = Object.assign({ + userId: {} + }, bid); + userIdBid.userIdAsEids = createEidsArray(USER_ID_DATA); + expect(spec.buildOpenRtbRequestData(userIdBid)).to.deep.equal({ + id: 'bid-id', + imp: [], + user: { + ext: { + eids: userIdBid.userIdAsEids + } + } + }); + }); }); describe('getUserSyncs()', function () { From fb958509e90c61c5c8861ff822f7702a847088ed Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Fri, 23 Oct 2020 11:02:26 +0200 Subject: [PATCH 101/190] the code to require local storage will be released in 4.14.0 not 4.13.0 (#5889) --- modules/id5IdSystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index b2369dba07e..80ba451b235 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -51,4 +51,4 @@ pbjs.setConfig({ | storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | | storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | -**ATTENTION:** As of Prebid.js v4.13.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). \ No newline at end of file +**ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). \ No newline at end of file From 0e0be711e03993cabc996b9b2394ea57b28e0721 Mon Sep 17 00:00:00 2001 From: pm-shashank-jain Date: Tue, 27 Oct 2020 16:41:32 +0530 Subject: [PATCH 102/190] piid for hybrid profiles --- modules/pubmaticServerBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/pubmaticServerBidAdapter.js b/modules/pubmaticServerBidAdapter.js index b5cdad75d6d..79a9f56aab0 100644 --- a/modules/pubmaticServerBidAdapter.js +++ b/modules/pubmaticServerBidAdapter.js @@ -626,6 +626,7 @@ export const spec = { ttl: 300, referrer: parsedReferrer, ad: firstSummary ? bid.adm : '', + sspID: bid.id || '', cpm: (parseFloat(summary.bid) || 0).toFixed(2), serverSideResponseTime: partnerResponseTimeObj[summary.bidder] || 0, mi: miObj.hasOwnProperty(summary.bidder) ? miObj[summary.bidder] : UNDEFINED, From 63604921c6cad4f85cac5611b61a694147688dd2 Mon Sep 17 00:00:00 2001 From: Ivan J Date: Tue, 27 Oct 2020 13:13:20 +0100 Subject: [PATCH 103/190] fix: schain complete can be 0 (#5902) --- modules/yieldlabBidAdapter.js | 2 +- test/spec/modules/yieldlabBidAdapter_spec.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index b252c0db2ee..5465a10a884 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -240,7 +240,7 @@ function createTargetingString (obj) { */ function createSchainString (schain) { const ver = schain.ver || '' - const complete = schain.complete || '' + const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : '' const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext'] const nodesString = schain.nodes.reduce((acc, node) => { return acc += `!${keys.map(key => node[key] ? encodeURIComponentWithBangIncluded(node[key]) : '').join(',')}` diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 90fa26fa823..cd2c46a5664 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -138,6 +138,12 @@ describe('yieldlabBidAdapter', function () { } }) + it('passes unencoded schain string to bid request when complete == 0', function () { + REQUEST.schain.complete = 0; + const request = spec.buildRequests([REQUEST]) + expect(request.url).to.include('schain=1.0,0!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') + }) + it('passes encoded referer to bid request', function () { expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring') }) From 894c1d7565c87972b1d95857963c8007c27fdfb9 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Tue, 27 Oct 2020 09:37:39 -0400 Subject: [PATCH 104/190] [AD-1020] JWPlayer RTD: Obtain targeting params from FPD (#5892) * reads jwTargeting from fpd * refactors param extraction * updates documentation * mentions support of config fpd * reduces auction delay examples Co-authored-by: karimJWP --- .../gpt/jwplayerRtdProvider_example.html | 17 +- modules/jwplayerRtdProvider.js | 14 +- modules/jwplayerRtdProvider.md | 35 ++--- test/spec/modules/jwplayerRtdProvider_spec.js | 146 +++++++++++++++--- 4 files changed, 166 insertions(+), 46 deletions(-) diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html index e47f3ca45ec..75eb85a2d8c 100644 --- a/integrationExamples/gpt/jwplayerRtdProvider_example.html +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -11,11 +11,18 @@ var adUnits = [{ code: 'div-gpt-ad-1460505748561-0', - jwTargeting: { - // Note: the following Ids are placeholders and should be replaced with your Ids. - playerID: '123', - mediaID: 'abc' + fpd: { + context: { + 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]], @@ -45,7 +52,7 @@ pbjs.que.push(function() { pbjs.setConfig({ realTimeData: { - auctionDelay: 5000, + auctionDelay: 100, dataProviders: [{ name: "jwplayer", waitForIt: true, diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 2262d39ff3a..17cb978aea3 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -144,6 +144,7 @@ function enrichBidRequest(bidReqConfig, onDone) { * @param {function} onDone */ export function enrichAdUnits(adUnits) { + const fpdFallback = config.getConfig('fpd.context.data.jwTargeting'); adUnits.forEach(adUnit => { const onVatResponse = function (vat) { if (!vat) { @@ -153,12 +154,21 @@ export function enrichAdUnits(adUnits) { addTargetingToBids(adUnit.bids, targeting); }; - loadVat(adUnit.jwTargeting, onVatResponse); + const jwTargeting = extractPublisherParams(adUnit, fpdFallback); + loadVat(jwTargeting, onVatResponse); }); } +export function extractPublisherParams(adUnit, fallback) { + let adUnitTargeting; + try { + adUnitTargeting = adUnit.fpd.context.data.jwTargeting; + } catch (e) {} + return Object.assign({}, fallback, adUnitTargeting); +} + function loadVat(params, onCompletion) { - if (!params) { + if (!params || !Object.keys(params).length) { return; } diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 83e1a4d7772..3c83b6f521c 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -25,16 +25,22 @@ pbjs.setConfig({ } }); ``` -Lastly, include the content's media ID and/or the player's ID in the matching AdUnit: +Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `fpd.context.data`: ```javascript const adUnit = { code: '/19968336/prebid_native_example_1', ... - jwTargeting: { - waitForIt: true, - playerID: 'abcd', - mediaID: '1234' + fpd: { + context: { + data: { + jwTargeting: { + // Note: the following Ids are placeholders and should be replaced with your Ids. + playerID: 'abcd', + mediaID: '1234' + } + } + } } }; @@ -45,34 +51,27 @@ 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. + ##Prefetching -In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var: +In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var and set `waitForIt` to `true`: ```javascript const jwplayerDataProvider = { name: "jwplayer", + waitForIt: true, params: { mediaIDs: ['abc', 'def', 'ghi', 'jkl'] } }; ``` -To ensure that the prefetched targeting information is added to your bid, we strongly suggest setting -`jwTargeting.waitForIt` to `true`. If the prefetch is still in progress at the time of the bid request, the auction will -be delayed until the targeting information specific to the requested adUnits has been obtained. - -```javascript -jwTargeting: { - waitForIt: true, - ... -} -``` - You must also set a value to `auctionDelay` in the config's `realTimeData` object ```javascript realTimeData = { - auctionDelay: 1000, + auctionDelay: 100, ... }; ``` diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 6e301e2a5a6..6e0fd8eb8d7 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -1,5 +1,5 @@ -import { fetchTargetingForMediaId, enrichBidRequest, - getVatFromCache, formatTargetingResponse, getVatFromPlayer, enrichAdUnits, +import { fetchTargetingForMediaId, getVatFromCache, extractPublisherParams, + formatTargetingResponse, getVatFromPlayer, enrichAdUnits, fetchTargetingInformation, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js'; import { server } from 'test/mocks/xhr.js'; @@ -229,9 +229,15 @@ describe('jwplayerRtdProvider', function() { const bid = {}; const adUnit = { - jwTargeting: { - mediaID: mediaIdWithSegment, - playerID: validPlayerID + fpd: { + context: { + data: { + jwTargeting: { + mediaID: mediaIdWithSegment, + playerID: validPlayerID + } + } + } }, bids: [ bid @@ -292,8 +298,14 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - jwTargeting: { - mediaID: testIdForSuccess + fpd: { + context: { + data: { + jwTargeting: { + mediaID: testIdForSuccess + } + } + } }, bids }; @@ -333,8 +345,14 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - jwTargeting: { - mediaID: testIdForSuccess + fpd: { + context: { + data: { + jwTargeting: { + mediaID: testIdForSuccess + } + } + } }, bids }; @@ -374,8 +392,14 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - jwTargeting: { - mediaID: testIdForFailure + fpd: { + context: { + data: { + jwTargeting: { + mediaID: testIdForFailure + } + } + } }, bids }; @@ -388,6 +412,50 @@ describe('jwplayerRtdProvider', function() { }); }); + describe(' Extract Publisher Params', function () { + it('should default to config', function () { + const config = { mediaID: 'test' }; + + const adUnit1 = { fpd: { context: {} } }; + const targeting1 = extractPublisherParams(adUnit1, config); + expect(targeting1).to.deep.equal(config); + + const adUnit2 = { fpd: { context: { data: { jwTargeting: {} } } } }; + const targeting2 = extractPublisherParams(adUnit2, config); + expect(targeting2).to.deep.equal(config); + + const targeting3 = extractPublisherParams(null, config); + expect(targeting3).to.deep.equal(config); + }); + + it('should prioritize adUnit properties ', function () { + const expectedMediaID = 'test_media_id'; + const expectedPlayerID = 'test_player_id'; + const config = { playerID: 'bad_id', mediaID: 'bad_id' }; + + const adUnit = { fpd: { context: { 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); + }); + + it('should use config properties as fallbacks', function () { + const expectedMediaID = 'test_media_id'; + const expectedPlayerID = 'test_player_id'; + const config = { playerID: expectedPlayerID, mediaID: 'bad_id' }; + + const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.have.property('mediaID', expectedMediaID); + expect(targeting).to.have.property('playerID', expectedPlayerID); + }); + + it('should return empty object when Publisher Params are absent', function () { + const targeting = extractPublisherParams(null, null); + expect(targeting).to.deep.equal({}); + }) + }); + describe('jwplayerSubmodule', function () { it('successfully instantiates', function () { expect(jwplayerSubmodule.init()).to.equal(true); @@ -404,16 +472,28 @@ describe('jwplayerRtdProvider', function() { bidReqConfig = { adUnits: [ { - jwTargeting: { - mediaID: validMediaIDs[0] + fpd: { + context: { + data: { + jwTargeting: { + mediaID: validMediaIDs[0] + } + } + } }, bids: [ {}, {} ] }, { - jwTargeting: { - mediaID: validMediaIDs[1] + fpd: { + context: { + data: { + jwTargeting: { + mediaID: validMediaIDs[1] + } + } + } }, bids: [ {}, {} @@ -473,8 +553,14 @@ describe('jwplayerRtdProvider', function() { it('sets targeting data in proper structure', function () { const bid = {}; const adUnitWithMediaId = { - jwTargeting: { - mediaID: testIdForSuccess + fpd: { + context: { + data: { + jwTargeting: { + mediaID: testIdForSuccess + } + } + } }, bids: [ bid @@ -499,12 +585,18 @@ describe('jwplayerRtdProvider', function() { const adUnitCode = 'test_ad_unit'; const bid = {}; const adUnit = { - jwTargeting: { - mediaID: testIdForFailure + fpd: { + context: { + data: { + jwTargeting: { + mediaID: testIdForFailure + } + } + } }, bids: [ bid ] }; - const expectedContentId = 'jw_' + adUnit.jwTargeting.mediaID; + const expectedContentId = 'jw_' + adUnit.fpd.context.data.jwTargeting.mediaID; const expectedTargeting = { content: { id: expectedContentId @@ -522,6 +614,7 @@ describe('jwplayerRtdProvider', function() { const adUnitCode = 'test_ad_unit'; const bid1 = {}; const bid2 = {}; + const bid3 = {}; const adUnitWithMediaId = { code: adUnitCode, mediaID: testIdForSuccess, @@ -532,10 +625,21 @@ describe('jwplayerRtdProvider', function() { bids: [ bid2 ] }; - jwplayerSubmodule.getBidRequestData({ adUnits: [adUnitWithMediaId, adUnitEmpty] }, bidRequestSpy); + const adUnitEmptyfpd = { + code: 'test_ad_unit_empty_fpd', + fpd: { + context: { + id: 'sthg' + } + }, + bids: [ bid3 ] + }; + + jwplayerSubmodule.getBidRequestData({ adUnits: [adUnitWithMediaId, adUnitEmpty, adUnitEmptyfpd] }, bidRequestSpy); expect(bidRequestSpy.calledOnce).to.be.true; expect(bid1).to.not.have.property('jwTargeting'); expect(bid2).to.not.have.property('jwTargeting'); + expect(bid3).to.not.have.property('jwTargeting'); }); }); }); From 6f531d5602ecee8381ffd2d568c3e553a10cca76 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 27 Oct 2020 09:07:46 -0700 Subject: [PATCH 105/190] Add support for Publisher Common ID Module (#5871) - New user id value to be sent to STR Ad Server as `pubcid` of the bid request object Story: [#175125639](https://www.pivotaltracker.com/story/show/175125639) --- modules/sharethroughBidAdapter.js | 6 + .../modules/sharethroughBidAdapter_spec.js | 127 ++++++++++-------- 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 0d183be05df..7df161db713 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -50,6 +50,12 @@ export const sharethroughAdapterSpec = { 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.schain) { query.schain = JSON.stringify(bidRequest.schain); } diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 92f9fd11eeb..d45d1e977e6 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -12,7 +12,13 @@ const bidRequests = [ params: { pkey: 'aaaa1111' }, - userId: { tdid: 'fake-tdid' } + userId: { + tdid: 'fake-tdid', + pubcid: 'fake-pubcid' + }, + crumbs: { + pubcid: 'fake-pubcid-in-crumbs-obj' + } }, { bidder: 'sharethrough', @@ -33,8 +39,8 @@ const bidRequests = [ pkey: 'cccc3333', iframe: true, iframeSize: [500, 500] - }, - }, + } + } ]; const prebidRequests = [ @@ -98,7 +104,7 @@ const prebidRequests = [ skipIframeBusting: false, sizes: [[300, 250], [300, 300], [250, 250], [600, 50]] } - }, + } ]; const bidderResponse = { @@ -121,12 +127,12 @@ const bidderResponse = { }; const setUserAgent = (uaString) => { - window.navigator['__defineGetter__']('userAgent', function () { + window.navigator['__defineGetter__']('userAgent', function() { return uaString; }); }; -describe('sharethrough internal spec', function () { +describe('sharethrough internal spec', function() { let windowSpy, windowTopSpy; beforeEach(function() { @@ -141,7 +147,7 @@ describe('sharethrough internal spec', function () { window.top.STR = undefined; }); - describe('we cannot access top level document', function () { + describe('we cannot access top level document', function() { beforeEach(function() { window.lockedInFrame = true; }); @@ -150,12 +156,12 @@ describe('sharethrough internal spec', function () { window.lockedInFrame = false; }); - it('appends sfp.js to the safeframe', function () { + it('appends sfp.js to the safeframe', function() { sharethroughInternal.handleIframe(); expect(windowSpy.calledOnce).to.be.true; }); - it('does not append anything if sfp.js is already loaded in the safeframe', function () { + it('does not append anything if sfp.js is already loaded in the safeframe', function() { window.STR = { Tag: true }; sharethroughInternal.handleIframe(); expect(windowSpy.notCalled).to.be.true; @@ -163,14 +169,14 @@ describe('sharethrough internal spec', function () { }); }); - describe('we are able to bust out of the iframe', function () { - it('appends sfp.js to window.top', function () { + describe('we are able to bust out of the iframe', function() { + it('appends sfp.js to window.top', function() { sharethroughInternal.handleIframe(); expect(windowSpy.calledOnce).to.be.true; expect(windowTopSpy.calledOnce).to.be.true; }); - it('only appends sfp-set-targeting.js if sfp.js is already loaded on the page', function () { + it('only appends sfp-set-targeting.js if sfp.js is already loaded on the page', function() { window.top.STR = { Tag: true }; sharethroughInternal.handleIframe(); expect(windowSpy.calledOnce).to.be.true; @@ -179,15 +185,15 @@ describe('sharethrough internal spec', function () { }); }); -describe('sharethrough adapter spec', function () { - describe('.code', function () { - it('should return a bidder code of sharethrough', function () { +describe('sharethrough adapter spec', function() { + describe('.code', function() { + it('should return a bidder code of sharethrough', function() { expect(spec.code).to.eql('sharethrough'); }); }); - describe('.isBidRequestValid', function () { - it('should return false if req has no pkey', function () { + describe('.isBidRequestValid', function() { + it('should return false if req has no pkey', function() { const invalidBidRequest = { bidder: 'sharethrough', params: { @@ -197,7 +203,7 @@ describe('sharethrough adapter spec', function () { expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - it('should return false if req has wrong bidder code', function () { + it('should return false if req has wrong bidder code', function() { const invalidBidRequest = { bidder: 'notSharethrough', params: { @@ -207,14 +213,14 @@ describe('sharethrough adapter spec', function () { expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - it('should return true if req is correct', function () { + it('should return true if req is correct', function() { expect(spec.isBidRequestValid(bidRequests[0])).to.eq(true); expect(spec.isBidRequestValid(bidRequests[1])).to.eq(true); - }) + }); }); - describe('.buildRequests', function () { - it('should return an array of requests', function () { + describe('.buildRequests', function() { + it('should return an array of requests', function() { const builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); @@ -222,7 +228,7 @@ describe('sharethrough adapter spec', function () { expect(builtBidRequests[0].method).to.eq('GET'); }); - it('should set the instant_play_capable parameter correctly based on browser userAgent string', function () { + it('should set the instant_play_capable parameter correctly based on browser userAgent string', function() { setUserAgent('Android Chrome/60'); let builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0].data.instant_play_capable).to.be.true; @@ -252,31 +258,31 @@ describe('sharethrough adapter spec', function () { const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('http:'); const bidRequest = spec.buildRequests(bidRequests, null)[0]; expect(bidRequest.data.secure).to.be.false; - stub.restore() + stub.restore(); }); it('should set the secure parameter to true when the protocol is https', function() { const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('https:'); const bidRequest = spec.buildRequests(bidRequests, null)[0]; expect(bidRequest.data.secure).to.be.true; - stub.restore() + stub.restore(); }); it('should set the secure parameter to true when the protocol is neither http or https', function() { const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('about:'); const bidRequest = spec.buildRequests(bidRequests, null)[0]; expect(bidRequest.data.secure).to.be.true; - stub.restore() + stub.restore(); }); - it('should add ccpa parameter if uspConsent is present', function () { + it('should add ccpa parameter if uspConsent is present', function() { const uspConsent = '1YNN'; const bidderRequest = { uspConsent: uspConsent }; const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(bidRequest.data.us_privacy).to.eq(uspConsent); }); - it('should add consent parameters if gdprConsent is present', function () { + it('should add consent parameters if gdprConsent is present', function() { const gdprConsent = { consentString: 'consent_string123', gdprApplies: true }; const bidderRequest = { gdprConsent: gdprConsent }; const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; @@ -284,19 +290,32 @@ describe('sharethrough adapter spec', function () { expect(bidRequest.data.consent_string).to.eq('consent_string123'); }); - it('should handle gdprConsent is present but values are undefined case', function () { + it('should handle gdprConsent is present but values are undefined case', function() { const gdprConsent = { consent_string: undefined, gdprApplies: undefined }; const bidderRequest = { gdprConsent: gdprConsent }; const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(bidRequest.data).to.not.include.any.keys('consent_string') + expect(bidRequest.data).to.not.include.any.keys('consent_string'); }); - it('should add the ttduid parameter if a bid request contains a value for Unified ID from The Trade Desk', function () { + it('should add the ttduid parameter if a bid request contains a value for Unified ID from The Trade Desk', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data.ttduid).to.eq('fake-tdid'); }); - it('should add Sharethrough specific parameters', function () { + it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + + ' userId object of the bidrequest', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + 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 bidRequest = spec.buildRequests(bidRequests)[0]; + delete bidRequest.userId; + expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); + }); + + it('should add Sharethrough specific parameters', function() { const builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0]).to.deep.include({ strData: { @@ -346,8 +365,8 @@ describe('sharethrough adapter spec', function () { }); }); - describe('.interpretResponse', function () { - it('returns a correctly parsed out response', function () { + describe('.interpretResponse', function() { + it('returns a correctly parsed out response', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[0])[0]).to.include( { width: 1, @@ -357,11 +376,11 @@ describe('sharethrough adapter spec', function () { dealId: 'aDealId', currency: 'USD', netRevenue: true, - ttl: 360, + ttl: 360 }); }); - it('returns a correctly parsed out response with largest size when strData.skipIframeBusting is true', function () { + it('returns a correctly parsed out response with largest size when strData.skipIframeBusting is true', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[1])[0]).to.include( { width: 300, @@ -371,11 +390,11 @@ describe('sharethrough adapter spec', function () { dealId: 'aDealId', currency: 'USD', netRevenue: true, - ttl: 360, + ttl: 360 }); }); - it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is true and strData.iframeSize is provided', function () { + it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is true and strData.iframeSize is provided', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[2])[0]).to.include( { width: 500, @@ -385,11 +404,11 @@ describe('sharethrough adapter spec', function () { dealId: 'aDealId', currency: 'USD', netRevenue: true, - ttl: 360, + ttl: 360 }); }); - it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is false and strData.sizes contains [0, 0] only', function () { + it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is false and strData.sizes contains [0, 0] only', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[3])[0]).to.include( { width: 0, @@ -399,11 +418,11 @@ describe('sharethrough adapter spec', function () { dealId: 'aDealId', currency: 'USD', netRevenue: true, - ttl: 360, + ttl: 360 }); }); - it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is false and strData.sizes contains multiple sizes', function () { + it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is false and strData.sizes contains multiple sizes', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[4])[0]).to.include( { width: 300, @@ -413,26 +432,26 @@ describe('sharethrough adapter spec', function () { dealId: 'aDealId', currency: 'USD', netRevenue: true, - ttl: 360, + ttl: 360 }); }); - it('returns a blank array if there are no creatives', function () { + it('returns a blank array if there are no creatives', function() { const bidResponse = { body: { creatives: [] } }; expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty; }); - it('returns a blank array if body object is empty', function () { + it('returns a blank array if body object is empty', function() { const bidResponse = { body: {} }; expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty; }); - it('returns a blank array if body is null', function () { + it('returns a blank array if body is null', function() { const bidResponse = { body: null }; expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty; }); - it('correctly generates ad markup when skipIframeBusting is false', function () { + it('correctly generates ad markup when skipIframeBusting is false', function() { const adMarkup = spec.interpretResponse(bidderResponse, prebidRequests[0])[0].ad; let resp = null; @@ -447,7 +466,7 @@ describe('sharethrough adapter spec', function () { expect(adMarkup).to.match(/handleIframe/); }); - it('correctly generates ad markup when skipIframeBusting is true', function () { + it('correctly generates ad markup when skipIframeBusting is true', function() { const adMarkup = spec.interpretResponse(bidderResponse, prebidRequests[1])[0].ad; let resp = null; @@ -461,11 +480,11 @@ describe('sharethrough adapter spec', function () { }); }); - describe('.getUserSyncs', function () { + describe('.getUserSyncs', function() { const cookieSyncs = ['cookieUrl1', 'cookieUrl2', 'cookieUrl3']; const serverResponses = [{ body: { cookieSyncUrls: cookieSyncs } }]; - it('returns an array of correctly formatted user syncs', function () { + it('returns an array of correctly formatted user syncs', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, null, 'fake-privacy-signal'); expect(syncArray).to.deep.equal([ { type: 'image', url: 'cookieUrl1&us_privacy=fake-privacy-signal' }, @@ -474,22 +493,22 @@ describe('sharethrough adapter spec', function () { ); }); - it('returns an empty array if serverResponses is empty', function () { + it('returns an empty array if serverResponses is empty', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: true }, []); expect(syncArray).to.be.an('array').that.is.empty; }); - it('returns an empty array if the body is null', function () { + it('returns an empty array if the body is null', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: true }, [{ body: null }]); expect(syncArray).to.be.an('array').that.is.empty; }); - it('returns an empty array if the body.cookieSyncUrls is missing', function () { + it('returns an empty array if the body.cookieSyncUrls is missing', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: true }, [{ body: { creatives: ['creative'] } }]); expect(syncArray).to.be.an('array').that.is.empty; }); - it('returns an empty array if pixels are not enabled', function () { + it('returns an empty array if pixels are not enabled', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: false }, serverResponses); expect(syncArray).to.be.an('array').that.is.empty; }); From 5bda2f162f155bb88c213a208d323088c422f0a3 Mon Sep 17 00:00:00 2001 From: Yevhenii Melnyk Date: Tue, 27 Oct 2020 18:09:28 +0100 Subject: [PATCH 106/190] Liveintent id module doesn't fall back to the default implementations of ajax, pixel and storage. (#5859) Liveintent id module reads an email hash that is provided in the configuration. --- modules/liveIntentIdSystem.js | 56 +++++-- modules/userId/index.js | 2 +- package-lock.json | 158 ++++++++++++++++++- package.json | 2 +- test/spec/modules/liveIntentIdSystem_spec.js | 123 +++++++++------ 5 files changed, 276 insertions(+), 65 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 7981b62dc51..4f18c73ad2a 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -5,14 +5,32 @@ * @requires module:modules/userId */ import * as utils from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; +import { triggerPixel } from '../src/utils.js'; +import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { LiveConnect } from 'live-connect-js/cjs/live-connect.js'; -import { uspDataHandler } from '../src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; const MODULE_NAME = 'liveIntentId'; export const storage = getStorageManager(null, MODULE_NAME); +const calls = { + ajaxGet: (url, onSuccess, onError, timeout) => { + ajaxBuilder(timeout)( + url, + { + success: onSuccess, + error: onError + }, + undefined, + { + method: 'GET', + withCredentials: true + } + ) + }, + pixelGet: (url, onload) => triggerPixel(url, onload) +} let eventFired = false; let liveConnect = null; @@ -64,18 +82,30 @@ function initializeLiveConnect(configParams) { if (configParams.partner) { identityResolutionConfig.source = configParams.partner } + if (configParams.ajaxTimeout) { + identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout; + } const liveConnectConfig = parseLiveIntentCollectorConfig(configParams.liCollectConfig); liveConnectConfig.wrapperName = 'prebid'; liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; + if (configParams.emailHash) { + liveConnectConfig.eventSource = { hash: configParams.emailHash } + } const usPrivacyString = uspDataHandler.getConsentData(); if (usPrivacyString) { liveConnectConfig.usPrivacyString = usPrivacyString; } + const gdprConsent = gdprDataHandler.getConsentData() + if (gdprConsent) { + liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; + liveConnectConfig.gdprConsent = gdprConsent.consentString; + } - // The second param is the storage object, which means that all LS & Cookie manipulation will go through PBJS utils. - liveConnect = LiveConnect(liveConnectConfig, storage); + // The second param is the storage object, LS & Cookie manipulation uses PBJS utils. + // The third param is the ajax and pixel object, the ajax and pixel use PBJS utils. + liveConnect = LiveConnect(liveConnectConfig, storage, calls); return liveConnect; } @@ -132,11 +162,9 @@ export const liveIntentIdSubmodule = { return; } tryFireEvent(); - // Don't do the internal ajax call, but use the composed url and fire it via PBJS ajax module - const url = liveConnect.resolutionCallUrl(); - const result = function (callback) { - const callbacks = { - success: response => { + const result = function(callback) { + liveConnect.resolve( + response => { let responseObj = {}; if (response) { try { @@ -147,14 +175,14 @@ export const liveIntentIdSubmodule = { } callback(responseObj); }, - error: error => { + error => { utils.logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); callback(); } - }; - ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); - }; - return {callback: result}; + ) + } + + return { callback: result }; } }; diff --git a/modules/userId/index.js b/modules/userId/index.js index 83573be8682..a5e5fd4eff1 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -83,9 +83,9 @@ * @property {(string|undefined)} publisherId - the unique identifier of the publisher in question * @property {(string|undefined)} ajaxTimeout - the number of milliseconds a resolution request can take before automatically being terminated * @property {(array|undefined)} identifiersToResolve - the identifiers from either ls|cookie to be attached to the getId query - * @property {(string|undefined)} providedIdentifierName - defines the name of an identifier that can be found in local storage or in the cookie jar that can be sent along with the getId request. This parameter should be used whenever a customer is able to provide the most stable identifier possible * @property {(LiveIntentCollectConfig|undefined)} liCollectConfig - the config for LiveIntent's collect requests * @property {(string|undefined)} pd - publisher provided data for reconciling ID5 IDs + * @property {(string|undefined)} emailHash - if provided, the hashed email address of a user */ /** diff --git a/package-lock.json b/package-lock.json index 81ff16fdc5d..bf95487ed9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.11.0-pre", + "version": "4.14.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -7800,7 +7800,9 @@ "is-regex": "^1.0.5", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", - "object.assign": "^4.1.0" + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" }, "dependencies": { "is-regex": { @@ -12029,6 +12031,12 @@ "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", "dev": true }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -15719,9 +15727,9 @@ "dev": true }, "live-connect-js": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-1.1.10.tgz", - "integrity": "sha512-G/LJKN3b21DZILCQRyataC/znLvJRyogtu7mAkKlkhP9B9UJ8bcOL7ihW/clD2PsT4hVUkeabHhUGsPCmhsjFw==", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-1.1.23.tgz", + "integrity": "sha512-alOXlYyDdMXt8zzCIs3+iCrdi6r/69c7YRN3sMETa3b2cCOxep3i9j2O0iepk2hxT5JxiR1MvqlqdWAL9d2Hcg==", "requires": { "@kiosked/ulid": "^3.0.0", "abab": "^2.0.3", @@ -20707,6 +20715,146 @@ "es-abstract": "^1.17.0-next.1" } }, + "string.prototype.trimend": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", + "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", + "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", diff --git a/package.json b/package.json index cf82e7a9e95..d7d71f28647 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,6 @@ "fun-hooks": "^0.9.9", "jsencrypt": "^3.0.0-rc.1", "just-clone": "^1.0.2", - "live-connect-js": "1.1.10" + "live-connect-js": "^1.1.23" } } diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 80f776168c4..aae60cbcd19 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -1,46 +1,50 @@ -import {liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage} from 'modules/liveIntentIdSystem.js'; +import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; import * as utils from 'src/utils.js'; -import {uspDataHandler} from '../../../src/adapterManager.js'; -import {server} from 'test/mocks/xhr.js'; +import { gdprDataHandler, uspDataHandler } from '../../../src/adapterManager.js'; +import { server } from 'test/mocks/xhr.js'; const PUBLISHER_ID = '89899'; const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; const responseHeader = {'Content-Type': 'application/json'} -describe('LiveIntentId', function () { - let pixel = {}; +describe('LiveIntentId', function() { let logErrorStub; - let consentDataStub; + let uspConsentDataStub; + let gdprConsentDataStub; let getCookieStub; let getDataFromLocalStorageStub; let imgStub; - beforeEach(function () { - imgStub = sinon.stub(window, 'Image').returns(pixel); + beforeEach(function() { + imgStub = sinon.stub(utils, 'triggerPixel'); getCookieStub = sinon.stub(storage, 'getCookie'); getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); logErrorStub = sinon.stub(utils, 'logError'); - consentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); }); - afterEach(function () { - pixel = {}; + afterEach(function() { imgStub.restore(); getCookieStub.restore(); getDataFromLocalStorageStub.restore(); logErrorStub.restore(); - consentDataStub.restore(); + uspConsentDataStub.restore(); + gdprConsentDataStub.restore(); resetLiveIntentIdSubmodule(); }); - it('should initialize LiveConnect with a us privacy string when getId, and include it in all requests', function () { - consentDataStub.returns('1YNY'); + 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, + consentString: 'consentDataString' + }) let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; - expect(pixel.src).to.match(/.*us_privacy=1YNY/); submoduleCallback(callBackSpy); - let request = server.requests[0]; - expect(request.url).to.match(/.*us_privacy=1YNY/); + let request = server.requests[1]; + expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&gdpr_consent=consentDataString.*/); request.respond( 200, responseHeader, @@ -49,9 +53,22 @@ describe('LiveIntentId', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should fire an event when getId', function () { + it('should fire an event when getId', function() { + uspConsentDataStub.returns('1YNY'); + gdprConsentDataStub.returns({ + gdprApplies: true, + consentString: 'consentDataString' + }) liveIntentIdSubmodule.getId(defaultConfigParams); - expect(pixel.src).to.match(/https:\/\/rp.liadm.com\/p\?wpn=prebid.*/) + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?wpn=prebid.*us_privacy=1YNY.*&gdpr=1&gdpr_consent=consentDataString.*/); + }); + + it('should fire an event when getId and a hash is provided', function() { + liveIntentIdSubmodule.getId({ params: { + ...defaultConfigParams, + emailHash: '58131bc547fb87af94cebdaf3102321f' + }}); + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) }); it('should initialize LiveConnect with the config params when decode and emit an event', function () { @@ -64,40 +81,52 @@ describe('LiveIntentId', function () { collectorUrl: 'https://collector.liveintent.com' } } - } }); - expect(pixel.src).to.match(/https:\/\/collector.liveintent.com\/p\?aid=a-0001&wpn=prebid.*/) + }}); + expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?aid=a-0001&wpn=prebid.*/); }); - it('should initialize LiveConnect and emit an event with a us privacy string when decode', function () { - consentDataStub.returns('1YNY'); + it('should initialize LiveConnect and emit an event with a privacy string when decode', function() { + uspConsentDataStub.returns('1YNY'); + gdprConsentDataStub.returns({ + gdprApplies: false, + consentString: 'consentDataString' + }) liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(pixel.src).to.match(/.*us_privacy=1YNY/); + expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*/); }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function () { - const result = liveIntentIdSubmodule.decode({additionalData: 'data'}); + it('should fire an event when decode and a hash is provided', function() { + liveIntentIdSubmodule.decode({}, { params: { + ...defaultConfigParams.params, + emailHash: '58131bc547fb87af94cebdaf3102321f' + }}); + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + }); + + it('should not return a decoded identifier when the unifiedId is not present in the value', function() { + const result = liveIntentIdSubmodule.decode({ additionalData: 'data' }); expect(result).to.be.undefined; }); - it('should fire an event when decode', function () { + it('should fire an event when decode', function() { liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(pixel.src).to.be.not.null + expect(server.requests[0].url).to.be.not.null }); - it('should initialize LiveConnect and send data only once', function () { + it('should initialize LiveConnect and send data only once', function() { liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(imgStub.calledOnce).to.be.true; + expect(server.requests.length).to.be.eq(1); }); - it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function () { + it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = server.requests[1]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899'); request.respond( 200, @@ -107,7 +136,7 @@ describe('LiveIntentId', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function () { + it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { @@ -118,7 +147,7 @@ describe('LiveIntentId', function () { } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = server.requests[1]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899'); request.respond( 200, @@ -128,12 +157,12 @@ describe('LiveIntentId', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should call the LiveIntent Identity Exchange endpoint, with no additional query params', function () { + it('should call the LiveIntent Identity Exchange endpoint, with no additional query params', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = server.requests[1]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899'); request.respond( 200, @@ -143,12 +172,12 @@ describe('LiveIntentId', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should log an error and continue to callback if ajax request errors', function () { + it('should log an error and continue to callback if ajax request errors', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = server.requests[1]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899'); request.respond( 503, @@ -159,13 +188,13 @@ describe('LiveIntentId', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should include the LiveConnect identifier when calling the LiveIntent Identity Exchange endpoint', function () { + it('should include the LiveConnect identifier when calling the LiveIntent Identity Exchange endpoint', function() { const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' getDataFromLocalStorageStub.withArgs('_li_duid').returns(oldCookie); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = server.requests[1]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}`); request.respond( 200, @@ -175,7 +204,7 @@ describe('LiveIntentId', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should include the LiveConnect identifier and additional Identifiers to resolve', function () { + it('should include the LiveConnect identifier and additional Identifiers to resolve', function() { const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' getDataFromLocalStorageStub.withArgs('_li_duid').returns(oldCookie); getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc'); @@ -188,7 +217,7 @@ describe('LiveIntentId', function () { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = server.requests[1]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&_thirdPC=third-pc`); request.respond( 200, @@ -198,7 +227,7 @@ describe('LiveIntentId', function () { expect(callBackSpy.calledOnce).to.be.true; }); - it('should include an additional identifier value to resolve even if it is an object', function () { + it('should include an additional identifier value to resolve even if it is an object', function() { getCookieStub.returns(null); getDataFromLocalStorageStub.withArgs('_thirdPC').returns({'key': 'value'}); const configParams = { params: { @@ -210,7 +239,7 @@ describe('LiveIntentId', function () { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = server.requests[1]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D'); request.respond( 200, @@ -219,4 +248,10 @@ describe('LiveIntentId', function () { ); expect(callBackSpy.calledOnce).to.be.true; }); + + it('should send an error when the cookie jar throws an unexpected error', function() { + getCookieStub.throws('CookieError', 'A message'); + liveIntentIdSubmodule.getId(defaultConfigParams); + expect(imgStub.getCall(0).args[0]).to.match(/.*ae=.+/); + }); }); From 28c1981e420f2250f93447ba25b6c99fa26a5c88 Mon Sep 17 00:00:00 2001 From: pm-shashank-jain Date: Wed, 28 Oct 2020 11:15:11 +0530 Subject: [PATCH 107/190] removed fix for piid from staged_nightly --- modules/pubmaticServerBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/pubmaticServerBidAdapter.js b/modules/pubmaticServerBidAdapter.js index 79a9f56aab0..b5cdad75d6d 100644 --- a/modules/pubmaticServerBidAdapter.js +++ b/modules/pubmaticServerBidAdapter.js @@ -626,7 +626,6 @@ export const spec = { ttl: 300, referrer: parsedReferrer, ad: firstSummary ? bid.adm : '', - sspID: bid.id || '', cpm: (parseFloat(summary.bid) || 0).toFixed(2), serverSideResponseTime: partnerResponseTimeObj[summary.bidder] || 0, mi: miObj.hasOwnProperty(summary.bidder) ? miObj[summary.bidder] : UNDEFINED, From 7da5dadce612890bbf2c101a61b02f6a546e2178 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 28 Oct 2020 02:15:55 -0700 Subject: [PATCH 108/190] aol bid adapter: support IE (#5894) * support IE in aol spec * array includes not supported IE11 --- modules/aolBidAdapter.js | 2 +- test/spec/modules/aolBidAdapter_spec.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index 93c56846639..c899da32340 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -116,7 +116,7 @@ function resolveEndpointCode(bid) { function getSupportedEids(bid) { return bid.userIdAsEids.filter(eid => { - return SUPPORTED_USER_ID_SOURCES.includes(eid.source) + return SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) !== -1 }); } diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 2276e32ece7..11e1a317b70 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -490,7 +490,7 @@ describe('AolAdapter', function () { '¶m1=val1¶m2=val2¶m3=val3¶m4=val4'); }); - for (const [source, idValue] of Object.entries(SUPPORTED_USER_ID_SOURCES)) { + Object.keys(SUPPORTED_USER_ID_SOURCES).forEach(source => { it(`should set the user ID query param for ${source}`, function () { let bidRequest = createCustomBidRequest({ params: getNexageGetBidParams() @@ -498,9 +498,9 @@ describe('AolAdapter', function () { bidRequest.bids[0].userId = {}; bidRequest.bids[0].userIdAsEids = createEidsArray(USER_ID_DATA); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(`&eid${source}=${encodeURIComponent(idValue)}`); + expect(request.url).to.contain(`&eid${source}=${encodeURIComponent(SUPPORTED_USER_ID_SOURCES[source])}`); }); - } + }); it('should return request object for One Mobile POST endpoint when POST configuration is present', function () { let bidConfig = getNexagePostBidParams(); From 9d37b25e9b36852761edc5f650803396112525cb Mon Sep 17 00:00:00 2001 From: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Date: Wed, 28 Oct 2020 15:25:44 +0530 Subject: [PATCH 109/190] add check for config to make sure its defined (#5873) --- modules/consentManagement.js | 2 +- modules/consentManagementUsp.js | 2 +- test/spec/modules/consentManagementUsp_spec.js | 7 +++++++ test/spec/modules/consentManagement_spec.js | 6 ++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/consentManagement.js b/modules/consentManagement.js index f44fde0554d..1060fdb5cc5 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -451,7 +451,7 @@ export function resetConsentData() { export function setConsentConfig(config) { // if `config.gdpr` or `config.usp` exist, assume new config format. // else for backward compatability, just use `config` - config = config.gdpr || config.usp ? config.gdpr : config; + config = config && (config.gdpr || config.usp ? config.gdpr : config); if (!config || typeof config !== 'object') { utils.logWarn('consentManagement config not defined, exiting consent manager'); return; diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index e4d5c12eb46..3edacb41549 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -269,7 +269,7 @@ export function resetConsentData() { * @param {object} config required; consentManagementUSP module config settings; usp (string), timeout (int), allowAuctionWithoutConsent (boolean) */ export function setConsentConfig(config) { - config = config.usp; + config = config && config.usp; if (!config || typeof config !== 'object') { utils.logWarn('consentManagement.usp config not defined, exiting usp consent manager'); return; diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index ee4140afa10..7d3cd48a8e4 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -64,6 +64,13 @@ describe('consentManagement', function () { sinon.assert.calledOnce(utils.logWarn); sinon.assert.notCalled(utils.logInfo); }); + + it('should exit consentManagementUsp module if config is "undefined"', function() { + setConsentConfig(undefined); + expect(consentAPI).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + sinon.assert.notCalled(utils.logInfo); + }); }); describe('valid setConsentConfig value', function () { diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index deaacbc5a28..cf5c578502f 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -39,6 +39,12 @@ describe('consentManagement', function () { expect(userCMP).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); + + it('should exit consentManagement module if config is "undefined"', function() { + setConsentConfig(undefined); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); }); describe('valid setConsentConfig value', function () { From 9dbc9985c3457631b269168d0ccfda60402e3797 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Wed, 28 Oct 2020 18:25:55 +0100 Subject: [PATCH 110/190] Prebid 4.14.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d7d71f28647..98bd1bbbce5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.14.0-pre", + "version": "4.14.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From d966829b23235e5782d01489fe3162e0cc8c9ff8 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Wed, 28 Oct 2020 18:41:41 +0100 Subject: [PATCH 111/190] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98bd1bbbce5..c6c78a45c5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.14.0", + "version": "4.15.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 78a2d289ade4d446baa4102d1b7484c77977a6f8 Mon Sep 17 00:00:00 2001 From: Mike Sperone Date: Thu, 29 Oct 2020 10:35:03 -0500 Subject: [PATCH 112/190] Media type renderers (#5760) * allow publisher to define a renderer specific to the mediaType * validate outstream bid with a renderer defined on the video mediaType * get the mediaTypes from the bidReqest * tests for publisher-defined, media-specific renderers * use single quote * undo inadvertent package-lock.json changes Co-authored-by: Michael Sperone --- src/auction.js | 23 ++++++++++++++++++++--- src/video.js | 2 +- test/spec/auctionmanager_spec.js | 30 ++++++++++++++++++++++++++++++ test/spec/video_spec.js | 23 +++++++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/auction.js b/src/auction.js index 5858c3edf78..e0b8feb5c62 100644 --- a/src/auction.js +++ b/src/auction.js @@ -512,9 +512,26 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode); const adUnitRenderer = bidReq && bidReq.renderer; - if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly && isBoolean(adUnitRenderer.backupOnly) && bid.renderer)) { - bidObject.renderer = Renderer.install({ url: adUnitRenderer.url }); - bidObject.renderer.setRender(adUnitRenderer.render); + // a publisher can also define a renderer for a mediaType + const bidObjectMediaType = bidObject.mediaType; + const bidMediaType = bidReq && + bidReq.mediaTypes && + bidReq.mediaTypes[bidObjectMediaType]; + + var mediaTypeRenderer = bidMediaType && bidMediaType.renderer; + + var renderer = null; + + // the renderer for the mediaType takes precendence + if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render) { + renderer = mediaTypeRenderer; + } else if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly && isBoolean(adUnitRenderer.backupOnly) && bid.renderer)) { + renderer = adUnitRenderer; + } + + if (renderer) { + bidObject.renderer = Renderer.install({ url: renderer.url }); + bidObject.renderer.setRender(renderer.render); } // Use the config value 'mediaTypeGranularity' if it has been defined for mediaType, else use 'customPriceBucket' diff --git a/src/video.js b/src/video.js index befeb2ded39..20df7a92442 100644 --- a/src/video.js +++ b/src/video.js @@ -59,7 +59,7 @@ export const checkVideoBidSetup = hook('sync', function(bid, bidRequest, videoMe // outstream bids require a renderer on the bid or pub-defined on adunit if (context === OUTSTREAM) { - return !!(bid.renderer || bidRequest.renderer); + return !!(bid.renderer || bidRequest.renderer || videoMediaType.renderer); } return true; diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index e35b1406fbf..1517b5d69d8 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -763,6 +763,36 @@ describe('auctionmanager.js', function () { assert.equal(addedBid.renderer.url, 'renderer.js'); }); + it('installs publisher-defined renderers for a media type', function () { + const renderer = { + url: 'videoRenderer.js', + render: (bid) => bid + }; + let myBid = mockBid(); + let bidRequest = mockBidRequest(myBid); + + bidRequest.bids[0] = { + ...bidRequest.bids[0], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + renderer + } + } + }; + makeRequestsStub.returns([bidRequest]); + + myBid.mediaType = 'video'; + spec.interpretResponse.returns(myBid); + auction.callBids(); + + const addedBid = auction.getBidsReceived().pop(); + assert.equal(addedBid.renderer.url, renderer.url); + }); + it('bid for a regular unit and a video unit', function() { let renderer = { url: 'renderer.js', diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 72a585049c3..3ce8ba081da 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -71,6 +71,29 @@ describe('video.js', function () { expect(valid).to.equal(true); }); + it('validates valid outstream bids with a publisher defined renderer', function () { + const bid = { + requestId: '123abc', + }; + const bidRequests = [{ + bids: [{ + bidId: '123abc', + bidder: 'appnexus', + mediaTypes: { + video: { + context: 'outstream', + renderer: { + url: 'render.url', + render: () => true, + } + } + } + }] + }]; + const valid = isValidVideoBid(bid, bidRequests); + expect(valid).to.equal(true); + }); + it('catches invalid outstream bids', function () { const bid = { requestId: '123abc' From 9a75d26d44114a3a4e8c0e2974bf881b7788e02f Mon Sep 17 00:00:00 2001 From: lowendavid <66423906+lowendavid@users.noreply.github.com> Date: Thu, 29 Oct 2020 18:08:39 +0100 Subject: [PATCH 113/190] Added GVL_ID & addtl_consent for smartadserverBidAdapter (#5870) * SIM-875 Adding GVL_ID * SIM-875 Added addtl_consent * SIM-875 removing trailing whitespaces --- modules/smartadserverBidAdapter.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 8462e749b91..ed9003e3b4d 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -13,8 +13,10 @@ import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'smartadserver'; +const GVL_ID = 45; export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, aliases: ['smart'], // short code supportedMediaTypes: [BANNER, VIDEO], /** @@ -99,6 +101,7 @@ export const spec = { } if (bidderRequest && bidderRequest.gdprConsent) { + payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; payload.gdpr_consent = bidderRequest.gdprConsent.consentString; payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side } From 51f240519da480d09c7258bb6599abcfee62f93a Mon Sep 17 00:00:00 2001 From: Krushmedia <71434282+Krushmedia@users.noreply.github.com> Date: Fri, 30 Oct 2020 14:33:02 +0200 Subject: [PATCH 114/190] New krushmedia Prebid.js adapter (#5833) * inital * fix * fix * fix * fix * fix * fix * add maintener to md * Added native support Co-authored-by: Aiholkin --- modules/krushmediaBidAdapter.js | 104 ++++++ modules/krushmediaBidAdapter.md | 80 +++++ .../spec/modules/krushmediaBidAdapter_spec.js | 304 ++++++++++++++++++ 3 files changed, 488 insertions(+) create mode 100644 modules/krushmediaBidAdapter.js create mode 100644 modules/krushmediaBidAdapter.md create mode 100644 test/spec/modules/krushmediaBidAdapter_spec.js diff --git a/modules/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js new file mode 100644 index 00000000000..f70500cc101 --- /dev/null +++ b/modules/krushmediaBidAdapter.js @@ -0,0 +1,104 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'krushmedia'; +const AD_URL = 'https://ads4.krushmedia.com/?c=rtb&m=hb'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.key))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + + const placements = []; + const request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + key: bid.params.key, + bidId: bid.bidId, + traffic: bid.params.traffic || BANNER, + schain: bid.schain || {}, + }; + + if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + placement.sizes = bid.mediaTypes[BANNER].sizes; + } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { + placement.wPlayer = bid.mediaTypes[VIDEO].playerSize[0]; + placement.hPlayer = bid.mediaTypes[VIDEO].playerSize[1]; + } else if (bid.mediaTypes && bid.mediaTypes[NATIVE]) { + placement.native = bid.mediaTypes[NATIVE]; + } + placements.push(placement); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, +}; + +registerBidder(spec); diff --git a/modules/krushmediaBidAdapter.md b/modules/krushmediaBidAdapter.md new file mode 100644 index 00000000000..7bf7c4fe491 --- /dev/null +++ b/modules/krushmediaBidAdapter.md @@ -0,0 +1,80 @@ +# Overview + +``` +Module Name: krushmedia Bidder Adapter +Module Type: krushmedia Bidder Adapter +Maintainer: adapter@krushmedia.com +``` + +# Description + +Module that connects to krushmedia demand sources + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'krushmedia', + params: { + key: 0, + traffic: 'banner' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'krushmedia', + params: { + key: 0, + traffic: 'video' + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'krushmedia', + params: { + key: 0, + traffic: 'native' + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js new file mode 100644 index 00000000000..2673627bc6d --- /dev/null +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -0,0 +1,304 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/krushmediaBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; + +describe('KrushmediabBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'krushmedia', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + key: 783, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.key; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://ads4.krushmedia.com/?c=rtb&m=hb'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('key', 'bidId', 'traffic', 'sizes', 'schain'); + expect(placement.key).to.equal(783); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('key', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns valid data for mediatype native', function () { + const native = { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + }; + + bid.mediaTypes = {}; + bid.params.traffic = NATIVE; + bid.mediaTypes[NATIVE] = native; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('key', 'bidId', 'traffic', 'native', 'schain'); + expect(placement.traffic).to.equal(NATIVE); + expect(placement.native).to.equal(native); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From 348a4f5bd00cf8292b613afe9c9f4d9a3bad23c7 Mon Sep 17 00:00:00 2001 From: etargetse <40423120+etargetse@users.noreply.github.com> Date: Fri, 30 Oct 2020 16:36:17 +0100 Subject: [PATCH 115/190] eTarget: adapter update (#5881) * adapter update Send response reason * Update etargetBidAdapter.js Adding optional response parameter * Update etargetBidAdapter_spec.js --- modules/etargetBidAdapter.js | 1 + test/spec/modules/etargetBidAdapter_spec.js | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/etargetBidAdapter.js b/modules/etargetBidAdapter.js index 5e07561044a..42c991a17a4 100644 --- a/modules/etargetBidAdapter.js +++ b/modules/etargetBidAdapter.js @@ -96,6 +96,7 @@ export const spec = { currency: data.win_cur, netRevenue: true, ttl: 360, + reason: data.reason ? data.reason : 'none', ad: data.banner, vastXml: data.vast_content, vastUrl: data.vast_link, diff --git a/test/spec/modules/etargetBidAdapter_spec.js b/test/spec/modules/etargetBidAdapter_spec.js index 4f5e0c224ec..2dbf6cd68c5 100644 --- a/test/spec/modules/etargetBidAdapter_spec.js +++ b/test/spec/modules/etargetBidAdapter_spec.js @@ -154,6 +154,7 @@ describe('etarget adapter', function () { assert.equal(result.height, 250); assert.equal(result.currency, 'EUR'); assert.equal(result.netRevenue, true); + assert.isNotNull(result.reason); assert.equal(result.ttl, 360); assert.equal(result.ad, ''); assert.equal(result.transactionId, '5f33781f-9552-4ca1'); From e3a9c6056a5e2b575f8dd0af57f5dcebfe601f35 Mon Sep 17 00:00:00 2001 From: Steve Alliance Date: Mon, 2 Nov 2020 04:33:47 -0500 Subject: [PATCH 116/190] DMX Fix video bug (#5910) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * adding CCPA support for DMX * adding test for ccpa and gdpr * districtm dmx adding deal id field * idsync support ccpa & gdpr * fix error on vast response that failed Co-authored-by: Steve Alliance Co-authored-by: Luis Co-authored-by: Steve Alliance Co-authored-by: Steve Alliance Co-authored-by: steve-a-districtm --- modules/districtmDMXBidAdapter.js | 72 ++++++++----------- .../modules/districtmDmxBidAdapter_spec.js | 7 +- 2 files changed, 32 insertions(+), 47 deletions(-) diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index bcb2bb97210..4d8154e429e 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -39,9 +39,9 @@ export const spec = { nBid.requestId = nBid.impid; nBid.width = nBid.w || width; nBid.height = nBid.h || height; - nBid.mediaType = bid.mediaTypes && bid.mediaTypes.video ? 'video' : null; + nBid.mediaType = bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner'; if (nBid.mediaType) { - nBid.vastXml = cleanVast(nBid.adm); + nBid.vastXml = cleanVast(nBid.adm, nBid.nurl); } if (nBid.dealid) { nBid.dealId = nBid.dealid; @@ -154,19 +154,16 @@ export const spec = { if (dmx.mediaTypes && dmx.mediaTypes.video) { obj.video = { topframe: 1, - skip: dmx.mediaTypes.video.skippable || 0, + skip: dmx.mediaTypes.video.skip || 0, linearity: dmx.mediaTypes.video.linearity || 1, minduration: dmx.mediaTypes.video.minduration || 5, maxduration: dmx.mediaTypes.video.maxduration || 60, - playbackmethod: getPlaybackmethod(dmx.mediaTypes.video.playback_method), + playbackmethod: dmx.mediaTypes.video.playbackmethod || [2], api: getApi(dmx.mediaTypes.video), mimes: dmx.mediaTypes.video.mimes || ['video/mp4'], protocols: getProtocols(dmx.mediaTypes.video), - w: dmx.mediaTypes.video.playerSize[0][0], h: dmx.mediaTypes.video.playerSize[0][1], - format: dmx.mediaTypes.video.playerSize.map(s => { - return {w: s[0], h: s[1]}; - }).filter(obj => typeof obj.w === 'number' && typeof obj.h === 'number') + w: dmx.mediaTypes.video.playerSize[0][0] }; } else { obj.banner = { @@ -380,20 +377,10 @@ export function bindUserId(eids, value, source, atype) { } } -export function getApi({protocols}) { +export function getApi({api}) { let defaultValue = [2]; - let listProtocols = [ - {key: 'VPAID_1_0', value: 1}, - {key: 'VPAID_2_0', value: 2}, - {key: 'MRAID_1', value: 3}, - {key: 'ORMMA', value: 4}, - {key: 'MRAID_2', value: 5}, - {key: 'MRAID_3', value: 6}, - ]; - if (protocols) { - return listProtocols.filter(p => { - return protocols.indexOf(p.key) !== -1; - }).map(p => p.value) + if (api && Array.isArray(api) && api.length > 0) { + return api } else { return defaultValue; } @@ -409,35 +396,32 @@ export function getPlaybackmethod(playback) { export function getProtocols({protocols}) { let defaultValue = [2, 3, 5, 6, 7, 8]; - let listProtocols = [ - {key: 'VAST_1_0', value: 1}, - {key: 'VAST_2_0', value: 2}, - {key: 'VAST_3_0', value: 3}, - {key: 'VAST_1_0_WRAPPER', value: 4}, - {key: 'VAST_2_0_WRAPPER', value: 5}, - {key: 'VAST_3_0_WRAPPER', value: 6}, - {key: 'VAST_4_0', value: 7}, - {key: 'VAST_4_0_WRAPPER', value: 8} - ]; - if (protocols) { - return listProtocols.filter(p => { - return protocols.indexOf(p.key) !== -1 - }).map(p => p.value); + if (protocols && Array.isArray(protocols) && protocols.length > 0) { + return protocols; } else { return defaultValue; } } -export function cleanVast(str) { - const toberemove = /]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/ - const [img, url] = str.match(toberemove) - str = str.replace(toberemove, '') - if (img) { - if (url) { - const insrt = `` - str = str.replace('', `${insrt}`) +export function cleanVast(str, nurl) { + try { + const toberemove = /]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/ + const [img, url] = str.match(toberemove) + str = str.replace(toberemove, '') + if (img) { + if (url) { + const insrt = `` + str = str.replace('', `${insrt}`) + } + } + return str; + } catch (e) { + if(!nurl) { + return str } + const insrt = `` + str = str.replace('', `${insrt}`) + return str } - return str; } registerBidder(spec); diff --git a/test/spec/modules/districtmDmxBidAdapter_spec.js b/test/spec/modules/districtmDmxBidAdapter_spec.js index 90e6957fc2c..9dd565f14b1 100644 --- a/test/spec/modules/districtmDmxBidAdapter_spec.js +++ b/test/spec/modules/districtmDmxBidAdapter_spec.js @@ -145,7 +145,7 @@ const bidRequestVideo = [{ } }, 'mediaTypes': { video: {context: 'instream', // or 'outstream' - playerSize: [[640, 480]]} }, + playerSize: [[640, 480]]} }, 'adUnitCode': 'div-gpt-ad-12345678-1', 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', 'sizes': [ @@ -616,7 +616,7 @@ describe('DistrictM Adaptor', function () { }) describe('Test getApi function', function() { const data = { - protocols: ['VPAID_1_0'] + api: [1] } it('Will return 1 for vpaid version 1', function() { expect(getApi(data)[0]).to.be.equal(1) @@ -646,7 +646,7 @@ describe('DistrictM Adaptor', function () { describe('Test getProtocols function', function() { it('getProtocols will return 3', function() { - expect(getProtocols({protocols: ['VAST_3_0']})[0]).to.be.equal(3) + expect(getProtocols({protocols: [3]})[0]).to.be.equal(3) }) it('getProtocols will return 6', function() { expect(_.isEqual(getProtocols({}), [2, 3, 5, 6, 7, 8])).to.be.equal(true) @@ -811,3 +811,4 @@ describe('DistrictM Adaptor', function () { }); }); }); + From eb55e676a25ad9c6908d864229be5175380ead73 Mon Sep 17 00:00:00 2001 From: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Date: Mon, 2 Nov 2020 16:01:49 +0530 Subject: [PATCH 117/190] fix failing lint errors on circle ci (#5918) --- modules/districtmDMXBidAdapter.js | 30 +++---- .../modules/districtmDmxBidAdapter_spec.js | 79 ++++++++++--------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index 4d8154e429e..a7bcead5f0b 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -1,6 +1,6 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; +import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'districtmDMX'; @@ -28,12 +28,12 @@ export const spec = { response = response.body || {}; if (response.seatbid) { if (utils.isArray(response.seatbid)) { - const {seatbid} = response; + const { seatbid } = response; let winners = seatbid.reduce((bid, ads) => { - let ad = ads.bid.reduce(function(oBid, nBid) { + let ad = ads.bid.reduce(function (oBid, nBid) { if (oBid.price < nBid.price) { const bid = matchRequest(nBid.impid, bidRequest); - const {width, height} = defaultSize(bid); + const { width, height } = defaultSize(bid); nBid.cpm = parseFloat(nBid.price).toFixed(2); nBid.bidId = nBid.impid; nBid.requestId = nBid.impid; @@ -61,7 +61,7 @@ export const spec = { oBid.cpm = oBid.price; return oBid; } - }, {price: 0}); + }, { price: 0 }); if (ad.adm) { bid.push(ad) } @@ -98,7 +98,7 @@ export const spec = { let params = config.getConfig('dmx'); dmxRequest.user = params.user || {}; let site = params.site || {}; - dmxRequest.site = {...dmxRequest.site, ...site} + dmxRequest.site = { ...dmxRequest.site, ...site } } catch (e) { } @@ -144,7 +144,7 @@ export const spec = { dmxRequest.source = {}; dmxRequest.source.ext = {}; dmxRequest.source.ext.schain = schain || {} - } catch (e) {} + } catch (e) { } let tosendtags = bidRequest.map(dmx => { var obj = {}; obj.id = dmx.bidId; @@ -171,7 +171,7 @@ export const spec = { w: cleanSizes(dmx.sizes, 'w'), h: cleanSizes(dmx.sizes, 'h'), format: cleanSizes(dmx.sizes).map(s => { - return {w: s[0], h: s[1]}; + return { w: s[0], h: s[1] }; }).filter(obj => typeof obj.w === 'number' && typeof obj.h === 'number') }; } @@ -214,7 +214,7 @@ export const spec = { } } -export function getFloor (bid) { +export function getFloor(bid) { let floor = null; if (typeof bid.getFloor === 'function') { const floorInfo = bid.getFloor({ @@ -294,7 +294,7 @@ export function shuffle(sizes, list) { } results.push(current); results = list.filter(l => results.map(r => `${r[0]}x${r[1]}`).indexOf(`${l.size[0]}x${l.size[1]}`) !== -1); - results = results.sort(function(a, b) { + results = results.sort(function (a, b) { return b.s - a.s; }) return results.map(r => r.size); @@ -340,7 +340,7 @@ export function upto5(allimps, dmxRequest, bidderRequest, DMXURI) { * */ export function matchRequest(id, bidRequest) { - const {bids} = bidRequest.bidderRequest; + const { bids } = bidRequest.bidderRequest; const [returnValue] = bids.filter(bid => bid.bidId === id); return returnValue; } @@ -356,7 +356,7 @@ export function checkDeepArray(Arr) { } } export function defaultSize(thebidObj) { - const {sizes} = thebidObj; + const { sizes } = thebidObj; const returnObject = {}; returnObject.width = checkDeepArray(sizes)[0]; returnObject.height = checkDeepArray(sizes)[1]; @@ -377,7 +377,7 @@ export function bindUserId(eids, value, source, atype) { } } -export function getApi({api}) { +export function getApi({ api }) { let defaultValue = [2]; if (api && Array.isArray(api) && api.length > 0) { return api @@ -394,7 +394,7 @@ export function getPlaybackmethod(playback) { return [2] } -export function getProtocols({protocols}) { +export function getProtocols({ protocols }) { let defaultValue = [2, 3, 5, 6, 7, 8]; if (protocols && Array.isArray(protocols) && protocols.length > 0) { return protocols; @@ -416,7 +416,7 @@ export function cleanVast(str, nurl) { } return str; } catch (e) { - if(!nurl) { + if (!nurl) { return str } const insrt = `` diff --git a/test/spec/modules/districtmDmxBidAdapter_spec.js b/test/spec/modules/districtmDmxBidAdapter_spec.js index 9dd565f14b1..5d1f299dad5 100644 --- a/test/spec/modules/districtmDmxBidAdapter_spec.js +++ b/test/spec/modules/districtmDmxBidAdapter_spec.js @@ -1,6 +1,6 @@ -import {expect} from 'chai'; +import { expect } from 'chai'; import * as _ from 'lodash'; -import {spec, matchRequest, checkDeepArray, defaultSize, upto5, cleanSizes, shuffle, getApi, bindUserId, getPlaybackmethod, getProtocols, cleanVast} from '../../../modules/districtmDMXBidAdapter.js'; +import { spec, matchRequest, checkDeepArray, defaultSize, upto5, cleanSizes, shuffle, getApi, bindUserId, getPlaybackmethod, getProtocols, cleanVast } from '../../../modules/districtmDMXBidAdapter.js'; const sample_vast = ` @@ -144,8 +144,12 @@ const bidRequestVideo = [{ 'video/mp4'], } }, - 'mediaTypes': { video: {context: 'instream', // or 'outstream' - playerSize: [[640, 480]]} }, + 'mediaTypes': { + video: { + context: 'instream', // or 'outstream' + playerSize: [[640, 480]] + } + }, 'adUnitCode': 'div-gpt-ad-12345678-1', 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', 'sizes': [ @@ -602,53 +606,53 @@ const emptyResponseSeatBid = { body: { seatbid: [] } }; describe('DistrictM Adaptor', function () { const districtm = spec; - describe('verification of upto5', function() { - it('upto5 function should always break 12 imps into 3 request same for 15', function() { + describe('verification of upto5', function () { + it('upto5 function should always break 12 imps into 3 request same for 15', function () { expect(upto5([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], bidRequest, bidderRequest, 'https://google').length).to.be.equal(3) expect(upto5([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], bidRequest, bidderRequest, 'https://google').length).to.be.equal(3) }) }) - describe('test vast tag', function() { - it('img tag should not be present', function() { + describe('test vast tag', function () { + it('img tag should not be present', function () { expect(cleanVast(sample_vast).indexOf('img') !== -1).to.be.equal(false) }) }) - describe('Test getApi function', function() { + describe('Test getApi function', function () { const data = { api: [1] } - it('Will return 1 for vpaid version 1', function() { + it('Will return 1 for vpaid version 1', function () { expect(getApi(data)[0]).to.be.equal(1) }) - it('Will return 2 for vpaid default', function() { + it('Will return 2 for vpaid default', function () { expect(getApi({})[0]).to.be.equal(2) }) }) - describe('Test cleanSizes function', function() { - it('sequence will be respected', function() { + describe('Test cleanSizes function', function () { + it('sequence will be respected', function () { expect(cleanSizes(bidderRequest.bids[0].sizes).toString()).to.be.equal('300,250,300,600') }) - it('sequence will be respected', function() { + it('sequence will be respected', function () { expect(cleanSizes([[728, 90], [970, 90], [300, 600], [320, 50]]).toString()).to.be.equal('728,90,320,50,300,600,970,90') }) }) - describe('Test getPlaybackmethod function', function() { - it('getPlaybackmethod will return 2', function() { + describe('Test getPlaybackmethod function', function () { + it('getPlaybackmethod will return 2', function () { expect(getPlaybackmethod([])[0]).to.be.equal(2) }) - it('getPlaybackmethod will return 6', function() { + it('getPlaybackmethod will return 6', function () { expect(getPlaybackmethod(['viewport_sound_off'])[0]).to.be.equal(6) }) }) - describe('Test getProtocols function', function() { - it('getProtocols will return 3', function() { - expect(getProtocols({protocols: [3]})[0]).to.be.equal(3) + describe('Test getProtocols function', function () { + it('getProtocols will return 3', function () { + expect(getProtocols({ protocols: [3] })[0]).to.be.equal(3) }) - it('getProtocols will return 6', function() { + it('getProtocols will return 6', function () { expect(_.isEqual(getProtocols({}), [2, 3, 5, 6, 7, 8])).to.be.equal(true) }) }) @@ -687,7 +691,7 @@ describe('DistrictM Adaptor', function () { memberid: 10003, }; it(`function should return true`, function () { - expect(districtm.isBidRequestValid({params})).to.be.equal(true); + expect(districtm.isBidRequestValid({ params })).to.be.equal(true); }); it(`function should return false`, function () { expect(districtm.isBidRequestValid({ params: { memberid: 12345 } })).to.be.equal(false); @@ -716,19 +720,19 @@ describe('DistrictM Adaptor', function () { it(`the function should return an array`, function () { expect(buildRequestResults).to.be.an('object'); }); - it(`contain gdpr consent & ccpa`, function() { + it(`contain gdpr consent & ccpa`, function () { const bidr = JSON.parse(buildRequestResults.data) expect(bidr.regs.ext.gdpr).to.be.equal(1); expect(bidr.regs.ext.us_privacy).to.be.equal('1NY'); expect(bidr.user.ext.consent).to.be.an('string'); }); - it(`test contain COPPA`, function() { + it(`test contain COPPA`, function () { const bidr = JSON.parse(buildRequestResults.data) bidr.regs = bidr.regs || {}; bidr.regs.coppa = 1; expect(bidr.regs.coppa).to.be.equal(1) }) - it(`test should not contain COPPA`, function() { + it(`test should not contain COPPA`, function () { const bidr = JSON.parse(buildRequestResultsNoCoppa.data) expect(bidr.regs.coppa).to.be.equal(0) }) @@ -737,17 +741,17 @@ describe('DistrictM Adaptor', function () { }); }); - describe('bidRequest Video testing', function() { + describe('bidRequest Video testing', function () { const request = districtm.buildRequests(bidRequestVideo, bidRequestVideo); const data = JSON.parse(request.data) expect(data instanceof Object).to.be.equal(true) }) describe(`interpretResponse test usage`, function () { - const responseResults = districtm.interpretResponse(responses, {bidderRequest}); - const emptyResponseResults = districtm.interpretResponse(emptyResponse, {bidderRequest}); - const emptyResponseResultsNegation = districtm.interpretResponse(responsesNegative, {bidderRequest}); - const emptyResponseResultsEmptySeat = districtm.interpretResponse(emptyResponseSeatBid, {bidderRequest}); + const responseResults = districtm.interpretResponse(responses, { bidderRequest }); + const emptyResponseResults = districtm.interpretResponse(emptyResponse, { bidderRequest }); + const emptyResponseResultsNegation = districtm.interpretResponse(responsesNegative, { bidderRequest }); + const emptyResponseResultsEmptySeat = districtm.interpretResponse(emptyResponseSeatBid, { bidderRequest }); it(`the function should return an array`, function () { expect(responseResults).to.be.an('array'); }); @@ -767,10 +771,10 @@ describe('DistrictM Adaptor', function () { }); describe(`check validation for id sync gdpr ccpa`, () => { - let allin = spec.getUserSyncs({iframeEnabled: true}, {}, bidderRequest.gdprConsent, bidderRequest.uspConsent)[0] - let noCCPA = spec.getUserSyncs({iframeEnabled: true}, {}, bidderRequest.gdprConsent, null)[0] - let noGDPR = spec.getUserSyncs({iframeEnabled: true}, {}, null, bidderRequest.uspConsent)[0] - let nothing = spec.getUserSyncs({iframeEnabled: true}, {}, null, null)[0] + let allin = spec.getUserSyncs({ iframeEnabled: true }, {}, bidderRequest.gdprConsent, bidderRequest.uspConsent)[0] + let noCCPA = spec.getUserSyncs({ iframeEnabled: true }, {}, bidderRequest.gdprConsent, null)[0] + let noGDPR = spec.getUserSyncs({ iframeEnabled: true }, {}, null, bidderRequest.uspConsent)[0] + let nothing = spec.getUserSyncs({ iframeEnabled: true }, {}, null, null)[0] /* @@ -793,10 +797,10 @@ describe('DistrictM Adaptor', function () { }) describe(`Helper function testing`, function () { - const bid = matchRequest('29a28a1bbc8a8d', {bidderRequest}); - const {width, height} = defaultSize(bid); + const bid = matchRequest('29a28a1bbc8a8d', { bidderRequest }); + const { width, height } = defaultSize(bid); it(`test matchRequest`, function () { - expect(matchRequest('29a28a1bbc8a8d', {bidderRequest})).to.be.an('object'); + expect(matchRequest('29a28a1bbc8a8d', { bidderRequest })).to.be.an('object'); }); it(`test checkDeepArray`, function () { expect(_.isEqual(checkDeepArray([728, 90]), [728, 90])).to.be.equal(true); @@ -811,4 +815,3 @@ describe('DistrictM Adaptor', function () { }); }); }); - From bb00c5c6f724ee21e9584b0dbcf572ad752cfadb Mon Sep 17 00:00:00 2001 From: pm-shashank-jain <40654031+pm-shashank-jain@users.noreply.github.com> Date: Mon, 2 Nov 2020 16:27:20 +0530 Subject: [PATCH 118/190] sspId for pubmatic only (#418) --- modules/pubmaticServerBidAdapter.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/pubmaticServerBidAdapter.js b/modules/pubmaticServerBidAdapter.js index b5cdad75d6d..5e930fbe5bc 100644 --- a/modules/pubmaticServerBidAdapter.js +++ b/modules/pubmaticServerBidAdapter.js @@ -641,6 +641,9 @@ export const spec = { if (bid.ext.prebid && bid.ext.prebid.targeting) { newBid.adserverTargeting = bid.ext.prebid.targeting } + if (newBid && newBid.originalBidder == "pubmatic") { + newBid.sspID = bid.id || ''; + } break; default: requestData.imp.forEach(function(impObj) { From 1a76a461055da8c6f9cf4cc29ffb0006687e394c Mon Sep 17 00:00:00 2001 From: ix-certification Date: Mon, 2 Nov 2020 06:54:00 -0500 Subject: [PATCH 119/190] IX missing sizes testing and diagnosis (#5856) * Added support for Liveramp userId submodule * Fixing URL length for large requests * adding telemetry to missing sizes feature * adding markdown file with detectMissingSizes * example value update Co-authored-by: IX-Prebid-Support --- modules/ixBidAdapter.js | 186 ++++++++++++++++++++--- modules/ixBidAdapter.md | 20 +++ test/spec/modules/ixBidAdapter_spec.js | 199 +++++++++++++++++++++++-- 3 files changed, 374 insertions(+), 31 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 77d4220e59a..cb37e4735c5 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -235,9 +235,9 @@ function addUserEids(userEids, seenIdPartners, id, source, ixlPartnerName, rtiPa * * @param {array} validBidRequests A list of valid bid request config objects. * @param {object} bidderRequest An object containing other info like gdprConsent. - * @param {array} impressions List of impression objects describing the bids. + * @param {object} impressions An object containing a list of impression objects describing the bids for each transactionId * @param {array} version Endpoint version denoting banner or video. - * @return {object} Info describing the request to the server. + * @return {array} List of objects describing the request to the server. * */ function buildRequest(validBidRequests, bidderRequest, impressions, version) { @@ -280,11 +280,10 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Since bidderRequestId are the same for different bid request, just use the first one. r.id = validBidRequests[0].bidderRequestId; - r.imp = impressions; - r.site = {}; r.ext = {}; r.ext.source = 'prebid'; + r.ext.ixdiag = {}; // if an schain is provided, send it along if (validBidRequests[0].schain) { @@ -358,30 +357,140 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (typeof otherIxConfig.timeout === 'number') { payload.t = otherIxConfig.timeout; } + + if (typeof otherIxConfig.detectMissingSizes === 'boolean') { + r.ext.ixdiag.dms = otherIxConfig.detectMissingSizes; + } else { + r.ext.ixdiag.dms = true; + } } // Use the siteId in the first bid request as the main siteId. payload.s = validBidRequests[0].params.siteId; payload.v = version; - payload.r = JSON.stringify(r); payload.ac = 'j'; payload.sd = 1; if (version === VIDEO_ENDPOINT_VERSION) { payload.nf = 1; } - return { + const requests = []; + + const request = { method: 'GET', url: baseUrl, data: payload }; + + const BASE_REQ_SIZE = new Blob([`${request.url}${utils.parseQueryStringParameters({...request.data, r: JSON.stringify(r)})}`]).size; + let currReqSize = BASE_REQ_SIZE; + + const MAX_REQ_SIZE = 8000; + const MAX_REQ_LIMIT = 4; + let sn = 0; + let msi = 0; + let msd = 0; + r.ext.ixdiag.msd = 0; + r.ext.ixdiag.msi = 0; + r.imp = []; + let i = 0; + const transactionIds = Object.keys(impressions); + let currMissingImps = []; + + while (i < transactionIds.length && requests.length < MAX_REQ_LIMIT) { + if (impressions[transactionIds[i]].hasOwnProperty('missingCount')) { + msd = impressions[transactionIds[i]].missingCount; + } + + trimImpressions(impressions[transactionIds[i]], MAX_REQ_SIZE - BASE_REQ_SIZE); + + if (impressions[transactionIds[i]].hasOwnProperty('missingImps')) { + msi = impressions[transactionIds[i]].missingImps.length; + } + + let currImpsSize = new Blob([encodeURIComponent(JSON.stringify(impressions[transactionIds[i]]))]).size; + currReqSize += currImpsSize; + if (currReqSize < MAX_REQ_SIZE) { + // pushing ix configured sizes first + r.imp.push(...impressions[transactionIds[i]].ixImps); + // update msd msi + r.ext.ixdiag.msd += msd; + r.ext.ixdiag.msi += msi; + + if (impressions[transactionIds[i]].hasOwnProperty('missingImps')) { + currMissingImps.push(...impressions[transactionIds[i]].missingImps); + } + + i++; + } else { + // pushing missing sizes after configured ones + const clonedPayload = utils.deepClone(payload); + + r.imp.push(...currMissingImps); + r.ext.ixdiag.sn = sn; + clonedPayload.sn = sn; + sn++; + clonedPayload.r = JSON.stringify(r); + + requests.push({ + method: 'GET', + url: baseUrl, + data: clonedPayload + }); + currMissingImps = []; + currReqSize = BASE_REQ_SIZE; + r.imp = []; + msd = 0; + msi = 0; + r.ext.ixdiag.msd = 0; + r.ext.ixdiag.msi = 0; + } + } + + if (currReqSize > BASE_REQ_SIZE && currReqSize < MAX_REQ_SIZE && requests.length < MAX_REQ_LIMIT) { + const clonedPayload = utils.deepClone(payload); + r.imp.push(...currMissingImps); + + if (requests.length > 0) { + r.ext.ixdiag.sn = sn; + clonedPayload.sn = sn; + } + clonedPayload.r = JSON.stringify(r); + + requests.push({ + method: 'GET', + url: baseUrl, + data: clonedPayload + }); + } + + return requests; } +/** + * + * @param {Object} impressions containing ixImps and possibly missingImps + * + */ +function trimImpressions(impressions, maxSize) { + let currSize = new Blob([encodeURIComponent(JSON.stringify(impressions))]).size; + if (currSize < maxSize) { + return; + } + while (currSize > maxSize) { + if (impressions.hasOwnProperty('missingImps') && impressions.missingImps.length > 0) { + impressions.missingImps.pop(); + } else if (impressions.hasOwnProperty('ixImps') && impressions.ixImps.length > 0) { + impressions.ixImps.pop(); + } + currSize = new Blob([encodeURIComponent(JSON.stringify(impressions))]).size; + } +} /** * * @param {array} bannerSizeList list of banner sizes * @param {array} bannerSize the size to be removed - * @return {boolean} true if succesfully removed, false if not found + * @return {boolean} true if successfully removed, false if not found */ function removeFromSizes(bannerSizeList, bannerSize) { @@ -496,19 +605,32 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { let reqs = []; - let bannerImps = []; - let videoImps = []; + let bannerImps = {}; + let videoImps = {}; let validBidRequest = null; // To capture the missing sizes i.e not configured for ix let missingBannerSizes = {}; + const DEFAULT_IX_CONFIG = { + detectMissingSizes: true, + }; + + const ixConfig = {...DEFAULT_IX_CONFIG, ...config.getConfig('ix')}; + for (let i = 0; i < validBidRequests.length; i++) { validBidRequest = validBidRequests[i]; if (validBidRequest.mediaType === VIDEO || utils.deepAccess(validBidRequest, 'mediaTypes.video')) { if (validBidRequest.mediaType === VIDEO || includesSize(validBidRequest.mediaTypes.video.playerSize, validBidRequest.params.size)) { - videoImps.push(bidToVideoImp(validBidRequest)); + if (!videoImps.hasOwnProperty(validBidRequest.transactionId)) { + videoImps[validBidRequest.transactionId] = {}; + } + if (!videoImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { + videoImps[validBidRequest.transactionId].ixImps = []; + } + + videoImps[validBidRequest.transactionId].ixImps.push(bidToVideoImp(validBidRequest)); } else { utils.logError('Bid size is not included in video playerSize') } @@ -516,27 +638,47 @@ export const spec = { if (validBidRequest.mediaType === BANNER || utils.deepAccess(validBidRequest, 'mediaTypes.banner') || (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { let imp = bidToBannerImp(validBidRequest); - bannerImps.push(imp); - updateMissingSizes(validBidRequest, missingBannerSizes, imp); + + if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { + bannerImps[validBidRequest.transactionId] = {}; + } + if (!bannerImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { + bannerImps[validBidRequest.transactionId].ixImps = [] + } + bannerImps[validBidRequest.transactionId].ixImps.push(imp); + if (ixConfig.hasOwnProperty('detectMissingSizes') && ixConfig.detectMissingSizes) { + updateMissingSizes(validBidRequest, missingBannerSizes, imp); + } } } - // Finding the missing banner sizes ,and making impressions for them - for (var transactionID in missingBannerSizes) { - if (missingBannerSizes.hasOwnProperty(transactionID)) { - let missingSizes = missingBannerSizes[transactionID].missingSizes; + + // Finding the missing banner sizes, and making impressions for them + for (var transactionId in missingBannerSizes) { + if (missingBannerSizes.hasOwnProperty(transactionId)) { + let missingSizes = missingBannerSizes[transactionId].missingSizes; + + if (!bannerImps.hasOwnProperty(transactionId)) { + bannerImps[transactionId] = {}; + } + if (!bannerImps[transactionId].hasOwnProperty('missingImps')) { + bannerImps[transactionId].missingImps = []; + bannerImps[transactionId].missingCount = 0; + } + + let origImp = missingBannerSizes[transactionId].impression; for (let i = 0; i < missingSizes.length; i++) { - let origImp = missingBannerSizes[transactionID].impression; let newImp = createMissingBannerImp(origImp, missingSizes[i]); - bannerImps.push(newImp); + bannerImps[transactionId].missingImps.push(newImp); + bannerImps[transactionId].missingCount++; } } } - if (bannerImps.length > 0) { - reqs.push(buildRequest(validBidRequests, bidderRequest, bannerImps, BANNER_ENDPOINT_VERSION)); + if (Object.keys(bannerImps).length > 0) { + reqs.push(...buildRequest(validBidRequests, bidderRequest, bannerImps, BANNER_ENDPOINT_VERSION)); } - if (videoImps.length > 0) { - reqs.push(buildRequest(validBidRequests, bidderRequest, videoImps, VIDEO_ENDPOINT_VERSION)); + if (Object.keys(videoImps).length > 0) { + reqs.push(...buildRequest(validBidRequests, bidderRequest, videoImps, VIDEO_ENDPOINT_VERSION)); } return reqs; diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index b5cb0d9d2c1..c2e308870db 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -288,6 +288,26 @@ pbjs.setConfig({ } }); ``` +#### The **detectMissingSizes** feature +With a recent update, the IX bid adapter bids on all banner sizes available in an ad unit, if IX is configured for at least one banner size in that ad unit. This default behavior if not required, can be turned off by using the `detectMissingSizes` flag. +``` +pbjs.setConfig({ + ix: { + detectMissingSizes: false + } + }); +``` +OR +``` +pbjs.setBidderConfig({ + bidders: ["ix"], + config: { + ix: { + detectMissingSizes: false + } + } + }); +``` ### 2. Include `ixBidAdapter` in your build process diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 63b04077f4e..ad4f90b0397 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -4,7 +4,7 @@ import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/ixBidAdapter.js'; -describe('IndexexchangeAdapter', function () { +describe.only('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; const VIDEO_ENDPOINT_VERSION = 8.1; const BANNER_ENDPOINT_VERSION = 7.2; @@ -18,7 +18,6 @@ describe('IndexexchangeAdapter', function () { 'sid': '00001', 'hp': 1 }, - { 'asi': 'indirectseller-2.com', 'sid': '00002', @@ -26,7 +25,74 @@ describe('IndexexchangeAdapter', function () { } ] }; - + var div_many_sizes = [ + [300, 250], + [600, 410], + [336, 280], + [400, 300], + [320, 50], + [360, 360], + [250, 250], + [320, 250], + [400, 250], + [387, 359], + [300, 50], + [372, 250], + [320, 320], + [412, 412], + [327, 272], + [312, 260], + [384, 320], + [335, 250], + [366, 305], + [374, 250], + [375, 375], + [272, 391], + [364, 303], + [414, 414], + [366, 375], + [272, 360], + [364, 373], + [366, 359], + [320, 100], + [360, 250], + [468, 60], + [480, 300], + [600, 400], + [600, 300], + [33, 28], + [40, 30], + [32, 5], + [36, 36], + [25, 25], + [320, 25], + [400, 25], + [387, 35], + [300, 5], + [372, 20], + [320, 32], + [412, 41], + [327, 27], + [312, 26], + [384, 32], + [335, 25], + [366, 30], + [374, 25], + [375, 37], + [272, 31], + [364, 303], + [414, 41], + [366, 35], + [272, 60], + [364, 73], + [366, 59], + [320, 10], + [360, 25], + [468, 6], + [480, 30], + [600, 40], + [600, 30] + ]; const DEFAULT_BANNER_VALID_BID = [ { bidder: 'ix', @@ -587,7 +653,6 @@ describe('IndexexchangeAdapter', function () { it('IX adapter reads LiveRamp IDL envelope from Prebid and adds it to Video', function () { const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); - const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); @@ -972,6 +1037,97 @@ describe('IndexexchangeAdapter', function () { expect(videoImp.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); }); + it('single request under 8k size limit for large ad unit', function () { + const options = {}; + const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid1.mediaTypes.banner.sizes = div_many_sizes; + const requests = spec.buildRequests([bid1], options); + + const reqSize = new Blob([`${requests[0].url}?${utils.parseQueryStringParameters(requests[0].data)}`]).size; + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(1); + expect(reqSize).to.be.lessThan(8000); + }); + + it('2 requests due to 2 ad units, one larger than url size', function () { + const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid1.mediaTypes.banner.sizes = div_many_sizes; + bid1.params.siteId = '124'; + bid1.adUnitCode = 'div-gpt-1' + bid1.transactionId = '152e36d1-1241-4242-t35e-y1dv34d12315'; + bid1.bidId = '2f6g5s5e'; + + const requests = spec.buildRequests([bid1, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(2); + expect(requests[0].data.sn).to.be.equal(0); + expect(requests[1].data.sn).to.be.equal(1); + }); + + it('6 ad units should generate only 4 requests', function () { + const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid1.mediaTypes.banner.sizes = div_many_sizes; + bid1.params.siteId = '121'; + bid1.adUnitCode = 'div-gpt-1' + bid1.transactionId = 'tr1'; + bid1.bidId = '2f6g5s5e'; + + const bid2 = utils.deepClone(bid1); + bid2.transactionId = 'tr2'; + + const bid3 = utils.deepClone(bid1); + bid3.transactionId = 'tr3'; + + const bid4 = utils.deepClone(bid1); + bid4.transactionId = 'tr4'; + + const bid5 = utils.deepClone(bid1); + bid5.transactionId = 'tr5'; + + const bid6 = utils.deepClone(bid1); + bid6.transactionId = 'tr6'; + + const requests = spec.buildRequests([bid1, bid2, bid3, bid4, bid5, bid6], DEFAULT_OPTION); + + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(4); + + // check if seq number increases + for (var i = 0; i < requests.length; i++) { + const reqSize = new Blob([`${requests[i].url}?${utils.parseQueryStringParameters(requests[i].data)}`]).size; + expect(reqSize).to.be.lessThan(8000); + let payload = JSON.parse(requests[i].data.r); + if (requests.length > 1) { + expect(requests[i].data.sn).to.equal(i); + } + expect(payload.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); + } + }); + + it('multiple ad units in one request', function () { + const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid1.mediaTypes.banner.sizes = [[300, 250], [300, 600], [100, 200]]; + bid1.params.siteId = '121'; + bid1.adUnitCode = 'div-gpt-1' + bid1.transactionId = 'tr1'; + bid1.bidId = '2f6g5s5e'; + + const bid2 = utils.deepClone(bid1); + bid2.transactionId = 'tr2'; + bid2.mediaTypes.banner.sizes = [[220, 221], [222, 223], [300, 250]]; + const bid3 = utils.deepClone(bid1); + bid3.transactionId = 'tr3'; + bid3.mediaTypes.banner.sizes = [[330, 331], [332, 333], [300, 250]]; + + const requests = spec.buildRequests([bid1, bid2, bid3], DEFAULT_OPTION); + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(1); + + const impressions = JSON.parse(requests[0].data.r).imp; + expect(impressions).to.be.an('array'); + expect(impressions).to.have.lengthOf(9); + }); + it('request should contain the extra banner ad sizes that IX is not configured for using the first site id in the ad unit', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.sizes.push([336, 280], [970, 90]); @@ -1017,23 +1173,26 @@ describe('IndexexchangeAdapter', function () { const impressions = JSON.parse(request.data.r).imp; expect(impressions).to.be.an('array'); expect(impressions).to.have.lengthOf(4); - - expect(impressions[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) - expect(impressions[1].ext.siteID).to.equal(bid.params.siteId) - expect(impressions[2].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()) - expect(impressions[3].ext.siteID).to.equal(bid.params.siteId) + expect(impressions[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impressions[1].ext.siteID).to.equal(bid.params.siteId); + expect(impressions[2].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impressions[3].ext.siteID).to.equal(bid.params.siteId); expect(impressions[0].banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); expect(impressions[0].banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); + expect(impressions[1].banner.w).to.equal(bid.params.size[0]); expect(impressions[1].banner.h).to.equal(bid.params.size[1]); + expect(impressions[2].banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][0]); expect(impressions[2].banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][1]); + expect(impressions[3].banner.w).to.equal(bid.mediaTypes.banner.sizes[1][0]); expect(impressions[3].banner.h).to.equal(bid.mediaTypes.banner.sizes[1][1]); expect(impressions[0].ext.sid).to.equal(`${DEFAULT_BANNER_VALID_BID[0].params.size[0].toString()}x${DEFAULT_BANNER_VALID_BID[0].params.size[1].toString()}`); expect(impressions[1].ext.sid).to.equal(`${bid.params.size[0].toString()}x${bid.params.size[1].toString()}`); + expect(impressions[2].ext.sid).to.equal(`${DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][0].toString()}x${DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[1][1].toString()}`); expect(impressions[3].ext.sid).to.equal(`${bid.mediaTypes.banner.sizes[1][0].toString()}x${bid.mediaTypes.banner.sizes[1][1].toString()}`); }); @@ -1045,6 +1204,28 @@ describe('IndexexchangeAdapter', function () { expect(impressions).to.be.an('array'); expect(impressions).to.have.lengthOf(1); }); + + describe('detect missing sizes', function () { + beforeEach(function () { + config.setConfig({ + ix: { + detectMissingSizes: false + } + }); + }) + + it('request should not contain missing sizes if detectMissingSizes = false', function () { + const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid1.mediaTypes.banner.sizes = div_many_sizes; + + const requests = spec.buildRequests([bid1, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + + const impressions = JSON.parse(requests[0].data.r).imp; + + expect(impressions).to.be.an('array'); + expect(impressions).to.have.lengthOf(2); + }); + }); }); describe('buildRequestVideo', function () { From ae1b4d74ad9e6e2cbfe6035de329d2b6d89ceadd Mon Sep 17 00:00:00 2001 From: thuyhq <61451682+thuyhq@users.noreply.github.com> Date: Mon, 2 Nov 2020 20:51:43 +0700 Subject: [PATCH 120/190] Add apacdex bid adapter & Merge valueimpression, quantumdex to apacdex (#5888) --- ...mdexBidAdapter.js => apacdexBidAdapter.js} | 12 ++- ...mdexBidAdapter.md => apacdexBidAdapter.md} | 16 ++-- ...pter_spec.js => apacdexBidAdapter_spec.js} | 74 +++++++++---------- 3 files changed, 53 insertions(+), 49 deletions(-) rename modules/{quantumdexBidAdapter.js => apacdexBidAdapter.js} (94%) rename modules/{quantumdexBidAdapter.md => apacdexBidAdapter.md} (56%) rename test/spec/modules/{quantumdexBidAdapter_spec.js => apacdexBidAdapter_spec.js} (92%) diff --git a/modules/quantumdexBidAdapter.js b/modules/apacdexBidAdapter.js similarity index 94% rename from modules/quantumdexBidAdapter.js rename to modules/apacdexBidAdapter.js index 738b6165f79..2582e4788c1 100644 --- a/modules/quantumdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,7 +1,11 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'quantumdex'; +const BIDDER_CODE = 'apacdex'; const CONFIG = { + 'apacdex': { + 'ENDPOINT': 'https://useast.quantumdex.io/auction/apacdex', + 'USERSYNC': 'https://sync.quantumdex.io/usersync/apacdex' + }, 'quantumdex': { 'ENDPOINT': 'https://useast.quantumdex.io/auction/quantumdex', 'USERSYNC': 'https://sync.quantumdex.io/usersync/quantumdex' @@ -12,14 +16,14 @@ const CONFIG = { } }; -var bidderConfig = CONFIG['quantumdex']; +var bidderConfig = CONFIG[BIDDER_CODE]; var bySlotTargetKey = {}; var bySlotSizesCount = {} export const spec = { code: BIDDER_CODE, supportedMediaTypes: ['banner', 'video'], - aliases: ['valueimpression'], + aliases: ['quantumdex', 'valueimpression'], isBidRequestValid: function (bid) { if (!bid.params) { return false; @@ -30,7 +34,7 @@ export const spec = { if (!utils.deepAccess(bid, 'mediaTypes.banner') && !utils.deepAccess(bid, 'mediaTypes.video')) { return false; } - if (utils.deepAccess(bid, 'mediaTypes.banner')) { // Quantumdex does not support multi type bids, favor banner over video + if (utils.deepAccess(bid, 'mediaTypes.banner')) { // Not support multi type bids, favor banner over video if (!utils.deepAccess(bid, 'mediaTypes.banner.sizes')) { // sizes at the banner is required. return false; diff --git a/modules/quantumdexBidAdapter.md b/modules/apacdexBidAdapter.md similarity index 56% rename from modules/quantumdexBidAdapter.md rename to modules/apacdexBidAdapter.md index 8c35ea8cb05..b88190cda94 100644 --- a/modules/quantumdexBidAdapter.md +++ b/modules/apacdexBidAdapter.md @@ -1,15 +1,15 @@ # Overview ``` -Module Name: Quantum Digital Exchange Bidder Adapter +Module Name: APAC Digital Exchange Bidder Adapter Module Type: Bidder Adapter -Maintainer: ken@quantumdex.io +Maintainer: ken@apacdex.com ``` # Description -Connects to Quantum Digital Exchange for bids. -Quantumdex bid adapter supports Banner and Video (Instream and Outstream) ads. +Connects to APAC Digital Exchange for bids. +Apacdex bid adapter supports Banner and Video (Instream and Outstream) ads. # Test Parameters ``` @@ -23,9 +23,9 @@ var adUnits = [ }, bids: [ { - bidder: 'quantumdex', + bidder: 'apacdex', params: { - siteId: 'quantumdex-site-id', // siteId provided by Quantumdex + siteId: 'apacdex1234', // siteId provided by Apacdex } } ] @@ -46,9 +46,9 @@ var videoAdUnit = { }, bids: [ { - bidder: 'quantumdex', + bidder: 'apacdex', params: { - siteId: 'quantumdex-site-id', // siteId provided by Quantumdex + siteId: 'apacdex1234', // siteId provided by Apacdex } } ] diff --git a/test/spec/modules/quantumdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js similarity index 92% rename from test/spec/modules/quantumdexBidAdapter_spec.js rename to test/spec/modules/apacdexBidAdapter_spec.js index d1817493b36..da9a050a8de 100644 --- a/test/spec/modules/quantumdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -1,14 +1,14 @@ import { expect } from 'chai' -import { spec } from 'modules/quantumdexBidAdapter.js' +import { spec } from 'modules/apacdexBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' import { userSync } from '../../../src/userSync.js'; -describe('QuantumdexBidAdapter', function () { +describe('ApacdexBidAdapter', function () { const adapter = newBidder(spec) describe('.code', function () { - it('should return a bidder code of quantumdex', function () { - expect(spec.code).to.equal('quantumdex') + it('should return a bidder code of apacdex', function () { + expect(spec.code).to.equal('apacdex') }) }) @@ -21,7 +21,7 @@ describe('QuantumdexBidAdapter', function () { describe('.isBidRequestValid', function () { it('should return false if there are no params', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', 'mediaTypes': { banner: { @@ -37,7 +37,7 @@ describe('QuantumdexBidAdapter', function () { it('should return false if there is no siteId param', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { site_id: '1a2b3c4d5e6f1a2b3c4d', @@ -56,7 +56,7 @@ describe('QuantumdexBidAdapter', function () { it('should return false if there is no mediaTypes', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d' @@ -72,7 +72,7 @@ describe('QuantumdexBidAdapter', function () { it('should return true if the bid is valid', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d' @@ -92,7 +92,7 @@ describe('QuantumdexBidAdapter', function () { describe('banner', () => { it('should return false if there are no banner sizes', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d' @@ -111,7 +111,7 @@ describe('QuantumdexBidAdapter', function () { it('should return true if there is banner sizes', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d' @@ -132,7 +132,7 @@ describe('QuantumdexBidAdapter', function () { describe('video', () => { it('should return false if there is no playerSize defined in the video mediaType', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d', @@ -152,7 +152,7 @@ describe('QuantumdexBidAdapter', function () { it('should return true if there is playerSize defined on the video mediaType', () => { const bid = { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'adUnitCode': 'adunit-code', params: { siteId: '1a2b3c4d5e6f1a2b3c4d', @@ -196,7 +196,7 @@ describe('QuantumdexBidAdapter', function () { }, ] }, - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'siteId': '1a2b3c4d5e6f1a2b3c4d', }, @@ -206,7 +206,7 @@ describe('QuantumdexBidAdapter', function () { 'bidId': '30b31c1838de1f', }, { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'ad_unit': '/7780971/sparks_prebid_LB', 'sizes': [[300, 250], [300, 600]], @@ -235,14 +235,14 @@ describe('QuantumdexBidAdapter', function () { it('should return a properly formatted request', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') expect(bidRequests.bidderRequests).to.eql(bidRequest); }) it('should return a properly formatted request with GDPR applies set to true', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') expect(bidRequests.data.gdpr.gdprApplies).to.equal(true) expect(bidRequests.data.gdpr.consentString).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==') @@ -251,7 +251,7 @@ describe('QuantumdexBidAdapter', function () { it('should return a properly formatted request with GDPR applies set to false', function () { bidderRequests.gdprConsent.gdprApplies = false; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') expect(bidRequests.data.gdpr.gdprApplies).to.equal(false) expect(bidRequests.data.gdpr.consentString).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==') @@ -271,7 +271,7 @@ describe('QuantumdexBidAdapter', function () { } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') expect(bidRequests.data.gdpr.gdprApplies).to.equal(false) expect(bidRequests.data.gdpr).to.not.include.keys('consentString') @@ -291,7 +291,7 @@ describe('QuantumdexBidAdapter', function () { } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/quantumdex') + expect(bidRequests.url).to.equal('https://useast.quantumdex.io/auction/apacdex') expect(bidRequests.method).to.equal('POST') expect(bidRequests.data.gdpr.gdprApplies).to.equal(true) expect(bidRequests.data.gdpr).to.not.include.keys('consentString') @@ -309,7 +309,7 @@ describe('QuantumdexBidAdapter', function () { describe('.interpretResponse', function () { const bidRequests = { 'method': 'POST', - 'url': 'https://useast.quantumdex.io/auction/quantumdex', + 'url': 'https://useast.quantumdex.io/auction/apacdex', 'withCredentials': true, 'data': { 'device': { @@ -328,7 +328,7 @@ describe('QuantumdexBidAdapter', function () { }, 'bidderRequests': [ { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'siteId': '343' }, @@ -363,7 +363,7 @@ describe('QuantumdexBidAdapter', function () { } }, { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'siteId': '343' }, @@ -398,7 +398,7 @@ describe('QuantumdexBidAdapter', function () { } }, { - 'bidder': 'quantumdex', + 'bidder': 'apacdex', 'params': { 'siteId': '343' }, @@ -464,12 +464,12 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1.07, 'width': 160, 'height': 600, - 'ad': `
Quantumdex AD
`, + 'ad': `
Apacdex AD
`, 'ttl': 500, 'creativeId': '1234abcd', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', + 'dealId': 'apacdex', 'mediaType': 'banner' }, { @@ -477,12 +477,12 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1, 'width': 300, 'height': 250, - 'ad': `
Quantumdex AD
`, + 'ad': `
Apacdex AD
`, 'ttl': 500, 'creativeId': '1234abcd', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', + 'dealId': 'apacdex', 'mediaType': 'banner' }, { @@ -490,12 +490,12 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1.25, 'width': 300, 'height': 250, - 'vastXml': 'quantumdex', + 'vastXml': 'apacdex', 'ttl': 500, 'creativeId': '30292e432662bd5f86d90774b944b038', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', + 'dealId': 'apacdex', 'mediaType': 'video' } ], @@ -512,12 +512,12 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1.07, 'width': 160, 'height': 600, - 'ad': `
Quantumdex AD
`, + 'ad': `
Apacdex AD
`, 'ttl': 500, 'creativeId': '1234abcd', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', + 'dealId': 'apacdex', 'mediaType': 'banner' }, { @@ -525,12 +525,12 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1, 'width': 300, 'height': 250, - 'ad': `
Quantumdex AD
`, + 'ad': `
Apacdex AD
`, 'ttl': 500, 'creativeId': '1234abcd', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', + 'dealId': 'apacdex', 'mediaType': 'banner' }, { @@ -538,12 +538,12 @@ describe('QuantumdexBidAdapter', function () { 'cpm': 1.25, 'width': 300, 'height': 250, - 'vastXml': 'quantumdex', + 'vastXml': 'apacdex', 'ttl': 500, 'creativeId': '30292e432662bd5f86d90774b944b038', 'netRevenue': true, 'currency': 'USD', - 'dealId': 'quantumdex', + 'dealId': 'apacdex', 'mediaType': 'video' } ]; @@ -561,10 +561,10 @@ describe('QuantumdexBidAdapter', function () { expect(resp.currency).to.equal(prebidResponse[i].currency); expect(resp.dealId).to.equal(prebidResponse[i].dealId); if (resp.mediaType === 'video') { - expect(resp.vastXml.indexOf('quantumdex')).to.be.greaterThan(0); + expect(resp.vastXml.indexOf('apacdex')).to.be.greaterThan(0); } if (resp.mediaType === 'banner') { - expect(resp.ad.indexOf('Quantumdex AD')).to.be.greaterThan(0); + expect(resp.ad.indexOf('Apacdex AD')).to.be.greaterThan(0); } }); }); From 640a3cd0d7fd6d601694455d34f99be7af306393 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Mon, 2 Nov 2020 21:09:24 +0200 Subject: [PATCH 121/190] Adkernel: basic meta forwarding (#5836) --- modules/adkernelBidAdapter.js | 26 +++++++++++++++++--- test/spec/modules/adkernelBidAdapter_spec.js | 20 +++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 972dd696bf6..d489a1a84e0 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -116,13 +116,10 @@ export const spec = { requestId: rtbBid.impid, cpm: rtbBid.price, creativeId: rtbBid.crid, - currency: 'USD', + currency: response.cur || 'USD', ttl: 360, netRevenue: true }; - if (rtbBid.dealid !== undefined) { - prBid.dealId = rtbBid.dealid; - } if ('banner' in imp) { prBid.mediaType = BANNER; prBid.width = rtbBid.w; @@ -137,6 +134,27 @@ export const spec = { prBid.mediaType = NATIVE; prBid.native = buildNativeAd(JSON.parse(rtbBid.adm)); } + if (utils.isStr(rtbBid.dealid)) { + prBid.dealId = rtbBid.dealid; + } + if (utils.isArray(rtbBid.adomain)) { + utils.deepSetValue(prBid, 'meta.advertiserDomains', rtbBid.adomain); + } + if (utils.isArray(rtbBid.cat)) { + utils.deepSetValue(prBid, 'meta.secondaryCatIds', rtbBid.cat); + } + if (utils.isPlainObject(rtbBid.ext)) { + if (utils.isNumber(rtbBid.ext.advertiser_id)) { + utils.deepSetValue(prBid, 'meta.advertiserId', rtbBid.ext.advertiser_id); + } + if (utils.isStr(rtbBid.ext.advertiser_name)) { + utils.deepSetValue(prBid, 'meta.advertiserName', rtbBid.ext.advertiser_name); + } + if (utils.isStr(rtbBid.ext.agency_name)) { + utils.deepSetValue(prBid, 'meta.agencyName', rtbBid.ext.agency_name); + } + } + return prBid; }); }, diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 87504aa46af..1483816d94d 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -175,7 +175,6 @@ describe('Adkernel adapter', function () { dealid: 'deal' }] }], - cur: 'USD', ext: { adk_usersync: [{type: 1, url: 'https://adk.sync.com/sync'}] } @@ -192,7 +191,6 @@ describe('Adkernel adapter', function () { cid: '16855' }] }], - cur: 'USD' }, usersyncOnlyResponse = { id: 'nobid1', ext: { @@ -222,12 +220,18 @@ describe('Adkernel adapter', function () { } }), adomain: ['displayurl.com'], + cat: ['IAB1-4', 'IAB8-16', 'IAB25-5'], cid: '1', - crid: '4' + crid: '4', + ext: { + 'advertiser_id': 777, + 'advertiser_name': 'advertiser', + 'agency_name': 'agency' + } }] }], bidid: 'pTuOlf5KHUo', - cur: 'USD' + cur: 'EUR' }; var sandbox; @@ -587,7 +591,13 @@ describe('Adkernel adapter', function () { let resp = spec.interpretResponse({body: nativeResponse}, pbRequests[0])[0]; expect(resp).to.have.property('requestId', 'Bid_01'); expect(resp).to.have.property('cpm', 2.25); - expect(resp).to.have.property('currency', 'USD'); + expect(resp).to.have.property('currency', 'EUR'); + expect(resp).to.have.property('meta'); + expect(resp.meta.advertiserId).to.be.eql(777); + expect(resp.meta.advertiserName).to.be.eql('advertiser'); + expect(resp.meta.agencyName).to.be.eql('agency'); + expect(resp.meta.advertiserDomains).to.be.eql(['displayurl.com']); + expect(resp.meta.secondaryCatIds).to.be.eql(['IAB1-4', 'IAB8-16', 'IAB25-5']); expect(resp).to.have.property('mediaType', NATIVE); expect(resp).to.have.property('native'); expect(resp.native).to.have.property('clickUrl', 'http://rtb.com/click?i=pTuOlf5KHUo_0'); From 78d51176d60686b308696bf81f94dbc23952b868 Mon Sep 17 00:00:00 2001 From: John Salis Date: Mon, 2 Nov 2020 14:09:58 -0500 Subject: [PATCH 122/190] Add skip params to Beachfront adapter (#5847) * feat: add skip params and standard params to video bid request * refactor: add props to exclude list * refactor: bump adapter version Co-authored-by: John Salis --- modules/beachfrontBidAdapter.js | 23 +++++++++++------- .../spec/modules/beachfrontBidAdapter_spec.js | 24 +++++++++++++++---- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 12e78c684ad..f0e28956e5d 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -6,7 +6,7 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; -const ADAPTER_VERSION = '1.11'; +const ADAPTER_VERSION = '1.12'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; @@ -14,7 +14,7 @@ export const VIDEO_ENDPOINT = 'https://reachms.bfmio.com/bid.json?exchange_id='; export const BANNER_ENDPOINT = 'https://display.bfmio.com/prebid_display'; export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; -export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement']; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'skip', 'skipmin', 'skipafter']; export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; let appId = ''; @@ -258,12 +258,19 @@ function getTopWindowReferrer() { } function getVideoTargetingParams(bid) { - return Object.keys(Object(bid.params.video)) - .filter(param => includes(VIDEO_TARGETING, param)) - .reduce((obj, param) => { - obj[ param ] = bid.params.video[ param ]; - return obj; - }, {}); + const result = {}; + const excludeProps = ['playerSize', 'context', 'w', 'h']; + Object.keys(Object(bid.mediaTypes.video)) + .filter(key => !includes(excludeProps, key)) + .forEach(key => { + result[ key ] = bid.mediaTypes.video[ key ]; + }); + Object.keys(Object(bid.params.video)) + .filter(key => includes(VIDEO_TARGETING, key)) + .forEach(key => { + result[ key ] = bid.params.video[ key ]; + }); + return result; } function createVideoRequestData(bid, bidderRequest) { diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 5711e111e30..587596eaa5c 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -223,17 +223,33 @@ describe('BeachfrontAdapter', function () { expect(data.imp[0].video).to.deep.contain({ w: width, h: height }); }); - it('must override video targeting params', function () { + it('must set video params from the standard object', function () { const bidRequest = bidRequests[0]; const mimes = ['video/webm']; const playbackmethod = 2; const maxduration = 30; const placement = 4; - bidRequest.mediaTypes = { video: {} }; - bidRequest.params.video = { mimes, playbackmethod, maxduration, placement }; + const skip = 1; + bidRequest.mediaTypes = { + video: { mimes, playbackmethod, maxduration, placement, skip } + }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); + }); + + it('must override video params from the bidder object', function () { + const bidRequest = bidRequests[0]; + const mimes = ['video/webm']; + const playbackmethod = 2; + const maxduration = 30; + const placement = 4; + const skip = 1; + bidRequest.mediaTypes = { video: { placement: 3, skip: 0 } }; + bidRequest.params.video = { mimes, playbackmethod, maxduration, placement, skip }; const requests = spec.buildRequests([ bidRequest ]); const data = requests[0].data; - expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement }); + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); }); it('must add US privacy data to the request', function () { From 39df31f88071f5827671accdbc7dc9be1605b631 Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Mon, 2 Nov 2020 14:10:24 -0500 Subject: [PATCH 123/190] AMX RTB: improve URL handling in request (#5905) --- modules/amxBidAdapter.js | 24 +++++++++++---- test/spec/modules/amxBidAdapter_spec.js | 39 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 2e9529b633c..a1fa202c154 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -6,16 +6,28 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'amx'; const storage = getStorageManager(737, BIDDER_CODE); -const SIMPLE_TLD_TEST = /\.co\.\w{2,4}$/; +const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; -const VERSION = 'pba1.2'; +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'; -const getLocation = (request) => - parseUrl(deepAccess(request, 'refererInfo.canonicalUrl', location.href)) +function getLocation (request) { + const refInfo = request.refererInfo; + if (refInfo == null) { + return parseUrl(location.href); + } + + if (refInfo.isAmp && refInfo.referer != null) { + return parseUrl(refInfo.referer) + } + + const topUrl = refInfo.numIframes > 0 && refInfo.stack[0] != null + ? refInfo.stack[0] : location.href; + return parseUrl(topUrl); +}; const largestSize = (sizes, mediaTypes) => { const allSizes = sizes @@ -44,7 +56,7 @@ const nullOrType = (value, type) => function getID(loc) { const host = loc.hostname.split('.'); const short = host.slice( - host.length - (SIMPLE_TLD_TEST.test(loc.host) ? 3 : 2) + host.length - (SIMPLE_TLD_TEST.test(loc.hostname) ? 3 : 2) ).join('.'); return btoa(short).replace(/=+$/, ''); } @@ -239,7 +251,7 @@ export const spec = { gs: deepAccess(bidderRequest, 'gdprConsent.gdprApplies', ''), gc: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), u: deepAccess(bidderRequest, 'refererInfo.canonicalUrl', loc.href), - do: loc.host, + do: loc.hostname, re: deepAccess(bidderRequest, 'refererInfo.referer'), am: getUIDSafe(), usp: bidderRequest.uspConsent || '1---', diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 91315da8801..766045b0f3e 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -189,6 +189,45 @@ describe('AmxBidAdapter', () => { expect(data.tm).to.equal(true); }); + it('if prebid is in an iframe, will use the frame url as domain, if the topmost is not avialable', () => { + const { data } = spec.buildRequests([sampleBidRequestBase], { + ...sampleBidderRequest, + refererInfo: { + numIframes: 1, + referer: 'http://search-traffic-source.com', + stack: [] + } + }); + expect(data.do).to.equal('localhost') + expect(data.re).to.equal('http://search-traffic-source.com'); + }); + + it('if we are in AMP, make sure we use the canonical URL or the referrer (which is sourceUrl)', () => { + const { data } = spec.buildRequests([sampleBidRequestBase], { + ...sampleBidderRequest, + refererInfo: { + isAmp: true, + referer: 'http://real-publisher-site.com/content', + stack: [] + } + }); + expect(data.do).to.equal('real-publisher-site.com') + expect(data.re).to.equal('http://real-publisher-site.com/content'); + }) + + it('if prebid is in an iframe, will use the topmost url as domain', () => { + const { data } = spec.buildRequests([sampleBidRequestBase], { + ...sampleBidderRequest, + refererInfo: { + numIframes: 1, + referer: 'http://search-traffic-source.com', + stack: ['http://top-site.com', 'http://iframe.com'] + } + }); + expect(data.do).to.equal('top-site.com'); + expect(data.re).to.equal('http://search-traffic-source.com'); + }); + it('handles referer data and GDPR, USP Consent, COPPA', () => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); delete data.m; // don't deal with "m" in this test From d19f4ca5dcc511fb5169f4f049a7b9b63d2706c7 Mon Sep 17 00:00:00 2001 From: gpolaert Date: Mon, 2 Nov 2020 20:39:38 +0100 Subject: [PATCH 124/190] feat: add the elapsed time to events for debugging (#5868) * feat: add the elapsed time to events for debugging * naming --- src/events.js | 3 ++- src/utils.js | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/events.js b/src/events.js index e7a11635476..8749ddf206b 100644 --- a/src/events.js +++ b/src/events.js @@ -44,7 +44,8 @@ module.exports = (function () { eventsFired.push({ eventType: eventString, args: eventPayload, - id: key + id: key, + elapsedTime: utils.getPerformanceNow(), }); /** Push each specific callback to the `callbacks` array. diff --git a/src/utils.js b/src/utils.js index 8af7a25668d..acdf0f101ad 100644 --- a/src/utils.js +++ b/src/utils.js @@ -727,6 +727,14 @@ export function timestamp() { return new Date().getTime(); } +/** + * The returned value represents the time elapsed since the time origin. @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/now + * @returns {number} + */ +export function getPerformanceNow() { + return (window.performance && window.performance.now && window.performance.now()) || 0; +} + /** * When the deviceAccess flag config option is false, no cookies should be read or set * @returns {boolean} From 024cac8e02fe4dbc899edfab3c3cc5506a82e18d Mon Sep 17 00:00:00 2001 From: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Date: Tue, 3 Nov 2020 15:53:04 +0530 Subject: [PATCH 125/190] remove 'only' to run all tests (#5926) --- test/spec/modules/ixBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index ad4f90b0397..7ac4bd94f9d 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -4,7 +4,7 @@ import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/ixBidAdapter.js'; -describe.only('IndexexchangeAdapter', function () { +describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; const VIDEO_ENDPOINT_VERSION = 8.1; const BANNER_ENDPOINT_VERSION = 7.2; From 07ec2bc4117672959ee81082d4e8956ea3a37f6f Mon Sep 17 00:00:00 2001 From: Nicholas Colletti <34142758+ncolletti@users.noreply.github.com> Date: Tue, 3 Nov 2020 10:24:10 -0500 Subject: [PATCH 126/190] Add Auction Options Config (#5787) * feature/auction-timing * rename to auctionOptions * move filtering outside of loop and organized logic. * remove auctionOptions test page --- src/auction.js | 14 +++- src/config.js | 34 +++++++++ test/spec/auctionmanager_spec.js | 115 +++++++++++++++++++++++++++++++ test/spec/config_spec.js | 33 +++++++++ 4 files changed, 194 insertions(+), 2 deletions(-) diff --git a/src/auction.js b/src/auction.js index e0b8feb5c62..6285bfdd905 100644 --- a/src/auction.js +++ b/src/auction.js @@ -66,6 +66,7 @@ import { config } from './config.js'; import { userSync } from './userSync.js'; import { hook } from './hook.js'; import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; import { OUTSTREAM } from './video.js'; import { VIDEO } from './mediaTypes.js'; @@ -397,10 +398,19 @@ export function auctionCallbacks(auctionDone, auctionInstance) { function adapterDone() { let bidderRequest = this; + let bidderRequests = auctionInstance.getBidRequests(); + const auctionOptionsConfig = config.getConfig('auctionOptions'); bidderRequestsDone.add(bidderRequest); - allAdapterCalledDone = auctionInstance.getBidRequests() - .every(bidderRequest => bidderRequestsDone.has(bidderRequest)); + + if (auctionOptionsConfig && !utils.isEmpty(auctionOptionsConfig)) { + const secondaryBidders = auctionOptionsConfig.secondaryBidders; + if (secondaryBidders && !bidderRequests.every(bidder => includes(secondaryBidders, bidder.bidderCode))) { + bidderRequests = bidderRequests.filter(request => !includes(secondaryBidders, request.bidderCode)); + } + } + + allAdapterCalledDone = bidderRequests.every(bidderRequest => bidderRequestsDone.has(bidderRequest)); bidderRequest.bids.forEach(bid => { if (!bidResponseMap[bid.bidId]) { diff --git a/src/config.js b/src/config.js index 3284be52296..daaf739bbbd 100644 --- a/src/config.js +++ b/src/config.js @@ -199,6 +199,16 @@ export function newConfig() { set disableAjaxTimeout(val) { this._disableAjaxTimeout = val; }, + + _auctionOptions: {}, + get auctionOptions() { + return this._auctionOptions; + }, + set auctionOptions(val) { + if (validateauctionOptions(val)) { + this._auctionOptions = val; + } + }, }; if (config) { @@ -237,6 +247,30 @@ export function newConfig() { } return true; } + + function validateauctionOptions(val) { + if (!utils.isPlainObject(val)) { + utils.logWarn('Auction Options must be an object') + return false + } + + for (let k of Object.keys(val)) { + if (k !== 'secondaryBidders') { + utils.logWarn(`Auction Options given an incorrect param: ${k}`) + return false + } + if (k === 'secondaryBidders') { + if (!utils.isArray(val[k])) { + utils.logWarn(`Auction Options ${k} must be of type Array`); + return false + } else if (!val[k].every(utils.isStr)) { + utils.logWarn(`Auction Options ${k} must be only string`); + return false + } + } + } + return true; + } } /** diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 1517b5d69d8..d880ff0eaee 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -1282,4 +1282,119 @@ describe('auctionmanager.js', function () { assert.equal(doneSpy.callCount, 1); }) }); + + describe('auctionOptions', function() { + let bidRequests; + let doneSpy; + let clock; + let auction = { + getBidRequests: () => bidRequests, + getAuctionId: () => '1', + addBidReceived: () => true, + getTimeout: () => 1000 + } + let requiredBidder = BIDDER_CODE; + let requiredBidder1 = BIDDER_CODE1; + let secondaryBidder = 'doNotWaitForMe'; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + doneSpy = sinon.spy(); + config.setConfig({ + 'auctionOptions': { + secondaryBidders: [ secondaryBidder ] + } + }) + }); + + afterEach(() => { + doneSpy.resetHistory(); + config.resetConfig(); + clock.restore(); + }); + + it('should not wait to call auction done for secondary bidders', function () { + let bids1 = [mockBid({ bidderCode: requiredBidder })]; + let bids2 = [mockBid({ bidderCode: requiredBidder1 })]; + let bids3 = [mockBid({ bidderCode: secondaryBidder })]; + bidRequests = [ + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }), + ]; + let cbs = auctionCallbacks(doneSpy, auction); + // required bidder responds immeaditely to auction + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[0]); + assert.equal(doneSpy.callCount, 0); + + // auction waits for second required bidder to respond + clock.tick(100); + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); + cbs.adapterDone.call(bidRequests[1]); + + // auction done is reported and does not wait for secondaryBidder request + assert.equal(doneSpy.callCount, 1); + + cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); + cbs.adapterDone.call(bidRequests[2]); + }); + + it('should wait for all bidders if they are all secondary', function () { + config.setConfig({ + 'auctionOptions': { + secondaryBidders: [requiredBidder, requiredBidder1, secondaryBidder] + } + }) + let bids1 = [mockBid({ bidderCode: requiredBidder })]; + let bids2 = [mockBid({ bidderCode: requiredBidder1 })]; + let bids3 = [mockBid({ bidderCode: secondaryBidder })]; + bidRequests = [ + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }), + ]; + let cbs = auctionCallbacks(doneSpy, auction); + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[0]); + clock.tick(100); + assert.equal(doneSpy.callCount, 0) + + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); + cbs.adapterDone.call(bidRequests[1]); + clock.tick(100); + assert.equal(doneSpy.callCount, 0); + + cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); + cbs.adapterDone.call(bidRequests[2]); + assert.equal(doneSpy.callCount, 1); + }); + + it('should allow secondaryBidders to respond in auction before is is done', function () { + let bids1 = [mockBid({ bidderCode: requiredBidder })]; + let bids2 = [mockBid({ bidderCode: requiredBidder1 })]; + let bids3 = [mockBid({ bidderCode: secondaryBidder })]; + bidRequests = [ + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }), + ]; + let cbs = auctionCallbacks(doneSpy, auction); + // secondaryBidder is first to respond + cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); + cbs.adapterDone.call(bidRequests[2]); + clock.tick(100); + assert.equal(doneSpy.callCount, 0); + + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); + cbs.adapterDone.call(bidRequests[1]); + clock.tick(100); + assert.equal(doneSpy.callCount, 0); + + // first required bidder takes longest to respond, auction isn't marked as done until this occurs + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[0]); + assert.equal(doneSpy.callCount, 1); + }); + }); }); diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index be5b4bbb78b..81ce966efb2 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -211,4 +211,37 @@ describe('config API', function () { setConfig({ bidderSequence: 'random' }); expect(logWarnSpy.called).to.equal(false); }); + + it('sets auctionOptions', function () { + const auctionOptionsConfig = { + 'secondaryBidders': ['rubicon', 'appnexus'] + } + setConfig({ auctionOptions: auctionOptionsConfig }); + expect(getConfig('auctionOptions')).to.eql(auctionOptionsConfig); + }); + + it('should log warning for the wrong value passed to auctionOptions', function () { + setConfig({ auctionOptions: '' }); + expect(logWarnSpy.calledOnce).to.equal(true); + const warning = 'Auction Options must be an object'; + assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged'); + }); + + it('should log warning for invalid auctionOptions bidder values', function () { + setConfig({ auctionOptions: { + 'secondaryBidders': 'appnexus, rubicon', + }}); + expect(logWarnSpy.calledOnce).to.equal(true); + const warning = 'Auction Options secondaryBidders must be of type Array'; + assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged'); + }); + + it('should log warning for invalid properties to auctionOptions', function () { + setConfig({ auctionOptions: { + 'testing': true + }}); + expect(logWarnSpy.calledOnce).to.equal(true); + const warning = 'Auction Options given an incorrect param: testing'; + assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged'); + }); }); From 84649e8c3dc836d65a9efccfb0b518c952eb3279 Mon Sep 17 00:00:00 2001 From: sdao-tl <49252703+sdao-tl@users.noreply.github.com> Date: Tue, 3 Nov 2020 07:29:16 -0800 Subject: [PATCH 127/190] TL: Add GVLID, update validation method, add unit tests (#5904) * Add IdentityLink support and fix UnifiedId. It appears we've been looking for UnifiedId userIds on the bidderRequest object, when they are found on bidRequests. This commit fixes that error, and adds support for IdentityLink. * change maintainer email to group * TripleLift: Sending schain (#1) * Sending schain * null -> undefined * Hardcode sync endpoint protocol * Switch to EB2 sync endpoint * Add support for image based user syncing * Rename endpoint variable * Add assertion * Add CCPA query param * Simplify check for usPrivacy argument * put advertiser name in the bid.meta field if it exists * update unit tests with meta.advertiserName field * Triplelift: FPD key value pair support (#5) * Triplelift: Add support for global fpd * don't filter fpd * adds coppa support back in * add gvlid, update validation method, add unit tests * remove advertiserDomains logic * typo * update _buildResponseObject to use new instream validation Co-authored-by: Will Chapin Co-authored-by: colbertk <50499465+colbertk@users.noreply.github.com> Co-authored-by: David Andersen Co-authored-by: Brandon Ling Co-authored-by: colbertk Co-authored-by: Kevin Zhou Co-authored-by: kzhouTL <43545828+kzhouTL@users.noreply.github.com> Co-authored-by: Sy Dao --- modules/tripleliftBidAdapter.js | 9 +- .../spec/modules/tripleliftBidAdapter_spec.js | 167 +++++++++++++++++- 2 files changed, 165 insertions(+), 11 deletions(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 77a313a0f55..69c52711236 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -3,20 +3,17 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; +const GVLID = 28; const BIDDER_CODE = 'triplelift'; const STR_ENDPOINT = 'https://tlx.3lift.com/header/auction?'; let gdprApplies = true; let consentString = null; export const tripleliftAdapterSpec = { - + gvlid: GVLID, code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function (bid) { - if (bid.mediaTypes.video) { - let video = _getORTBVideo(bid); - if (!video.w || !video.h) return false; - } return typeof bid.params.inventoryCode !== 'undefined'; }, @@ -288,7 +285,7 @@ function _buildResponseObject(bidderRequest, bid) { meta: {} }; - if (breq.mediaTypes.video) { + if (_isInstreamBidRequest(breq)) { bidResponse.vastXml = bid.ad; bidResponse.mediaType = 'video'; }; diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 96cab3d837c..f01949755c7 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -83,15 +83,21 @@ describe('triplelift adapter', function () { expect(tripleliftAdapterSpec.isBidRequestValid(instreamBid)).to.equal(true); }); + it('should return true when required params found - instream - 2', function () { + delete instreamBid.mediaTypes.playerSize; + delete instreamBid.params.video.w; + delete instreamBid.params.video.h; + // the only required param is inventoryCode + expect(tripleliftAdapterSpec.isBidRequestValid(instreamBid)).to.equal(true); + }); + it('should return false when required params are not passed', function () { delete bid.params.inventoryCode; expect(tripleliftAdapterSpec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when required params are not passed - instream', function () { - delete instreamBid.mediaTypes.playerSize; - delete instreamBid.params.video.w; - delete instreamBid.params.video.h; + delete instreamBid.params.inventoryCode; expect(tripleliftAdapterSpec.isBidRequestValid(instreamBid)).to.equal(false); }); }); @@ -165,6 +171,7 @@ describe('triplelift adapter', function () { userId: {}, schain, }, + // banner and outstream video { bidder: 'triplelift', params: { @@ -197,6 +204,140 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + }, + // banner and incomplete video + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + + }, + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, + }, + // incomplete banner and incomplete video + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + + }, + banner: { + + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, + }, + // banner and instream video + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + }, + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, + }, + // banner and outream video and native + { + bidder: 'triplelift', + params: { + inventoryCode: 'outstream_test', + floor: 1.0, + video: { + mimes: ['video/mp4'], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + }, + banner: { + sizes: [ + [970, 250], + [1, 1] + ] + }, + native: { + + } + }, + adUnitCode: 'adunit-code-instream', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, } ]; @@ -261,10 +402,26 @@ describe('triplelift adapter', function () { expect(payload.imp[1].tagid).to.equal('insteam_test'); expect(payload.imp[1].floor).to.equal(1.0); expect(payload.imp[1].video).to.exist.and.to.be.a('object'); - + // banner and outstream video expect(payload.imp[2]).to.not.have.property('video'); expect(payload.imp[2]).to.have.property('banner'); expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + // banner and incomplete video + expect(payload.imp[3]).to.not.have.property('video'); + expect(payload.imp[3]).to.have.property('banner'); + expect(payload.imp[3].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + // incomplete mediatypes.banner and incomplete video + expect(payload.imp[4]).to.not.have.property('video'); + expect(payload.imp[4]).to.have.property('banner'); + expect(payload.imp[4].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + // banner and instream video + expect(payload.imp[5]).to.not.have.property('banner'); + expect(payload.imp[5]).to.have.property('video'); + expect(payload.imp[5].video).to.exist.and.to.be.a('object'); + // banner and outream video and native + expect(payload.imp[6]).to.not.have.property('video'); + expect(payload.imp[6]).to.have.property('banner'); + expect(payload.imp[6].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); }); it('should add tdid to the payload if included', function () { @@ -587,7 +744,7 @@ describe('triplelift adapter', function () { it('should include the advertiser name in the meta field if available', function () { let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); - expect(result[0].meta.advertiserName).to.equal('fake advertiser name') + expect(result[0].meta.advertiserName).to.equal('fake advertiser name'); expect(result[1].meta).to.not.have.key('advertiserName'); }); }); From 38a1c8edc8e6adefb81fd6a096aa511404f50305 Mon Sep 17 00:00:00 2001 From: harpere Date: Tue, 3 Nov 2020 14:44:31 -0500 Subject: [PATCH 128/190] rubicon - support all userIds (#5923) * rubicon - support all userIds * rubicon - support all userIds update * rubicon update to userId logic Co-authored-by: Eric Harper --- modules/rubiconBidAdapter.js | 107 +++++++++----------- test/spec/modules/rubiconBidAdapter_spec.js | 18 ++-- 2 files changed, 52 insertions(+), 73 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 5d631d2b08e..b44ae108b38 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -238,15 +238,6 @@ export const spec = { const eids = utils.deepAccess(bidderRequest, 'bids.0.userIdAsEids'); if (eids && eids.length) { utils.deepSetValue(data, 'user.ext.eids', eids); - - // liveintent requires additional props to be set - const liveIntentEid = find(data.user.ext.eids, eid => eid.source === 'liveintent.com'); - if (liveIntentEid) { - utils.deepSetValue(data, 'user.ext.tpid', { source: liveIntentEid.source, uid: liveIntentEid.uids[0].id }); - if (liveIntentEid.ext && liveIntentEid.ext.segments) { - utils.deepSetValue(data, 'rp.target.LIseg', liveIntentEid.ext.segments); - } - } } // set user.id value from config value @@ -374,6 +365,7 @@ export const spec = { getOrderedParams: function(params) { const containsTgV = /^tg_v/ const containsTgI = /^tg_i/ + const containsUId = /^eid_|^tpid_/ const orderedParams = [ 'account_id', @@ -386,18 +378,15 @@ export const spec = { 'gdpr_consent', 'us_privacy', 'rp_schain', - 'tpid_tdid', - 'tpid_liveintent.com', - 'tg_v.LIseg', - 'ppuid', - 'eid_pubcid.org', - 'eid_sharedid.org', - 'eid_criteo.com', - 'rf', - 'p_geo.latitude', - 'p_geo.longitude', - 'kw' - ].concat(Object.keys(params).filter(item => containsTgV.test(item))) + ].concat(Object.keys(params).filter(item => containsUId.test(item))) + .concat([ + 'x_liverampidl', + 'ppuid', + 'rf', + 'p_geo.latitude', + 'p_geo.longitude', + 'kw' + ]).concat(Object.keys(params).filter(item => containsTgV.test(item))) .concat(Object.keys(params).filter(item => containsTgI.test(item))) .concat([ 'tk_flint', @@ -503,51 +492,47 @@ export const spec = { // For SRA we need to explicitly put empty semi colons so AE treats it as empty, instead of copying the latter value data['p_pos'] = (params.position === 'atf' || params.position === 'btf') ? params.position : ''; - if (bidRequest.userIdAsEids && bidRequest.userIdAsEids.length) { - const unifiedId = find(bidRequest.userIdAsEids, eid => eid.source === 'adserver.org'); - if (unifiedId) { - data['tpid_tdid'] = unifiedId.uids[0].id; - } - const liveintentId = find(bidRequest.userIdAsEids, eid => eid.source === 'liveintent.com'); - if (liveintentId) { - data['tpid_liveintent.com'] = liveintentId.uids[0].id; - if (liveintentId.ext && Array.isArray(liveintentId.ext.segments) && liveintentId.ext.segments.length) { - data['tg_v.LIseg'] = liveintentId.ext.segments.join(','); - } - } - const liverampId = find(bidRequest.userIdAsEids, eid => eid.source === 'liveramp.com'); - if (liverampId) { - data['x_liverampidl'] = liverampId.uids[0].id; - } - const sharedId = find(bidRequest.userIdAsEids, eid => eid.source === 'sharedid.org'); - if (sharedId) { - data['eid_sharedid.org'] = `${sharedId.uids[0].id}^${sharedId.uids[0].atype}^${sharedId.uids[0].ext.third}`; - } - const pubcid = find(bidRequest.userIdAsEids, eid => eid.source === 'pubcid.org'); - if (pubcid) { - data['eid_pubcid.org'] = `${pubcid.uids[0].id}^${pubcid.uids[0].atype}`; - } - const criteoId = find(bidRequest.userIdAsEids, eid => eid.source === 'criteo.com'); - if (criteoId) { - data['eid_criteo.com'] = `${criteoId.uids[0].id}^${criteoId.uids[0].atype}`; - } - } - - // set ppuid value from config value + // pass publisher provided userId if configured const configUserId = config.getConfig('user.id'); if (configUserId) { data['ppuid'] = configUserId; - } else { - // if config.getConfig('user.id') doesn't return anything, then look for the first eid.uids[*].ext.stype === 'ppuid' - for (let i = 0; bidRequest.userIdAsEids && i < bidRequest.userIdAsEids.length; i++) { - if (bidRequest.userIdAsEids[i].uids) { - const pubProvidedId = find(bidRequest.userIdAsEids[i].uids, uid => uid.ext && uid.ext.stype === 'ppuid'); - if (pubProvidedId && pubProvidedId.id) { - data['ppuid'] = pubProvidedId.id; - break; + } + // loop through userIds and add to request + if (bidRequest.userIdAsEids) { + bidRequest.userIdAsEids.forEach(eid => { + try { + // special cases + if (eid.source === 'adserver.org') { + data['tpid_tdid'] = eid.uids[0].id; + data['eid_adserver.org'] = eid.uids[0].id; + } else if (eid.source === 'liveintent.com') { + data['tpid_liveintent.com'] = eid.uids[0].id; + data['eid_liveintent.com'] = eid.uids[0].id; + if (eid.ext && Array.isArray(eid.ext.segments) && eid.ext.segments.length) { + data['tg_v.LIseg'] = eid.ext.segments.join(','); + } + } else if (eid.source === 'liveramp.com') { + data['x_liverampidl'] = eid.uids[0].id; + } else if (eid.source === 'sharedid.org') { + data['eid_sharedid.org'] = `${eid.uids[0].id}^${eid.uids[0].atype}^${(eid.uids[0].ext && eid.uids[0].ext.third) || ''}`; + } else if (eid.source === 'id5-sync.com') { + data['eid_id5-sync.com'] = `${eid.uids[0].id}^${eid.uids[0].atype}^${(eid.ext && eid.ext.linkType) || ''}`; + } else { + // add anything else with this generic format + data[`eid_${eid.source}`] = `${eid.uids[0].id}^${eid.uids[0].atype || ''}`; } + // send AE "ppuid" signal if exists, and hasn't already been sent + if (!data['ppuid']) { + // get the first eid.uids[*].ext.stype === 'ppuid', if one exists + const ppId = find(eid.uids, uid => uid.ext && uid.ext.stype === 'ppuid'); + if (ppId && ppId.id) { + data['ppuid'] = ppId.id; + } + } + } catch (e) { + utils.logWarn('Rubicon: error reading eid:', eid, e); } - } + }); } if (bidderRequest.gdprConsent) { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 920a2fb2502..6659c281c33 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -246,7 +246,7 @@ describe('the rubicon adapter', function () { id: '4444444' }] }], - criteoId: '1111' + criteoId: '1111', }; bid.userIdAsEids = createEidsArray(bid.userId); bid.storedAuctionResponse = 11111; @@ -1097,6 +1097,7 @@ describe('the rubicon adapter', function () { let data = parseQuery(request.data); expect(data['tpid_tdid']).to.equal('abcd-efgh-ijkl-mnop-1234'); + expect(data['eid_adserver.org']).to.equal('abcd-efgh-ijkl-mnop-1234'); }); describe('LiveIntent support', function () { @@ -1104,7 +1105,8 @@ describe('the rubicon adapter', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { lipb: { - lipbid: '0000-1111-2222-3333' + lipbid: '0000-1111-2222-3333', + segments: ['segA', 'segB'] } }; clonedBid.userIdAsEids = createEidsArray(clonedBid.userId); @@ -1112,6 +1114,8 @@ describe('the rubicon adapter', function () { let data = parseQuery(request.data); expect(data['tpid_liveintent.com']).to.equal('0000-1111-2222-3333'); + expect(data['eid_liveintent.com']).to.equal('0000-1111-2222-3333'); + expect(data['tg_v.LIseg']).to.equal('segA,segB'); }); it('should send tg_v.LIseg when userIdAsEids contains liveintentId with ext.segments as array', function () { @@ -1429,20 +1433,10 @@ describe('the rubicon adapter', function () { expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); expect(post.user.ext.eids[0].ext.segments[1]).to.equal('segB'); - // Non-EID properties set using liveintent EID values - expect(post.user.ext).to.have.property('tpid').that.is.an('object'); - expect(post.user.ext.tpid.source).to.equal('liveintent.com'); - expect(post.user.ext.tpid.uid).to.equal('0000-1111-2222-3333'); - expect(post).to.have.property('rp').that.is.an('object'); - expect(post.rp).to.have.property('target').that.is.an('object'); - expect(post.rp.target).to.have.property('LIseg').that.is.an('array'); - expect(post.rp.target.LIseg[0]).to.equal('segA'); - expect(post.rp.target.LIseg[1]).to.equal('segB'); // LiveRamp should exist expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); expect(post.user.ext.eids[1].uids[0].atype).to.equal(1); - // SharedId should exist expect(post.user.ext.eids[2].source).to.equal('sharedid.org'); expect(post.user.ext.eids[2].uids[0].id).to.equal('1111'); From 97766fbda01d5d3ada1836dbb09366b4619119d6 Mon Sep 17 00:00:00 2001 From: OneTagDevOps <38786435+OneTagDevOps@users.noreply.github.com> Date: Tue, 3 Nov 2020 23:02:24 +0100 Subject: [PATCH 129/190] Adds tcf v2 support (#5883) Co-authored-by: francesco --- modules/onetagBidAdapter.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index fd66c8ce69f..1a2df023b81 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -7,11 +7,12 @@ import find from 'core-js-pure/features/array/find.js'; import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -const storage = getStorageManager(); - const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; const BIDDER_CODE = 'onetag'; +const GVLID = 241; + +const storage = getStorageManager(GVLID); /** * Determines whether or not the given bid request is valid. @@ -231,7 +232,7 @@ function getPageInfo() { timing: getTiming(), version: { prebid: '$prebid.version$', - adapter: '1.0.0' + adapter: '1.1.0' } }; } @@ -379,6 +380,7 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: isBidRequestValid, buildRequests: buildRequests, From 1da9ca6e07f02068d80ab71525bd5e5f592312fb Mon Sep 17 00:00:00 2001 From: liranbaruch Date: Wed, 4 Nov 2020 03:32:57 +0200 Subject: [PATCH 130/190] get dynamic ttl from the server response (#5896) * Change ironsource to be lower case all over code * Add test mode to the IronSource bidder * get dynamic ttl from the server response --- modules/ironsourceBidAdapter.js | 2 +- test/spec/modules/ironsourceBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ironsourceBidAdapter.js b/modules/ironsourceBidAdapter.js index 922e7b94c5b..e23c0cb857d 100644 --- a/modules/ironsourceBidAdapter.js +++ b/modules/ironsourceBidAdapter.js @@ -48,7 +48,7 @@ export const spec = { creativeId: body.requestId, currency: body.currency, netRevenue: body.netRevenue, - ttl: TTL, + ttl: body.ttl || TTL, vastXml: body.vastXml, mediaType: VIDEO }; diff --git a/test/spec/modules/ironsourceBidAdapter_spec.js b/test/spec/modules/ironsourceBidAdapter_spec.js index 0c59dfef14b..93c3a6fb7b9 100644 --- a/test/spec/modules/ironsourceBidAdapter_spec.js +++ b/test/spec/modules/ironsourceBidAdapter_spec.js @@ -278,7 +278,7 @@ describe('ironsourceAdapter', function () { mediaType: VIDEO } ]; - const result = spec.interpretResponse({ body: [response] }); + const result = spec.interpretResponse({ body: response }); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); }) From fa2394f984630886fe8edbfbd6989f8251e59766 Mon Sep 17 00:00:00 2001 From: Kylian Deau Date: Wed, 4 Nov 2020 10:38:20 +0100 Subject: [PATCH 131/190] Teads adapter: add Global Vendor Id (GDPR enforcement) (#5929) --- modules/teadsBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 6d55aabbfb5..aad7f6762c4 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -1,6 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; const utils = require('../src/utils.js'); const BIDDER_CODE = 'teads'; +const GVL_ID = 132; const ENDPOINT_URL = 'https://a.teads.tv/hb/bid-request'; const gdprStatus = { GDPR_APPLIES_PUBLISHER: 12, @@ -11,6 +12,7 @@ const gdprStatus = { export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, supportedMediaTypes: ['video', 'banner'], /** * Determines whether or not the given bid request is valid. From 7bbb05312ca86e8c9db1fdd495222902e8b1bc83 Mon Sep 17 00:00:00 2001 From: Stephan Brosinski Date: Wed, 4 Nov 2020 14:02:47 +0100 Subject: [PATCH 132/190] Smaato: Add userIds to BidRequest (#5927) --- modules/smaatoBidAdapter.js | 5 +++ test/spec/modules/smaatoBidAdapter_spec.js | 46 +++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 49b4ed6aa34..93915689cee 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -117,6 +117,11 @@ const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { utils.deepSetValue(request, 'device.ifa', ifa); } + const eids = utils.deepAccess(validBidRequests[0], 'userIdAsEids'); + if (eids && eids.length) { + utils.deepSetValue(request, 'user.ext.eids', eids); + } + utils.logInfo('[SMAATO] OpenRTB Request:', request); return JSON.stringify(request); } diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 13716b51436..6af0a855800 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -1,6 +1,7 @@ import { spec } from 'modules/smaatoBidAdapter.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; +import {createEidsArray} from 'modules/userId/eids.js'; const imageAd = { image: { @@ -321,6 +322,37 @@ const inAppBidRequest = { bidderWinsCount: 0 }; +const userIdBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + mediaTypes: { + banner: { + sizes: [[300, 50]] + } + }, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + userId: { + criteoId: '123456', + tdid: '89145' + }, + userIdAsEids: createEidsArray({ + criteoId: '123456', + tdid: '89145' + }) +}; + describe('smaatoBidAdapterTest', () => { describe('isBidRequestValid', () => { it('has valid params', () => { @@ -422,7 +454,11 @@ describe('smaatoBidAdapterTest', () => { expect(req_fpd.user.ext.consent).to.equal('HFIDUYFIUYIUYWIPOI87392DSU'); expect(req_fpd.site.keywords).to.eql('power tools,drills'); expect(req_fpd.site.publisher.id).to.equal('publisherId'); - }) + }); + + it('has no user ids', () => { + expect(this.req.user.ext.eids).to.not.exist; + }); }); describe('buildRequests for video imps', () => { @@ -513,6 +549,14 @@ describe('smaatoBidAdapterTest', () => { }); }); + describe('user ids in requests', () => { + it('user ids are added to user.ext.eids', () => { + let req = JSON.parse(spec.buildRequests([userIdBidRequest], defaultBidderRequest).data); + expect(req.user.ext.eids).to.exist; + expect(req.user.ext.eids).to.have.length(2); + }); + }); + describe('interpretResponse', () => { it('single image reponse', () => { const bids = spec.interpretResponse(openRtbBidResponse(ADTYPE_IMG), request); From 7729bb7e6d167d862056dcabbe299d6b6052543f Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Wed, 4 Nov 2020 20:09:21 +0100 Subject: [PATCH 133/190] Mediasquare: add native and video support (#5823) * Mediasquare: Add support for uspConsent + schain userIds support. Plus enhance userSync * fix iframeEnabled and pixelEnabled + suggested shortand statement * mediasquare bidder: add metrics to onBidWon Event * mediasquare bidder: fix getUserSyncs * MediaSquare: add native and video support --- modules/mediasquareBidAdapter.js | 14 ++++- .../modules/mediasquareBidAdapter_spec.js | 56 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 288526d3cc5..87f768d3b77 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -1,7 +1,7 @@ import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' @@ -13,7 +13,7 @@ const BIDDER_ENDPOINT_WINNING = 'winning'; export const spec = { code: BIDDER_CODE, aliases: ['msq'], // short code - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE, VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -99,6 +99,14 @@ export const spec = { 'code': value['code'] } }; + if ('native' in value) { + bidResponse['native'] = value['native']; + bidResponse['mediaType'] = 'native'; + } else if ('video' in value) { + if ('url' in value['video']) { bidResponse['vastUrl'] = value['video']['url'] } + if ('xml' in value['video']) { bidResponse['vastXml'] = value['video']['xml'] } + bidResponse['mediaType'] = 'video'; + } if (value.hasOwnProperty('deal_id')) { bidResponse['dealId'] = value['deal_id']; } bidResponses.push(bidResponse); }); @@ -136,7 +144,7 @@ export const spec = { // fires a pixel to confirm a winning bid let params = []; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; - let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'auctionId', 'requestId'] + let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond'] if (bid.hasOwnProperty('mediasquare')) { if (bid['mediasquare'].hasOwnProperty('bidder')) { params.push('bidder=' + bid['mediasquare']['bidder']); } if (bid['mediasquare'].hasOwnProperty('code')) { params.push('code=' + bid['mediasquare']['code']); } diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index 5d930f2b6ac..3c18cfe0be7 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -24,6 +24,43 @@ describe('MediaSquare bid adapter tests', function () { code: 'publishername_atf_desktop_rg_pave' }, }]; + var VIDEO_PARAMS = [{ + adUnitCode: 'banner-div', + bidId: 'aaaa1234', + auctionId: 'bbbb1234', + transactionId: 'cccc1234', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + } + }, + bidder: 'mediasquare', + params: { + owner: 'test', + code: 'publishername_atf_desktop_rg_pave' + }, + }]; + var NATIVE_PARAMS = [{ + adUnitCode: 'banner-div', + bidId: 'aaaa1234', + auctionId: 'bbbb1234', + transactionId: 'cccc1234', + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + } + }, + bidder: 'mediasquare', + params: { + owner: 'test', + code: 'publishername_atf_desktop_rg_pave' + }, + }]; var BID_RESPONSE = {'body': { 'responses': [{ @@ -128,4 +165,23 @@ describe('MediaSquare bid adapter tests', function () { expect(syncs[0]).to.have.property('type').and.to.equal('image'); expect(syncs[0]).to.have.property('url').and.to.equal('http://www.cookie.sync.org/'); }); + it('Verifies native in bid response', function () { + const request = spec.buildRequests(NATIVE_PARAMS, DEFAULT_OPTIONS); + BID_RESPONSE.body.responses[0].native = {'title': 'native title'}; + const response = spec.interpretResponse(BID_RESPONSE, request); + expect(response).to.have.lengthOf(1); + const bid = response[0]; + expect(bid).to.have.property('native'); + delete BID_RESPONSE.body.responses[0].native; + }); + it('Verifies video in bid response', function () { + const request = spec.buildRequests(VIDEO_PARAMS, DEFAULT_OPTIONS); + BID_RESPONSE.body.responses[0].video = {'xml': 'my vast XML', 'url': 'my vast url'}; + const response = spec.interpretResponse(BID_RESPONSE, request); + expect(response).to.have.lengthOf(1); + const bid = response[0]; + expect(bid).to.have.property('vastXml'); + expect(bid).to.have.property('vastUrl'); + delete BID_RESPONSE.body.responses[0].video; + }); }); From 31e00f20b73ed5e8f35aa9178972158d3b8cf65d Mon Sep 17 00:00:00 2001 From: Aparna Rao Date: Wed, 4 Nov 2020 14:47:35 -0500 Subject: [PATCH 134/190] 33Across: Added Video Support (#5884) * check gdpr in buildRequest * User sync based on whether gdpr applies or not * check if consent data exists during user sync * split user sync into further branches: 1) when gdpr does not apply 2) when consent data is unavailable * contribute viewability to ttxRequest * update tests * remove window mock from tests * use local variables * introduce ServerRequestBuilder * add withOptions() method to ServerRequestBuilder * add semicolons * sync up package-lock.json with upstream/master * stub window.top in tests * introduce getTopWindowSize() for test purpose * reformat code * add withSite() method to TtxRequestBuilder add withSite() method to TtxRequestBuilder * add isIframe() and _isViewabilityMeasurable() * handle NON_MEASURABLE viewability in nested iframes * consider page visibility, stub utils functions getWindowTop() and getWindowSelf() * contribute viewability as 0 for inactive tab * add prebidjs version to ttx request * send caller as an array * send viewability as non measurable when unable to locate target HTMLElement, add warning message * fix JSDoc in utils.js * introduce mapAdSlotPathToElementId() * introduce getAdSlotHTMLElement(), add logging * introduce mapAdSlotPathToElementId() * update logging in ad unit path to element id mapping * rephrase logging, fix tests * update adapter documentation * remove excessive logging * improve logging * revert change * fix return of _mapAdUnitPathToElementId() * improve logging of _mapAdUnitPathToElementId() * do not use Array.find() * return id once element is found * return id once element is found * let -> const * Removing killswitch behavior for GDPR * Updated comments to reflect current gdpr logic * URI encode consent string * Updated example site ID to help Prebid team e2e test our adapter * send page url in ortb * Removed redundant pageUrl default * Restored package-log.json that mirrors prebid's repo * Sending USP string during buildRequest * Adding USP consent data to user sync * add unit test for syncing without bidrequest * Changed to uspConsent to make the connatation consistent * Resetting adapter state in adapter after user sync rather than exposing it. * removed console log * Adding schain info * remove setting empty format ext * better tests invalid values * removing validation of schain * Fixed lint errors * First cut for bidfloors support * fixed where getFloors is read * fixed merge conflicts * support the guid in the api endpoint * Reformat + validation updates * refactor banner to conform to mediaType format * Building video ORTB * code review changes for better refactor * Building video ORTB * Interpret video response * Updated documentation * Updated supported mediatypes * Added bidfloors * Adding support bidder specific overrides * only validate startdelay when instream * fixed incorrect params for instream * Removed usage of an actual GUID for safety. * Added mimes and protocols as required * placement is +ve int * fix for sizes + valid sample GUID Co-authored-by: Gleb Glushtsov Co-authored-by: Gleb Glushtsov Co-authored-by: Gleb Glushtsov Co-authored-by: Aparna Hegde Co-authored-by: Aparna Hegde Co-authored-by: Aparna Hegde Co-authored-by: Aparna Hegde Co-authored-by: Aparna Hegde Co-authored-by: Aparna Hegde Co-authored-by: terryc33x <64039851+terryc33x@users.noreply.github.com> Co-authored-by: Terry Chen --- modules/33acrossBidAdapter.js | 518 ++++++++---- modules/33acrossBidAdapter.md | 146 +++- test/spec/modules/33acrossBidAdapter_spec.js | 811 ++++++++++++++++--- 3 files changed, 1184 insertions(+), 291 deletions(-) diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 798b6450946..65df8baad2e 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -1,12 +1,37 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import * as utils from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = '33across'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; const SYNC_ENDPOINT = 'https://ssc-cms.33across.com/ps/?m=xch&rt=html&ru=deb'; -const MEDIA_TYPE = 'banner'; + const CURRENCY = 'USD'; +const GUID_PATTERN = /^[a-zA-Z0-9_-]{22}$/; + +const PRODUCT = { + SIAB: 'siab', + INVIEW: 'inview', + INSTREAM: 'instream' +}; + +const VIDEO_ORTB_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity' +]; const adapterState = { uniqueSiteIds: [] @@ -14,57 +39,122 @@ const adapterState = { const NON_MEASURABLE = 'nm'; -// All this assumes that only one bid is ever returned by ttx -function _createBidResponse(response) { - return { - requestId: response.id, - bidderCode: BIDDER_CODE, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ad: response.seatbid[0].bid[0].adm, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - currency: response.cur, - netRevenue: true +// **************************** VALIDATION *************************** // +function isBidRequestValid(bid) { + return ( + _validateBasic(bid) && + _validateBanner(bid) && + _validateVideo(bid) + ); +} + +function _validateBasic(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + if (!_validateGUID(bid)) { + return false; } + + return true; } -function _isViewabilityMeasurable(element) { - return !_isIframe() && element !== null; +function _validateGUID(bid) { + const siteID = utils.deepAccess(bid, 'params.siteId', '') || ''; + if (siteID.trim().match(GUID_PATTERN) === null) { + return false; + } + + return true; } -function _getViewability(element, topWin, { w, h } = {}) { - return topWin.document.visibilityState === 'visible' - ? _getPercentInView(element, topWin, { w, h }) - : 0; +function _validateBanner(bid) { + const banner = utils.deepAccess(bid, 'mediaTypes.banner'); + // If there's no banner no need to validate against banner rules + if (banner === undefined) { + return true; + } + + if (!Array.isArray(banner.sizes)) { + return false; + } + + return true; } -function _mapAdUnitPathToElementId(adUnitCode) { - if (utils.isGptPubadsDefined()) { - // eslint-disable-next-line no-undef - const adSlots = googletag.pubads().getSlots(); - const isMatchingAdSlot = utils.isSlotMatchingAdUnitCode(adUnitCode); +function _validateVideo(bid) { + const videoAdUnit = utils.deepAccess(bid, 'mediaTypes.video'); + const videoBidderParams = utils.deepAccess(bid, 'params.video', {}); - for (let i = 0; i < adSlots.length; i++) { - if (isMatchingAdSlot(adSlots[i])) { - const id = adSlots[i].getSlotElementId(); + // If there's no video no need to validate against video rules + if (videoAdUnit === undefined) { + return true; + } - utils.logInfo(`[33Across Adapter] Map ad unit path to HTML element id: '${adUnitCode}' -> ${id}`); + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } - return id; - } - } + if (!videoAdUnit.context) { + return false; } - utils.logWarn(`[33Across Adapter] Unable to locate element for ad unit code: '${adUnitCode}'`); + const videoParams = { + ...videoAdUnit, + ...videoBidderParams + }; - return null; + if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { + return false; + } + + if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { + return false; + } + + // If placement if defined, it must be a number + if ( + typeof videoParams.placement !== 'undefined' && + typeof videoParams.placement !== 'number' + ) { + return false; + } + + // If startdelay is defined it must be a number + if ( + videoAdUnit.context === 'instream' && + typeof videoParams.startdelay !== 'undefined' && + typeof videoParams.startdelay !== 'number' + ) { + return false; + } + + return true; } -function _getAdSlotHTMLElement(adUnitCode) { - return document.getElementById(adUnitCode) || - document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); +// **************************** BUILD REQUESTS *************************** // +// NOTE: With regards to gdrp consent data, the server will independently +// infer the gdpr applicability therefore, setting the default value to false +function buildRequests(bidRequests, bidderRequest) { + const gdprConsent = Object.assign({ + consentString: undefined, + gdprApplies: false + }, bidderRequest && bidderRequest.gdprConsent); + + const uspConsent = bidderRequest && bidderRequest.uspConsent; + const pageUrl = (bidderRequest && bidderRequest.refererInfo) ? (bidderRequest.refererInfo.referer) : (undefined); + + adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(utils.uniques); + + return bidRequests.map(bidRequest => _createServerRequest( + { + bidRequest, + gdprConsent, + uspConsent, + pageUrl + }) + ); } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request @@ -72,46 +162,28 @@ function _getAdSlotHTMLElement(adUnitCode) { function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl}) { const ttxRequest = {}; const params = bidRequest.params; - const element = _getAdSlotHTMLElement(bidRequest.adUnitCode); - const sizes = _transformSizes(bidRequest.sizes); - let format; - - // We support size based bidfloors so obtain one if there's a rule associated - if (typeof bidRequest.getFloor === 'function') { - let getFloor = bidRequest.getFloor.bind(bidRequest); - - format = sizes.map((size) => { - const formatExt = _getBidFloors(getFloor, size); + /* + * Infer data for the request payload + */ + ttxRequest.imp = [{}]; - return Object.assign({}, size, formatExt); - }); - } else { - format = sizes; + if (utils.deepAccess(bidRequest, 'mediaTypes.banner')) { + ttxRequest.imp[0].banner = { + ..._buildBannerORTB(bidRequest) + } } - const minSize = _getMinSize(sizes); - - const viewabilityAmount = _isViewabilityMeasurable(element) - ? _getViewability(element, utils.getWindowTop(), minSize) - : NON_MEASURABLE; - - const contributeViewability = ViewabilityContributor(viewabilityAmount); + if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + ttxRequest.imp[0].video = _buildVideoORTB(bidRequest); + } - /* - * Infer data for the request payload - */ - ttxRequest.imp = []; - ttxRequest.imp[0] = { - banner: { - format - }, - ext: { - ttx: { - prod: params.productId - } + ttxRequest.imp[0].ext = { + ttx: { + prod: _getProduct(bidRequest) } }; + ttxRequest.site = { id: params.siteId }; if (pageUrl) { @@ -173,53 +245,187 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl return { 'method': 'POST', 'url': url, - 'data': JSON.stringify(contributeViewability(ttxRequest)), + 'data': JSON.stringify(ttxRequest), 'options': options } } -// Sync object will always be of type iframe for TTX -function _createSync({ siteId = 'zzz000000000003zzz', gdprConsent = {}, uspConsent }) { - const ttxSettings = config.getConfig('ttxSettings'); - const syncUrl = (ttxSettings && ttxSettings.syncUrl) || SYNC_ENDPOINT; +// BUILD REQUESTS: SIZE INFERENCE +function _transformSizes(sizes) { + if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { + return [ _getSize(sizes) ]; + } - const { consentString, gdprApplies } = gdprConsent; + return sizes.map(_getSize); +} - const sync = { - type: 'iframe', - url: `${syncUrl}&id=${siteId}&gdpr_consent=${encodeURIComponent(consentString)}&us_privacy=${encodeURIComponent(uspConsent)}` - }; +function _getSize(size) { + return { + w: parseInt(size[0], 10), + h: parseInt(size[1], 10) + } +} - if (typeof gdprApplies === 'boolean') { - sync.url += `&gdpr=${Number(gdprApplies)}`; +// BUILD REQUESTS: PRODUCT INFERENCE +function _getProduct(bidRequest) { + const { params, mediaTypes } = bidRequest; + + const { banner, video } = mediaTypes; + + if ((video && !banner) && video.context === 'instream') { + return PRODUCT.INSTREAM; } - return sync; + return (params.productId === PRODUCT.INVIEW) ? (params.productId) : PRODUCT.SIAB; } -function _getBidFloors(getFloor, size) { - const bidFloors = getFloor({ +// BUILD REQUESTS: BANNER +function _buildBannerORTB(bidRequest) { + const bannerAdUnit = utils.deepAccess(bidRequest, 'mediaTypes.banner', {}); + const element = _getAdSlotHTMLElement(bidRequest.adUnitCode); + + const sizes = _transformSizes(bannerAdUnit.sizes); + + let format; + + // We support size based bidfloors so obtain one if there's a rule associated + if (typeof bidRequest.getFloor === 'function') { + format = sizes.map((size) => { + const bidfloors = _getBidFloors(bidRequest, size, BANNER); + + let formatExt; + if (bidfloors) { + formatExt = { + ext: { + ttx: { + bidfloors: [ bidfloors ] + } + } + } + } + + return Object.assign({}, size, formatExt); + }); + } else { + format = sizes; + } + + const minSize = _getMinSize(sizes); + + const viewabilityAmount = _isViewabilityMeasurable(element) + ? _getViewability(element, utils.getWindowTop(), minSize) + : NON_MEASURABLE; + + const ext = contributeViewability(viewabilityAmount); + + return { + format, + ext + } +} + +// BUILD REQUESTS: VIDEO +// eslint-disable-next-line no-unused-vars +function _buildVideoORTB(bidRequest) { + const videoAdUnit = utils.deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {}); + + const videoParams = { + ...videoAdUnit, + ...videoBidderParams // Bidder Specific overrides + }; + + const video = {} + + const {w, h} = _getSize(videoParams.playerSize[0]); + video.w = w; + video.h = h; + + // Obtain all ORTB params related video from Ad Unit + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + video[param] = videoParams[param]; + } + }); + + const product = _getProduct(bidRequest); + + // Placement Inference Rules: + // - If no placement is defined then default to 2 (In Banner) + // - If product is instream (for instream context) then override placement to 1 + video.placement = video.placement || 2; + + if (product === PRODUCT.INSTREAM) { + video.startdelay = video.startdelay || 0; + video.placement = 1; + }; + + // bidfloors + if (typeof bidRequest.getFloor === 'function') { + const bidfloors = _getBidFloors(bidRequest, {w: video.w, h: video.h}, VIDEO); + + if (bidfloors) { + Object.assign(video, { + ext: { + ttx: { + bidfloors: [ bidfloors ] + } + } + }); + } + } + return video; +} + +// BUILD REQUESTS: BIDFLOORS +function _getBidFloors(bidRequest, size, mediaType) { + const bidFloors = bidRequest.getFloor({ currency: CURRENCY, - mediaType: MEDIA_TYPE, + mediaType, size: [ size.w, size.h ] }); if (!isNaN(bidFloors.floor) && (bidFloors.currency === CURRENCY)) { - return { - ext: { - ttx: { - bidfloors: [ bidFloors.floor ] - } + return bidFloors.floor; + } +} + +// BUILD REQUESTS: VIEWABILITY +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, { w, h } = {}) { + return topWin.document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; +} + +function _mapAdUnitPathToElementId(adUnitCode) { + if (utils.isGptPubadsDefined()) { + // eslint-disable-next-line no-undef + const adSlots = googletag.pubads().getSlots(); + const isMatchingAdSlot = utils.isSlotMatchingAdUnitCode(adUnitCode); + + for (let i = 0; i < adSlots.length; i++) { + if (isMatchingAdSlot(adSlots[i])) { + const id = adSlots[i].getSlotElementId(); + + utils.logInfo(`[33Across Adapter] Map ad unit path to HTML element id: '${adUnitCode}' -> ${id}`); + + return id; } } } + + utils.logWarn(`[33Across Adapter] Unable to locate element for ad unit code: '${adUnitCode}'`); + + return null; } -function _getSize(size) { - return { - w: parseInt(size[0], 10), - h: parseInt(size[1], 10) - } +function _getAdSlotHTMLElement(adUnitCode) { + return document.getElementById(adUnitCode) || + document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); } function _getMinSize(sizes) { @@ -239,14 +445,6 @@ function _getBoundingBox(element, { w, h } = {}) { return { width, height, left, top, right, bottom }; } -function _transformSizes(sizes) { - if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { - return [ _getSize(sizes) ]; - } - - return sizes.map(_getSize); -} - function _getIntersectionOfRects(rects) { const bbox = { left: rects[0].left, @@ -307,20 +505,16 @@ function _getPercentInView(element, topWin, { w, h } = {}) { /** * Viewability contribution to request.. */ -function ViewabilityContributor(viewabilityAmount) { - function contributeViewability(ttxRequest) { - const req = Object.assign({}, ttxRequest); - const imp = req.imp = req.imp.map(impItem => Object.assign({}, impItem)); - const banner = imp[0].banner = Object.assign({}, imp[0].banner); - const ext = banner.ext = Object.assign({}, banner.ext); - const ttx = ext.ttx = Object.assign({}, ext.ttx); - - ttx.viewability = { amount: isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount) }; +function contributeViewability(viewabilityAmount) { + const amount = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); - return req; - } - - return contributeViewability; + return { + ttx: { + viewability: { + amount + } + } + }; } function _isIframe() { @@ -331,42 +525,9 @@ function _isIframe() { } } -function isBidRequestValid(bid) { - if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { - return false; - } - - if (typeof bid.params.siteId === 'undefined' || typeof bid.params.productId === 'undefined') { - return false; - } - - return true; -} - -// NOTE: With regards to gdrp consent data, -// - the server independently infers gdpr applicability therefore, setting the default value to false -function buildRequests(bidRequests, bidderRequest) { - const gdprConsent = Object.assign({ - consentString: undefined, - gdprApplies: false - }, bidderRequest && bidderRequest.gdprConsent); - - const uspConsent = bidderRequest && bidderRequest.uspConsent; - const pageUrl = (bidderRequest && bidderRequest.refererInfo) ? (bidderRequest.refererInfo.referer) : (undefined); - - adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(utils.uniques); - - return bidRequests.map(bidRequest => _createServerRequest( - { - bidRequest, - gdprConsent, - uspConsent, - pageUrl - }) - ); -} - -// NOTE: At this point, the response from 33exchange will only ever contain one bid i.e. the highest bid +// **************************** INTERPRET RESPONSE ******************************** // +// NOTE: At this point, the response from 33exchange will only ever contain one bid +// i.e. the highest bid function interpretResponse(serverResponse, bidRequest) { const bidResponses = []; @@ -378,6 +539,36 @@ function interpretResponse(serverResponse, bidRequest) { return bidResponses; } +// All this assumes that only one bid is ever returned by ttx +function _createBidResponse(response) { + const bid = { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ad: response.seatbid[0].bid[0].adm, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + mediaType: utils.deepAccess(response.seatbid[0].bid[0], 'ext.ttx.mediaType', BANNER), + currency: response.cur, + netRevenue: true + } + + if (bid.mediaType === VIDEO) { + const vastType = utils.deepAccess(response.seatbid[0].bid[0], 'ext.ttx.vastType', 'xml'); + + if (vastType === 'xml') { + bid.vastXml = bid.ad; + } else { + bid.vastUrl = bid.ad; + } + } + + return bid; +} + +// **************************** USER SYNC *************************** // // Register one sync per unique guid so long as iframe is enable // Else no syncs // For logic on how we handle gdpr data see _createSyncs and module's unit tests @@ -395,11 +586,30 @@ function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { return syncUrls; } +// Sync object will always be of type iframe for TTX +function _createSync({ siteId = 'zzz000000000003zzz', gdprConsent = {}, uspConsent }) { + const ttxSettings = config.getConfig('ttxSettings'); + const syncUrl = (ttxSettings && ttxSettings.syncUrl) || SYNC_ENDPOINT; + + const { consentString, gdprApplies } = gdprConsent; + + const sync = { + type: 'iframe', + url: `${syncUrl}&id=${siteId}&gdpr_consent=${encodeURIComponent(consentString)}&us_privacy=${encodeURIComponent(uspConsent)}` + }; + + if (typeof gdprApplies === 'boolean') { + sync.url += `&gdpr=${Number(gdprApplies)}`; + } + + return sync; +} + export const spec = { NON_MEASURABLE, code: BIDDER_CODE, - + supportedMediaTypes: [ BANNER, VIDEO ], isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/33acrossBidAdapter.md b/modules/33acrossBidAdapter.md index c313f3b6e0b..c01c04251e5 100644 --- a/modules/33acrossBidAdapter.md +++ b/modules/33acrossBidAdapter.md @@ -10,23 +10,145 @@ Maintainer: headerbidding@33across.com Connects to 33Across's exchange for bids. -33Across bid adapter supports only Banner at present and follows MRA +33Across bid adapter supports Banner and Video at present and follows MRA # Sample Ad Unit: For Publishers +## Sample Banner only Ad Unit ``` var adUnits = [ { - code: '33across-hb-ad-123456-1', // ad slot HTML element ID - sizes: [ - [300, 250], - [728, 90] - ], - bids: [{ - bidder: '33across', - params: { - siteId: 'cxBE0qjUir6iopaKkGJozW', - productId: 'siab' + code: '33across-hb-ad-123456-1', // ad slot HTML element ID + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + } + bids: [{ + bidder: '33across', + params: { + siteId: 'sample33xGUID123456789', + productId: 'siab' + } + }] +} +``` + +## Sample Video only Ad Unit: Outstream +``` +var adUnits = [ +{ + code: '33across-hb-ad-123456-1', // ad slot HTML element ID + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'outstream', + placement: 2 + ... // Aditional ORTB video params + } + }, + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + adResponse = { + ad: { + video: { + content: bid.vastXml, + player_height: bid.playerHeight, + player_width: bid.playerWidth + } + } } - }] + // push to render queue because ANOutstreamVideo may not be loaded yet. + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, // target div id to render video. + adResponse: adResponse + }); + }); + } + }, + bids: [{ + bidder: '33across', + params: { + siteId: 'sample33xGUID123456789', + productId: 'siab' + } + }] +} +``` + +## Sample Multi-Format Ad Unit: Outstream +``` +var adUnits = [ +{ + code: '33across-hb-ad-123456-1', // ad slot HTML element ID + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + }, + video: { + playerSize: [300, 250], + context: 'outstream', + placement: 2 + ... // Aditional ORTB video params + } + }, + renderer: { + url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + adResponse = { + ad: { + video: { + content: bid.vastXml, + player_height: bid.playerHeight, + player_width: bid.playerWidth + } + } + } + // push to render queue because ANOutstreamVideo may not be loaded yet. + bid.renderer.push(() => { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, // target div id to render video. + adResponse: adResponse + }); + }); + } + }, + bids: [{ + bidder: '33across', + params: { + siteId: 'sample33xGUID123456789', + productId: 'siab' + } + }] +} +``` + +## Sample Video only Ad Unit: Instream +``` +var adUnits = [ +{ + code: '33across-hb-ad-123456-1', // ad slot HTML element ID + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'intstream', + placement: 1 + ... // Aditional ORTB video params + } + } + bids: [{ + bidder: '33across', + params: { + siteId: 'sample33xGUID123456789', + productId: 'instream' + } + }] } ``` diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index d30659791ea..edc7b7a2767 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -7,8 +7,8 @@ import { spec } from 'modules/33acrossBidAdapter.js'; describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; - const SITE_ID = 'pub1234'; - const PRODUCT_ID = 'product1'; + const SITE_ID = 'sample33xGUID123456789'; + const PRODUCT_ID = 'siab'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; let element, win; @@ -17,39 +17,13 @@ describe('33acrossBidAdapter:', function () { function TtxRequestBuilder() { const ttxRequest = { - imp: [{ - banner: { - format: [ - { - w: 300, - h: 250 - }, - { - w: 728, - h: 90 - } - ], - ext: { - ttx: { - viewability: { - amount: 100 - } - } - } - }, - ext: { - ttx: { - prod: PRODUCT_ID - } - } - }], + imp: [{}], site: { id: SITE_ID }, id: 'b1', user: { ext: { - consent: undefined } }, regs: { @@ -69,13 +43,52 @@ describe('33acrossBidAdapter:', function () { } }; - this.withSizes = sizes => { + this.withBanner = () => { + Object.assign(ttxRequest.imp[0], { + banner: { + format: [ + { + w: 300, + h: 250 + }, + { + w: 728, + h: 90 + } + ], + ext: { + ttx: { + viewability: { + amount: 100 + } + } + } + } + }); + + return this; + }; + + this.withBannerSizes = this.withSizes = sizes => { Object.assign(ttxRequest.imp[0].banner, { format: sizes }); return this; }; - this.withViewability = viewability => { - Object.assign(ttxRequest.imp[0].banner, { + this.withVideo = (params = {}) => { + Object.assign(ttxRequest.imp[0], { + video: { + w: 300, + h: 250, + placement: 2, + ...params + } + }); + + return this; + }; + + this.withViewability = (viewability, format = 'banner') => { + Object.assign(ttxRequest.imp[0][format], { ext: { ttx: { viewability } } @@ -83,6 +96,18 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withProduct = (prod = PRODUCT_ID) => { + Object.assign(ttxRequest.imp[0], { + ext: { + ttx: { + prod + } + } + }); + + return this; + }; + this.withGdprConsent = (consent, gdpr) => { Object.assign(ttxRequest, { user: { @@ -140,18 +165,31 @@ describe('33acrossBidAdapter:', function () { return this; }; - this.withFormatFloors = floors => { - const format = ttxRequest.imp[0].banner.format.map((fm, i) => { - return Object.assign(fm, { - ext: { - ttx: { - bidfloors: [ floors[i] ] + this.withFloors = this.withFormatFloors = (mediaType, floors) => { + switch (mediaType) { + case 'banner': + const format = ttxRequest.imp[0].banner.format.map((fm, i) => { + return Object.assign(fm, { + ext: { + ttx: { + bidfloors: [ floors[i] ] + } + } + }) + }); + + ttxRequest.imp[0].banner.format = format; + break; + case 'video': + Object.assign(ttxRequest.imp[0].video, { + ext: { + ttx: { + bidfloors: floors + } } - } - }) - }); - - ttxRequest.imp[0].banner.format = format; + }); + break; + } return this; }; @@ -188,6 +226,53 @@ describe('33acrossBidAdapter:', function () { this.build = () => serverRequest; } + function BidRequestsBuilder() { + const bidRequests = [ + { + bidId: 'b1', + bidder: '33across', + bidderRequestId: 'b1a', + params: { + siteId: SITE_ID, + productId: PRODUCT_ID + }, + adUnitCode: 'div-id', + auctionId: 'r1', + mediaTypes: {}, + transactionId: 't1' + } + ]; + + this.withBanner = () => { + bidRequests[0].mediaTypes.banner = { + sizes: [ + [300, 250], + [728, 90] + ] + }; + + return this; + }; + + this.withProduct = (prod) => { + bidRequests[0].params.productId = prod; + + return this; + }; + + this.withVideo = (params) => { + bidRequests[0].mediaTypes.video = { + playerSize: [[300, 250]], + context: 'outstream', + ...params + }; + + return this; + } + + this.build = () => bidRequests; + } + beforeEach(function() { element = { x: 0, @@ -217,24 +302,11 @@ describe('33acrossBidAdapter:', function () { innerHeight: 600 }; - bidRequests = [ - { - bidId: 'b1', - bidder: '33across', - bidderRequestId: 'b1a', - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - }, - adUnitCode: 'div-id', - auctionId: 'r1', - sizes: [ - [300, 250], - [728, 90] - ], - transactionId: 't1' - } - ]; + bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .build() + ); sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1); @@ -248,78 +320,246 @@ describe('33acrossBidAdapter:', function () { }); describe('isBidRequestValid:', function() { - it('returns true when valid bid request is sent', function() { - const validBid = { - bidder: BIDDER_CODE, - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - } - }; + context('basic validation', function() { + it('returns true for valid guid values', function() { + // NOTE: We ignore whitespace at the start and end since + // in our experience these are common typos + const validGUIDs = [ + `${SITE_ID}`, + `${SITE_ID} `, + ` ${SITE_ID}`, + ` ${SITE_ID} ` + ]; - expect(spec.isBidRequestValid(validBid)).to.be.true; - }); + validGUIDs.forEach((siteId) => { + const bid = { + bidder: '33across', + params: { + siteId + } + }; - it('returns true when valid test bid request is sent', function() { - const validBid = { - bidder: BIDDER_CODE, - params: { - siteId: SITE_ID, - productId: PRODUCT_ID, - test: 1 - } - }; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + }); - expect(spec.isBidRequestValid(validBid)).to.be.true; - }); + it('returns false for invalid guid values', function() { + const invalidGUIDs = [ + undefined, + 'siab' + ]; - it('returns false when bidder not set to "33across"', function() { - const invalidBid = { - bidder: 'foo', - params: { - siteId: SITE_ID, - productId: PRODUCT_ID - } - }; + invalidGUIDs.forEach((siteId) => { + const bid = { + bidder: '33across', + params: { + siteId + } + }; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); }); - it('returns false when params not set', function() { - const invalidBid = { - bidder: 'foo' - }; + context('banner validation', function() { + it('returns true when banner mediaType does not exist', function() { + const bid = { + bidder: '33across', + params: { + siteId: 'cxBE0qjUir6iopaKkGJozW' + } + }; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; - }); + expect(spec.isBidRequestValid(bid)).to.be.true; + }); - it('returns false when site ID is not set in params', function() { - const invalidBid = { - bidder: 'foo', - params: { - productId: PRODUCT_ID - } - }; + it('returns true when banner sizes are defined', function() { + const bid = { + bidder: '33across', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + siteId: 'cxBE0qjUir6iopaKkGJozW' + } + }; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; - }); + expect(spec.isBidRequestValid(bid)).to.be.true; + }); - it('returns false when product ID not set in params', function() { - const invalidBid = { - bidder: 'foo', - params: { - siteId: SITE_ID - } - }; + it('returns false when banner sizes are invalid', function() { + const invalidSizes = [ + undefined, + '16:9', + 300, + 'foo' + ]; + + invalidSizes.forEach((sizes) => { + const bid = { + bidder: '33across', + mediaTypes: { + banner: { + sizes + } + }, + params: { + siteId: 'cxBE0qjUir6iopaKkGJozW' + } + }; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); }); + + context('video validation', function() { + beforeEach(function() { + // Basic Valid BidRequest + this.bid = { + bidder: '33across', + mediaTypes: { + video: { + playerSize: [[300, 50]], + context: 'outstream', + mimes: ['foo', 'bar'], + protocols: [1, 2] + } + }, + params: { + siteId: `${SITE_ID}` + } + }; + }); + + it('returns true when video mediaType does not exist', function() { + const bid = { + bidder: '33across', + params: { + siteId: `${SITE_ID}` + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns true when valid video mediaType is defined', function() { + expect(spec.isBidRequestValid(this.bid)).to.be.true; + }); + + it('returns false when video context is not defined', function() { + delete this.bid.mediaTypes.video.context; + + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + + it('returns false when video playserSize is invalid', function() { + const invalidSizes = [ + undefined, + '16:9', + 300, + 'foo' + ]; + + invalidSizes.forEach((playerSize) => { + this.bid.mediaTypes.video.playerSize = playerSize; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + it('returns false when video mimes is invalid', function() { + const invalidMimes = [ + undefined, + 'foo', + 1, + [] + ] + + invalidMimes.forEach((mimes) => { + this.bid.mediaTypes.video.mimes = mimes; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + + it('returns false when video protocols is invalid', function() { + const invalidMimes = [ + undefined, + 'foo', + 1, + [] + ] + + invalidMimes.forEach((protocols) => { + this.bid.mediaTypes.video.protocols = protocols; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + + it('returns false when video placement is invalid', function() { + const invalidPlacement = [ + [], + '1', + {}, + 'foo' + ]; + + invalidPlacement.forEach((placement) => { + this.bid.mediaTypes.video.placement = placement; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + it('returns false when video startdelay is invalid for instream context', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'instream', protocols: [1, 2], mimes: ['foo', 'bar']}) + .build() + ); + + const invalidStartdelay = [ + [], + '1', + {}, + 'foo' + ]; + + invalidStartdelay.forEach((startdelay) => { + bidRequests[0].mediaTypes.video.startdelay = startdelay; + expect(spec.isBidRequestValid(bidRequests[0])).to.be.false; + }); + }); + + it('returns true when video startdelay is invalid for outstream context', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream', protocols: [1, 2], mimes: ['foo', 'bar']}) + .build() + ); + + const invalidStartdelay = [ + [], + '1', + {}, + 'foo' + ]; + + invalidStartdelay.forEach((startdelay) => { + bidRequests[0].mediaTypes.video.startdelay = startdelay; + expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; + }); + }); + }) }); describe('buildRequests:', function() { context('when element is fully in view', function() { it('returns 100', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: 100}) .build(); const serverRequest = new ServerRequestBuilder() @@ -335,6 +575,8 @@ describe('33acrossBidAdapter:', function () { context('when element is out of view', function() { it('returns 0', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: 0}) .build(); const serverRequest = new ServerRequestBuilder() @@ -350,6 +592,8 @@ describe('33acrossBidAdapter:', function () { context('when element is partially in view', function() { it('returns percentage', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: 75}) .build(); const serverRequest = new ServerRequestBuilder() @@ -365,6 +609,8 @@ describe('33acrossBidAdapter:', function () { context('when width or height of the element is zero', function() { it('try to use alternative values', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withSizes([{ w: 800, h: 2400 }]) .withViewability({amount: 25}) .build(); @@ -373,7 +619,7 @@ describe('33acrossBidAdapter:', function () { .build(); Object.assign(element, { width: 0, height: 0 }); - bidRequests[0].sizes = [[800, 2400]]; + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); }); @@ -382,6 +628,8 @@ describe('33acrossBidAdapter:', function () { context('when nested iframes', function() { it('returns \'nm\'', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: spec.NON_MEASURABLE}) .build(); const serverRequest = new ServerRequestBuilder() @@ -402,6 +650,8 @@ describe('33acrossBidAdapter:', function () { context('when tab is inactive', function() { it('returns 0', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withViewability({amount: 0}) .build(); const serverRequest = new ServerRequestBuilder() @@ -432,6 +682,8 @@ describe('33acrossBidAdapter:', function () { it('returns corresponding server requests with gdpr consent data', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withGdprConsent('foobarMyPreference', 1) .build(); const serverRequest = new ServerRequestBuilder() @@ -450,6 +702,8 @@ describe('33acrossBidAdapter:', function () { }); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withGdprConsent('foobarMyPreference', 1) .build(); const serverRequest = new ServerRequestBuilder() @@ -471,6 +725,8 @@ describe('33acrossBidAdapter:', function () { it('returns corresponding server requests with default gdpr consent data', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -488,6 +744,8 @@ describe('33acrossBidAdapter:', function () { }); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -510,6 +768,8 @@ describe('33acrossBidAdapter:', function () { it('returns corresponding server requests with us_privacy consent data', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withUspConsent('foo') .build(); const serverRequest = new ServerRequestBuilder() @@ -528,6 +788,8 @@ describe('33acrossBidAdapter:', function () { }); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withUspConsent('foo') .build(); const serverRequest = new ServerRequestBuilder() @@ -549,6 +811,8 @@ describe('33acrossBidAdapter:', function () { it('returns corresponding server requests with default us_privacy data', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -566,6 +830,8 @@ describe('33acrossBidAdapter:', function () { }); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -586,6 +852,8 @@ describe('33acrossBidAdapter:', function () { }; const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withPageUrl('http://foo.com/bar') .build(); const serverRequest = new ServerRequestBuilder() @@ -605,6 +873,8 @@ describe('33acrossBidAdapter:', function () { }; const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -656,6 +926,8 @@ describe('33acrossBidAdapter:', function () { bidRequests[0].schain = schain; const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withSchain(schain) .build(); const serverRequest = new ServerRequestBuilder() @@ -672,6 +944,8 @@ describe('33acrossBidAdapter:', function () { context('when there no schain object is passed', function() { it('does not set source field', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() @@ -684,9 +958,11 @@ describe('33acrossBidAdapter:', function () { }); }); - context('when price floor module is not enabled in bidRequest', function() { + context('when price floor module is not enabled for banner in bidRequest', function() { it('does not set any bidfloors in ttxRequest', function() { const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -697,11 +973,13 @@ describe('33acrossBidAdapter:', function () { }); }); - context('when price floor module is enabled in bidRequest', function() { + context('when price floor module is enabled for banner in bidRequest', function() { it('does not set any bidfloors in ttxRequest if there is no floor', function() { bidRequests[0].getFloor = () => ({}); const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .build(); const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) @@ -723,9 +1001,188 @@ describe('33acrossBidAdapter:', function () { }; const ttxRequest = new TtxRequestBuilder() - .withFormatFloors([ 1.0, 0.10 ]) + .withBanner() + .withProduct() + .withFormatFloors('banner', [ 1.0, 0.10 ]) + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + + context('when mediaType has video only and context is instream', function() { + it('builds instream request with default params', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'instream'}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('instream') + .build(); + + ttxRequest.imp[0].video.placement = 1; + ttxRequest.imp[0].video.startdelay = 0; + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + + it('builds instream request with params passed', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'instream', startdelay: -2}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo({startdelay: -2, placement: 1}) + .withProduct('instream') + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequests[0].data)).to.deep.equal(ttxRequest); + }); + }); + + context('when mediaType has video only and context is outstream', function() { + it('builds siab request with video only with default params', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream'}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('siab') + .build(); + + ttxRequest.imp[0].video.placement = 2; + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + + it('builds siab request with video params passed', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream', placement: 3, playbackmethod: [2]}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo({placement: 3, playbackmethod: [2]}) + .withProduct('siab') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + + context('when mediaType has banner only', function() { + it('builds default siab request', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct('siab') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + + it('builds default inview request when product is set as such', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .withProduct('inview') + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct('inview') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + }); + + context('when mediaType has banner and video', function() { + it('builds siab request with banner and outstream video', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .withVideo({context: 'outstream'}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withVideo() + .withProduct('siab') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverRequest]); + }); + + it('builds siab request with banner and outstream video even when context is instream', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withBanner() + .withVideo({context: 'instream'}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withVideo() + .withProduct('siab') .build(); + ttxRequest.imp[0].video.placement = 2; + const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); @@ -734,6 +1191,55 @@ describe('33acrossBidAdapter:', function () { expect(builtServerRequests).to.deep.equal([serverRequest]); }); }); + + context('when price floor module is enabled for video in bidRequest', function() { + it('does not set any bidfloors in video if there is no floor', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream'}) + .build() + ); + + bidRequests[0].getFloor = () => ({}); + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct() + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequests[0].data)).to.deep.equal(ttxRequest); + }); + + it('sets bidfloors in video if there is a floor', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream'}) + .build() + ); + + bidRequests[0].getFloor = ({size, currency, mediaType}) => { + const floor = (mediaType === 'video') ? 1.0 : 0.10 + return ( + { + floor, + currency: 'USD' + } + ); + }; + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct() + .withFloors('video', [ 1.0 ]) + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(JSON.parse(builtServerRequests[0].data)).to.deep.equal(ttxRequest); + }); + }); }); describe('interpretResponse', function() { @@ -741,6 +1247,8 @@ describe('33acrossBidAdapter:', function () { beforeEach(function() { ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() .withSite({ id: SITE_ID, page: 'https://test-url.com' @@ -757,7 +1265,7 @@ describe('33acrossBidAdapter:', function () { }); context('when exactly one bid is returned', function() { - it('interprets and returns the single bid response', function() { + it('interprets and returns the single banner bid response', function() { const serverResponse = { cur: 'USD', ext: {}, @@ -784,12 +1292,56 @@ describe('33acrossBidAdapter:', function () { ad: '

I am an ad

', ttl: 60, creativeId: 1, + mediaType: 'banner', currency: 'USD', netRevenue: true }; expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); }); + + it('interprets and returns the single video bid response', function() { + const videoBid = ''; + const serverResponse = { + cur: 'USD', + ext: {}, + id: 'b1', + seatbid: [ + { + bid: [{ + id: '1', + adm: videoBid, + ext: { + ttx: { + mediaType: 'video', + vastType: 'xml' + } + }, + crid: 1, + h: 250, + w: 300, + price: 0.0938 + }] + } + ] + }; + const bidResponse = { + requestId: 'b1', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: videoBid, + ttl: 60, + creativeId: 1, + mediaType: 'video', + currency: 'USD', + netRevenue: true, + vastXml: videoBid + }; + + expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); + }); }); context('when no bids are returned', function() { @@ -852,6 +1404,7 @@ describe('33acrossBidAdapter:', function () { ad: '

I am an ad

', ttl: 60, creativeId: 1, + mediaType: 'banner', currency: 'USD', netRevenue: true }; @@ -886,9 +1439,13 @@ describe('33acrossBidAdapter:', function () { }, adUnitCode: 'div-id', auctionId: 'r1', - sizes: [ - [300, 250] - ], + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, transactionId: 't1' }, { @@ -901,9 +1458,13 @@ describe('33acrossBidAdapter:', function () { }, adUnitCode: 'div-id', auctionId: 'r1', - sizes: [ - [300, 250] - ], + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, transactionId: 't2' } ]; From 014b65f29cfc812e8e3e26a6c5c66939f494cd86 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 4 Nov 2020 14:51:57 -0800 Subject: [PATCH 135/190] Prebid 4.15.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6c78a45c5c..ce8c660a869 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.15.0-pre", + "version": "4.15.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From d3eec3a0c6cb973493e44cffed530a9e137f9a02 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 4 Nov 2020 15:18:15 -0800 Subject: [PATCH 136/190] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce8c660a869..8dc3535cc21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.15.0", + "version": "4.16.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 28473bcf5981ea101270d986bb239b5f924334bc Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Thu, 5 Nov 2020 14:20:29 +0100 Subject: [PATCH 137/190] Improve Digital adapter: eids support (#5935) * Improve Digital adapter: eids support * Fix quotes --- modules/improvedigitalBidAdapter.js | 11 +++++++++++ .../modules/improvedigitalBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 3c000258ede..0e2d8f6f7dd 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -3,6 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; +import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'improvedigital'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -56,6 +57,13 @@ export const spec = { requestParameters.schain = bidRequests[0].schain; + if (bidRequests[0].userId) { + const eids = createEidsArray(bidRequests[0].userId); + if (eids.length) { + utils.deepSetValue(requestParameters, 'user.ext.eids', eids); + } + } + let requestObj = idClient.createRequest( normalizedBids, // requestObject requestParameters @@ -552,6 +560,9 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (requestParameters.schain) { impressionBidRequestObject.schain = requestParameters.schain; } + if (requestParameters.user) { + impressionBidRequestObject.user = requestParameters.user; + } if (extraRequestParameters) { for (let prop in extraRequestParameters) { impressionBidRequestObject[prop] = extraRequestParameters[prop]; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 5a20944a6ed..89ec5aed8c3 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -154,6 +154,8 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); expect(params.bid_request.gdpr).to.not.exist; expect(params.bid_request.us_privacy).to.not.exist; + expect(params.bid_request.schain).to.not.exist; + expect(params.bid_request.user).to.not.exist; expect(params.bid_request.imp).to.deep.equal([ { id: '33e9500b21129f', @@ -345,6 +347,22 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.schain).to.equal(schain); }); + it('should add eids', function () { + const userId = { id5id: { uid: '1111' } }; + const expectedUserObject = { ext: { eids: [{ + source: 'id5-sync.com', + uids: [{ + atype: 1, + id: '1111' + }] + }]}}; + const bidRequest = Object.assign({}, simpleBidRequest); + bidRequest.userId = userId; + const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.user).to.deep.equal(expectedUserObject); + }); + it('should return 2 requests', function () { const requests = spec.buildRequests([ simpleBidRequest, From bb60431114c9bfcd870aee9d41d5942bca865cea Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Thu, 5 Nov 2020 18:16:32 +0200 Subject: [PATCH 138/190] Adkernel: andbeyond alias (#5922) --- modules/adkernelBidAdapter.js | 2 +- modules/andbeyondBidAdapter.md | 32 -------------------- test/spec/modules/adkernelBidAdapter_spec.js | 3 +- 3 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 modules/andbeyondBidAdapter.md diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index d489a1a84e0..0e9093b0f63 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'], + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond'], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** diff --git a/modules/andbeyondBidAdapter.md b/modules/andbeyondBidAdapter.md deleted file mode 100644 index 7d58bac0abc..00000000000 --- a/modules/andbeyondBidAdapter.md +++ /dev/null @@ -1,32 +0,0 @@ -# Overview - -``` -Module Name: andbeyond Bidder Adapter -Module Type: Bidder Adapter -Maintainer: shreyanschopra@rtbdemand.com -``` - -# Description - -Connects to andbeyond whitelabel platform. -Banner formats are supported. - - -# Test Parameters -``` - var adUnits = [ - { - code: 'banner-ad-div', - sizes: [[300, 250]], // banner size - bids: [ - { - bidder: 'andbeyond', - params: { - zoneId: '30164', //required parameter - host: 'cpm.metaadserving.com' //required parameter - } - } - ] - } - ]; -``` diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 1483816d94d..4015a56e82b 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -556,8 +556,7 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(6); - expect(spec.aliases).to.include.members(['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon']); + expect(spec.aliases).to.have.lengthOf(7); }); }); From 9989480505180fd56d8eb5fc4534461d07e10375 Mon Sep 17 00:00:00 2001 From: manisha Date: Fri, 6 Nov 2020 12:47:37 +0530 Subject: [PATCH 139/190] fix to remove redundant validation for datatype for partner value - UOE-5788 --- modules/id5IdSystem.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index f4617afdae5..a2ff8dfe97f 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -58,9 +58,10 @@ export const id5IdSubmodule = { * @returns {IdResponse|undefined} */ getId(configParams, consentData, cacheIdObj) { - if (!hasRequiredParams(configParams)) { + //This check is redundant. Is being handled at line #65. - UOE-5788 + /*if (!hasRequiredParams(configParams)) { return undefined; - } + }*/ if (!configParams || typeof parseInt(configParams.partner) !== 'number') { utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); } From eb05f50caf5e8e12f0aa3bca918903551ca068f6 Mon Sep 17 00:00:00 2001 From: manisha Date: Fri, 6 Nov 2020 14:57:16 +0530 Subject: [PATCH 140/190] fix for UOE-5788 --- modules/id5IdSystem.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index a2ff8dfe97f..3e87c14e50e 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -58,12 +58,13 @@ export const id5IdSubmodule = { * @returns {IdResponse|undefined} */ getId(configParams, consentData, cacheIdObj) { - //This check is redundant. Is being handled at line #65. - UOE-5788 - /*if (!hasRequiredParams(configParams)) { + // This check is redundant. Is being handled at line #65. - UOE-5788 + /* if (!hasRequiredParams(configParams)) { return undefined; - }*/ + } */ if (!configParams || typeof parseInt(configParams.partner) !== 'number') { utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); + return undefined; } configParams.partner = parseInt(configParams.partner) const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; From f9fdc9cab59bdb5b21b875b13eb832e2a035edcb Mon Sep 17 00:00:00 2001 From: lunamedia <73552749+lunamedia@users.noreply.github.com> Date: Sat, 7 Nov 2020 01:19:36 +0200 Subject: [PATCH 141/190] LunamediaHB bid adapter (#5906) --- modules/lunamediahbBidAdapter.js | 107 ++++++ modules/lunamediahbBidAdapter.md | 72 +++++ .../modules/lunamediahbBidAdapter_spec.js | 304 ++++++++++++++++++ 3 files changed, 483 insertions(+) create mode 100644 modules/lunamediahbBidAdapter.js create mode 100644 modules/lunamediahbBidAdapter.md create mode 100644 test/spec/modules/lunamediahbBidAdapter_spec.js diff --git a/modules/lunamediahbBidAdapter.js b/modules/lunamediahbBidAdapter.js new file mode 100644 index 00000000000..00f7a9a056c --- /dev/null +++ b/modules/lunamediahbBidAdapter.js @@ -0,0 +1,107 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'lunamediahb'; +const AD_URL = 'https://balancer.lmgssp.com/?c=o&m=multi'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + + const placements = []; + const request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + }; + const mediaType = bid.mediaTypes + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.sizes = mediaType[BANNER].sizes; + placement.traffic = BANNER; + } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { + placement.wPlayer = mediaType[VIDEO].playerSize[0]; + placement.hPlayer = mediaType[VIDEO].playerSize[1]; + placement.traffic = VIDEO; + } else if (mediaType && mediaType[NATIVE]) { + placement.native = mediaType[NATIVE]; + placement.traffic = NATIVE; + } + placements.push(placement); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, +}; + +registerBidder(spec); diff --git a/modules/lunamediahbBidAdapter.md b/modules/lunamediahbBidAdapter.md new file mode 100644 index 00000000000..184dd846a9d --- /dev/null +++ b/modules/lunamediahbBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: lunamedia Bidder Adapter +Module Type: lunamedia Bidder Adapter +Maintainer: support@lunamedia.io +``` + +# Description + +Module that connects to lunamedia demand sources + +# Test Parameters +``` + var adUnits = [ + { + code:'1', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'lunamediahb', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids:[ + { + bidder: 'lunamediahb', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + native: { + title: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids:[ + { + bidder: 'lunamediahb', + params: { + placementId: 0 + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/lunamediahbBidAdapter_spec.js b/test/spec/modules/lunamediahbBidAdapter_spec.js new file mode 100644 index 00000000000..e9f88935ed5 --- /dev/null +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -0,0 +1,304 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/lunamediahbBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; + +describe('LunamediaHBBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'lunamediahb', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 783, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://balancer.lmgssp.com/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); + expect(placement.placementId).to.equal(783); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns valid data for mediatype native', function () { + const native = { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + }; + + bid.mediaTypes = {}; + bid.params.traffic = NATIVE; + bid.mediaTypes[NATIVE] = native; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain'); + expect(placement.traffic).to.equal(NATIVE); + expect(placement.native).to.equal(native); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From 7efc3330e8e5013dab727a8024c4ce9d6ba1b6ee Mon Sep 17 00:00:00 2001 From: Lyubomir Shishkov <61063794+lyubomirshishkov@users.noreply.github.com> Date: Mon, 9 Nov 2020 10:05:51 +0200 Subject: [PATCH 142/190] Add User ID Targeting to googletag.cmd as a fallback when GPT API is not ready (#5925) * Add User IDs to googletag.cmd The purpose of this change is to allow the userIdTargeting module to function even when googletag has not been defined yet. * Fixing indentation errors Fixing indentation errors thrown by * Fix 'googletag' is not defined errors * Added unit test for userIdTargeting fallback --- modules/userIdTargeting.js | 14 ++++++++------ test/spec/modules/shareUserIds_spec.js | 10 ++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/modules/userIdTargeting.js b/modules/userIdTargeting.js index 3ed8b2a14b5..e15c9ddaca2 100644 --- a/modules/userIdTargeting.js +++ b/modules/userIdTargeting.js @@ -20,14 +20,16 @@ export function userIdTargeting(userIds, config) { if (!SHARE_WITH_GAM) { logInfo(MODULE_NAME + ': Not enabled for ' + GAM); - } - - if (window.googletag && isFn(window.googletag.pubads) && hasOwn(window.googletag.pubads(), 'setTargeting') && isFn(window.googletag.pubads().setTargeting)) { + } else if (window.googletag && isFn(window.googletag.pubads) && hasOwn(window.googletag.pubads(), 'setTargeting') && isFn(window.googletag.pubads().setTargeting)) { GAM_API = window.googletag.pubads().setTargeting; } else { - SHARE_WITH_GAM = false; - logInfo(MODULE_NAME + ': Could not find googletag.pubads().setTargeting API. Not adding User Ids in targeting.') - return; + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + GAM_API = function (key, value) { + window.googletag.cmd.push(function () { + window.googletag.pubads().setTargeting(key, value); + }); + }; } Object.keys(userIds).forEach(function(key) { diff --git a/test/spec/modules/shareUserIds_spec.js b/test/spec/modules/shareUserIds_spec.js index 451892919cb..67e39533fc7 100644 --- a/test/spec/modules/shareUserIds_spec.js +++ b/test/spec/modules/shareUserIds_spec.js @@ -50,6 +50,16 @@ describe('#userIdTargeting', function() { pubads.setTargeting('test', ['TEST']); config.GAM_KEYS.tdid = ''; userIdTargeting(userIds, config); + expect(pubads.getTargeting('tdid')).to.be.an('array').that.is.empty; expect(pubads.getTargeting('test')).to.deep.equal(['TEST']); }); + + it('User Id Targeting is added to googletag queue when GPT is not ready', function() { + let pubads = window.googletag.pubads; + delete window.googletag.pubads; + userIdTargeting(userIds, config); + window.googletag.pubads = pubads; + window.googletag.cmd.map(command => command()); + expect(window.googletag.pubads().getTargeting('TD_ID')).to.deep.equal(['my-tdid']); + }); }); From 83279da21e8d4a8d29b1be7fdbbde354287b6722 Mon Sep 17 00:00:00 2001 From: redaguermas Date: Mon, 9 Nov 2020 10:46:55 -0800 Subject: [PATCH 143/190] No bid version 1.2.9 (#5794) * Enable supplyChain support * Added support for COPPA * rebuilt * Added support for Extended User IDs. Co-authored-by: Reda Guermas --- modules/nobidBidAdapter.js | 21 ++++++- test/spec/modules/nobidBidAdapter_spec.js | 69 +++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 00cb14dc01d..051202cab97 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -6,7 +6,7 @@ import { getStorageManager } from '../src/storageManager.js'; const storage = getStorageManager(); const BIDDER_CODE = 'nobid'; -window.nobidVersion = '1.2.8'; +window.nobidVersion = '1.2.9'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; window.nobid.timeoutTotal = 0; @@ -114,6 +114,23 @@ function nobidBuildRequests(bids, bidderRequest) { utils.logWarn('Could not parse screen dimensions, error details:', e); } } + var getEIDs = function(eids) { + if (utils.isArray(eids) && eids.length > 0) { + let src = []; + eids.forEach((eid) => { + let ids = []; + if (eid.uids) { + eid.uids.forEach(value => { + ids.push({'id': value.id + ''}); + }); + } + if (eid.source && ids.length > 0) { + src.push({source: eid.source, uids: ids}); + } + }); + return src; + } + } var state = {}; state['sid'] = siteId; state['l'] = topLocation(bidderRequest); @@ -131,6 +148,8 @@ function nobidBuildRequests(bids, bidderRequest) { if (sch) state['schain'] = sch; const cop = coppa(); if (cop) state['coppa'] = cop; + const eids = getEIDs(utils.deepAccess(bids, '0.userIdAsEids')); + if (eids && eids.length > 0) state['eids'] = eids; return state; } function newAdunit(adunitObject, adunits) { diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index 346356e7d5b..e67d3b41f1d 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -228,6 +228,75 @@ describe('Nobid Adapter', function () { }); }); + describe('buildRequestsEIDs', function () { + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + let bidRequests = [ + { + 'bidder': 'nobid', + 'params': { + 'siteId': SITE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'userIdAsEids': [ + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'CRITEO_ID', + 'atype': 1 + } + ] + }, + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'ID5_ID', + 'atype': 1 + } + ], + 'ext': { + 'linkType': 0 + } + }, + { + 'source': 'adserver.org', + 'uids': [ + { + 'id': 'TD_ID', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + } + ] + } + ]; + + let bidderRequest = { + refererInfo: {referer: REFERER} + } + + it('should criteo eid', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.sid).to.exist.and.to.equal(2); + expect(payload.eids[0].source).to.exist.and.to.equal('criteo.com'); + expect(payload.eids[0].uids[0].id).to.exist.and.to.equal('CRITEO_ID'); + expect(payload.eids[1].source).to.exist.and.to.equal('id5-sync.com'); + expect(payload.eids[1].uids[0].id).to.exist.and.to.equal('ID5_ID'); + expect(payload.eids[2].source).to.exist.and.to.equal('adserver.org'); + expect(payload.eids[2].uids[0].id).to.exist.and.to.equal('TD_ID'); + }); + }); + describe('buildRequests', function () { const SITE_ID = 2; const REFERER = 'https://www.examplereferer.com'; From 49f0be3bb55b37d5f43881abc4060edc54efc703 Mon Sep 17 00:00:00 2001 From: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Date: Mon, 9 Nov 2020 17:20:08 -0500 Subject: [PATCH 144/190] EMX Adding Schain forwarding (#5946) * adding ccpa support for emx_digital adapter * emx_digital ccpa compliance: lint fix * emx 3.0 compliance update * fix outstream renderer issue, update test spec * refactor formatVideoResponse function to use core-js/find * Add support for schain forwarding Co-authored-by: Nick Colletti Co-authored-by: Nick Colletti Co-authored-by: Kiyoshi Hara Co-authored-by: Dan Bogdan Co-authored-by: Jherez Taylor Co-authored-by: EMXDigital --- modules/emx_digitalBidAdapter.js | 12 +++++++++++ .../modules/emx_digitalBidAdapter_spec.js | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index fa58481548a..72da18d5691 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -156,6 +156,17 @@ export const emxAdapter = { }; } + return emxData; + }, + getSupplyChain: (bidRequests, emxData) => { + if (bidRequests.schain) { + emxData.source = { + ext: { + schain: bidRequests.schain + } + }; + } + return emxData; } }; @@ -237,6 +248,7 @@ export const spec = { }; emxData = emxAdapter.getGdpr(bidderRequest, Object.assign({}, emxData)); + emxData = emxAdapter.getSupplyChain(bidderRequest, Object.assign({}, emxData)); if (bidderRequest && bidderRequest.uspConsent) { emxData.us_privacy = bidderRequest.uspConsent } diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index 138786b9c74..39e56638ece 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -367,6 +367,27 @@ describe('emx_digital Adapter', function () { expect(request.us_privacy).to.exist; expect(request.us_privacy).to.exist.and.to.equal(consentString); }); + + it('should add schain object to request', function() { + const schainBidderRequest = utils.deepClone(bidderRequest); + schainBidderRequest.schain = { + 'complete': 1, + 'ver': '1.0', + 'nodes': [ + { + 'asi': 'testing.com', + 'sid': 'abc', + 'hp': 1 + } + ] + }; + let request = spec.buildRequests(schainBidderRequest.bids, schainBidderRequest); + request = JSON.parse(request.data); + expect(request.source.ext.schain).to.exist; + expect(request.source.ext.schain).to.have.property('complete', 1); + expect(request.source.ext.schain).to.have.property('ver', '1.0'); + expect(request.source.ext.schain.nodes[0].asi).to.equal(schainBidderRequest.schain.nodes[0].asi); + }); }); describe('interpretResponse', function () { From 0a6b3229f6c70b2f8c622a2689f73f58f6de4430 Mon Sep 17 00:00:00 2001 From: Meng <5110935+edmonl@users.noreply.github.com> Date: Mon, 9 Nov 2020 17:21:24 -0500 Subject: [PATCH 145/190] pubGENIUS bid adapter: fix bug that requestBids timeout is not respected (#5940) * fix requestBids timeout * fix pubgenius bid adapter test --- modules/pubgeniusBidAdapter.js | 2 +- test/spec/modules/pubgeniusBidAdapter_spec.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 05f18f99a9a..55f50e4b6a9 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -37,7 +37,7 @@ export const spec = { const data = { id: bidderRequest.auctionId, imp: bidRequests.map(buildImp), - tmax: config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, ext: { pbadapter: { version: BIDDER_VERSION, diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 52f2e3aeefe..4b5bf7efac0 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -122,6 +122,7 @@ describe('pubGENIUS adapter', () => { bidderCode: 'pubgenius', bidderRequestId: 'fakebidderrequestid', refererInfo: {}, + timeout: 1200, }; expectedRequest = { @@ -149,7 +150,7 @@ describe('pubGENIUS adapter', () => { }; config.setConfig({ - bidderTimeout: 1200, + bidderTimeout: 1000, pageUrl: undefined, coppa: undefined, }); From ffc62100a173946b01d4c73a9e7d0424901f8637 Mon Sep 17 00:00:00 2001 From: rimaburder-index <55195208+rimaburder-index@users.noreply.github.com> Date: Mon, 9 Nov 2020 17:21:50 -0500 Subject: [PATCH 146/190] Updated the text in line 292 (#5937) Updated the text in line 292 --- modules/ixBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index c2e308870db..5b9903c91d2 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -289,7 +289,7 @@ pbjs.setConfig({ }); ``` #### The **detectMissingSizes** feature -With a recent update, the IX bid adapter bids on all banner sizes available in an ad unit, if IX is configured for at least one banner size in that ad unit. This default behavior if not required, can be turned off by using the `detectMissingSizes` flag. +By default, the IX bidding adapter bids on all banner sizes available in the ad unit when configured to at least one banner size. If you want the IX bidding adapter to only bid on the banner size it’s configured to, switch off this feature using `detectMissingSizes`. ``` pbjs.setConfig({ ix: { From 7070b1f78b3c0a49bab4a3bfb5fadc2886e92612 Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Tue, 10 Nov 2020 05:22:43 +0700 Subject: [PATCH 147/190] Update for Qwarry bid adapter (#5936) * 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 Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 3 ++- test/spec/modules/qwarryBidAdapter_spec.js | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index 36c1562324a..0c6dee9be69 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -19,7 +19,8 @@ export const spec = { validBidRequests.forEach(bidRequest => { bids.push({ bidId: bidRequest.bidId, - zoneToken: bidRequest.params.zoneToken + zoneToken: bidRequest.params.zoneToken, + pos: bidRequest.params.pos }) }) diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index a5bb438f384..02fe9c4538b 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -6,7 +6,8 @@ const REQUEST = { 'bidId': '456', 'bidder': 'qwarry', 'params': { - zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f' + zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', + pos: 7 } } @@ -70,7 +71,7 @@ describe('qwarryBidAdapter', function () { 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.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f' }) + 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') expect(bidderRequest.url).to.equal(ENDPOINT) From bbdeae3e2a1f260380d4037d937336586e3476b4 Mon Sep 17 00:00:00 2001 From: manisha Date: Tue, 10 Nov 2020 08:54:01 +0530 Subject: [PATCH 148/190] moved changes for UOE-5788 in hasRequiredParams function --- modules/id5IdSystem.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 3e87c14e50e..f5dab06bd61 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -58,14 +58,14 @@ export const id5IdSubmodule = { * @returns {IdResponse|undefined} */ getId(configParams, consentData, cacheIdObj) { - // This check is redundant. Is being handled at line #65. - UOE-5788 - /* if (!hasRequiredParams(configParams)) { + if (!hasRequiredParams(configParams)) { return undefined; - } */ - if (!configParams || typeof parseInt(configParams.partner) !== 'number') { + } + //This check is redundant. moving it to the 'hasRequiredParams function instead' - UOE-5788 + /*if (!configParams || typeof parseInt(configParams.partner) !== 'number') { utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); return undefined; - } + }*/ configParams.partner = parseInt(configParams.partner) const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; const gdprConsentString = hasGdpr ? consentData.consentString : ''; @@ -127,8 +127,8 @@ export const id5IdSubmodule = { }; function hasRequiredParams(configParams) { - if (!configParams || typeof configParams.partner !== 'number') { - utils.logError(`User ID - ID5 submodule requires partner to be defined as a number`); + if (!configParams || typeof parseInt(configParams.partner) !== 'number' || isNaN(parseInt(configParams.partner))) { + utils.logError(`User ID - ID5 submodule requires partner to be defined as a number - Current value received: ` + configParams.partner); return false; } return true; From 9676ce0e16c668fd324cbba62ea52864c728d18f Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 10 Nov 2020 11:35:08 +0100 Subject: [PATCH 149/190] Adagio Bid Adapter: support UserId's (#5938) --- modules/adagioBidAdapter.js | 13 +++++- test/spec/modules/adagioBidAdapter_spec.js | 48 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 65a35284d1b..b20f832fd42 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -7,10 +7,11 @@ import JSEncrypt from 'jsencrypt/bin/jsencrypt.js'; 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'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.4.0'; +export const VERSION = '2.5.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; export const SUPPORTED_MEDIA_TYPES = ['banner']; @@ -572,6 +573,12 @@ function _getSchain(bidRequest) { } } +function _getEids(bidRequest) { + if (utils.deepAccess(bidRequest, 'userId')) { + return createEidsArray(bidRequest.userId) + } +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -657,6 +664,7 @@ export const spec = { const uspConsent = _getUspConsent(bidderRequest) || {}; const coppa = _getCoppa(); const schain = _getSchain(validBidRequests[0]); + const eids = _getEids(validBidRequests[0]) || []; const adUnits = utils._map(validBidRequests, (bidRequest) => { bidRequest.features = internal.getFeatures(bidRequest, bidderRequest); return bidRequest; @@ -691,6 +699,9 @@ export const spec = { ccpa: uspConsent }, schain: schain, + user: { + eids: eids + }, prebidVersion: '$prebid.version$', adapterVersion: VERSION, featuresVersion: FEATURES_VERSION diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index a18cd797d68..86fb2e7cbd3 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -287,6 +287,7 @@ describe('Adagio bid adapter', () => { 'pageviewId', 'adUnits', 'regs', + 'user', 'schain', 'prebidVersion', 'adapterVersion', @@ -573,6 +574,53 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.regs.ccpa).to.be.empty; }); }); + + describe('with userID modules', function() { + const userId = { + sharedid: {id: '01EAJWWNEPN3CYMM5N8M5VXY22', third: '01EAJWWNEPN3CYMM5N8M5VXY22'}, + unsuported: '666' + } + + it('should send "user.eids" in the request for Prebid.js supported modules only', function() { + const bid01 = new BidRequestBuilder({ + userId + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + const expected = [{ + source: 'sharedid.org', + uids: [ + { + atype: 1, + ext: { + third: '01EAJWWNEPN3CYMM5N8M5VXY22' + }, + id: '01EAJWWNEPN3CYMM5N8M5VXY22' + } + ] + }] + + expect(requests[0].data.user.eids).to.have.lengthOf(1) + expect(requests[0].data.user.eids).to.deep.equal(expected) + }) + + it('should send an empty "user.eids" array in the request if userId module is unsupported', function() { + const bid01 = new BidRequestBuilder({ + userId: { + unsuported: '666' + } + }).withParams().build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.user.eids).to.be.empty + }) + }) }); describe('interpretResponse()', function() { From 690709bc13b9b712630bde1fd63ca980846e955a Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Tue, 10 Nov 2020 11:41:28 +0100 Subject: [PATCH 150/190] userId module: fix auctionDelay submodules with callbacks (#5891) * clearTimeout only after all submodules are done * check that setTimeout function was not cleared --- modules/userId/index.js | 9 +++++++-- test/spec/modules/userId_spec.js | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index a5e5fd4eff1..9ef4da0f96f 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -319,7 +319,13 @@ function hasGDPRConsent(consentData) { * @param {function} cb - callback for after processing is done. */ function processSubmoduleCallbacks(submodules, cb) { - const done = cb ? utils.delayExecution(cb, submodules.length) : function () { }; + let done = () => {}; + if (cb) { + done = utils.delayExecution(() => { + clearTimeout(timeoutID); + cb(); + }, submodules.length); + } submodules.forEach(function (submodule) { submodule.callback(function callbackCompleted(idObj) { // if valid, id data should be saved to cookie/html storage @@ -338,7 +344,6 @@ function processSubmoduleCallbacks(submodules, cb) { // clear callback, this prop is used to test if all submodule callbacks are complete below submodule.callback = undefined; }); - clearTimeout(timeoutID); } /** diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index c5ab2e249fc..d5ed96a5bc1 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -611,6 +611,7 @@ describe('User ID', function () { beforeEach(function () { sandbox = sinon.createSandbox(); sandbox.stub(global, 'setTimeout').returns(2); + sandbox.stub(global, 'clearTimeout'); sandbox.stub(events, 'on'); sandbox.stub(coreStorage, 'getCookie'); @@ -662,6 +663,7 @@ describe('User ID', function () { requestBidsHook(auctionSpy, {adUnits}); // check auction was delayed + global.clearTimeout.calledOnce.should.equal(false); global.setTimeout.calledOnce.should.equal(true); global.setTimeout.calledWith(sinon.match.func, 33); auctionSpy.calledOnce.should.equal(false); @@ -696,6 +698,7 @@ describe('User ID', function () { // check auction was delayed // global.setTimeout.calledOnce.should.equal(true); + global.clearTimeout.calledOnce.should.equal(false); global.setTimeout.calledWith(sinon.match.func, 33); auctionSpy.calledOnce.should.equal(false); From fee75c7322d3e6064bd2c9a3220751503d539c97 Mon Sep 17 00:00:00 2001 From: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Date: Tue, 10 Nov 2020 17:00:01 +0530 Subject: [PATCH 151/190] fix circle ci failing lint error (#5952) --- modules/lunamediahbBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/lunamediahbBidAdapter.js b/modules/lunamediahbBidAdapter.js index 00f7a9a056c..1376d0c1714 100644 --- a/modules/lunamediahbBidAdapter.js +++ b/modules/lunamediahbBidAdapter.js @@ -1,4 +1,4 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; @@ -70,7 +70,7 @@ export const spec = { schain: bid.schain || {}, }; const mediaType = bid.mediaTypes - + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { placement.sizes = mediaType[BANNER].sizes; placement.traffic = BANNER; From 1bbe837f764cce8da6559dc259f849c63fb9704b Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 10 Nov 2020 11:23:38 -0500 Subject: [PATCH 152/190] PR-Review process: fleshing out RTD review (#5948) * PR-Review process: fleshing out RTD review * align bidrequest attribute --- PR_REVIEW.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/PR_REVIEW.md b/PR_REVIEW.md index a4bcc43b11a..f991a0254f5 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -89,9 +89,14 @@ Documentation they're supposed to be following is https://docs.prebid.org/dev-do Follow steps above for general review process. In addition: - The RTD Provider must include a `providerRtdProvider.md` file. This file must have example parameters and document a sense of what to expect: what should change in the bidrequest, or what targeting data should be added? - Try running the new sub-module and confirm the provided test parameters. -- Make sure the sub-module is making HTTP requests as early as possible, but not more often than needed. +- Confirm that the module + - is not loading external code. If it is, escalate to the #prebid-js Slack channel. + - is reading `config` from the function signature rather than calling `getConfig`. + - is sending data to the bid request only as either First Party Data or in bidRequest.rtd.RTDPROVIDERCODE. + - is making HTTPS requests as early as possible, but not more often than needed. + - doesn't force bid adapters to load additional code. - Consider whether the kind of data the module is obtaining could have privacy implications. If so, make sure they're utilizing the `consent` data passed to them. -- make sure there's a docs pull request +- Make sure there's a docs pull request ## Ticket Coordinator From 0de9caf66128a42b68f7da514707a238305c5a4a Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Tue, 10 Nov 2020 18:29:26 +0100 Subject: [PATCH 153/190] delete pubcommon test cookie for domainOverride after writing it in all cases (#5943) * delete pubcommon test cookie after writing it in all cases, not just when it is found again * fix lunamediahbBidAdapter lint issue * call domainOverride only when needed in the module, not ahead of time when the module is registered. --- modules/pubCommonIdSystem.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index 51b4335fe60..919e735d34e 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -37,7 +37,7 @@ function storeData(config, value) { if (config.storage.type === COOKIE) { if (storage.cookiesAreEnabled()) { - storage.setCookie(key, value, expiresStr, 'LAX', COOKIE_DOMAIN); + storage.setCookie(key, value, expiresStr, 'LAX', pubCommonIdSubmodule.domainOverride()); } } else if (config.storage.type === LOCAL_STORAGE) { if (storage.hasLocalStorage()) { @@ -282,16 +282,19 @@ export const pubCommonIdSubmodule = { domainOverride: function () { const domainElements = document.domain.split('.'); const cookieName = `_gd${Date.now()}`; - for (let i = 0, topDomain; i < domainElements.length; i++) { + for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { const nextDomain = domainElements.slice(i).join('.'); // write test cookie storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); // read test cookie to verify domain was valid - if (storage.getCookie(cookieName) === '1') { - // delete test cookie - storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + testCookie = storage.getCookie(cookieName); + + // delete test cookie + storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + + if (testCookie === '1') { // cookie was written successfully using test domain so the topDomain is updated topDomain = nextDomain; } else { @@ -302,6 +305,4 @@ export const pubCommonIdSubmodule = { } }; -const COOKIE_DOMAIN = pubCommonIdSubmodule.domainOverride(); - submodule('userId', pubCommonIdSubmodule); From e065e87da9c958468fb692bfdba348dbc0ff67af Mon Sep 17 00:00:00 2001 From: Salomon Rada Date: Wed, 11 Nov 2020 00:03:07 +0200 Subject: [PATCH 154/190] Gamoshi - Add new alias (#5895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add logic to prefer prebid modules over external modules in build process (#4124) * add check in getModules helper function * update logic based on feedback * update node version of project * Improve Digital adapter: adding bid floor, referrer, more native fields (#4103) * Bid floor, https, native ad update * Update the ad server protocol module * Adding referrer * YIELDONE adapter - change urls to adapt https (#4139) * update: change urls to adapt https * fix test code * Added SupplyChain Object support and an onTimeout Callback (#4137) * - Implemented the 'onTimeout' callback to fire a pixel when there's a timeout. - Added the ability to serialize an schain object according to the description provided here: https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md * some mods to the schain tag generation * - added tests for schain param checking. * - fixed a malformed url for timeouts * - Removed a trailing ',' while generating a schain param. * Revert "Added SupplyChain Object support and an onTimeout Callback (#4137)" This reverts commit e61b246b45bd2c2390350eaeca693f208b1a3a24. This commit doesn't use the schain module added in #4084 * Nobid Prebid Adapter commit (#4050) * Nobid Prebid Adapter commit * Fixed global replace and unit tests * Fixed find function * Added nobidBidAdapter.md * Removed description and added "Bid Params" section. * Added test siteId 2 for testing. * Refactored the Adapter to remove most references to the nobid object. We still need the nobid object because we have a passback tag in DFP that makes reference to it. * Fix concurrent responses on the page * Cosmetic change to log an error in case of missing ad markup * Keep nobid.bidResponses cross adapters. * Added GDPR support in user sync and added test coverage. gulp test-coverage gulp view-coverage * Padding issues * Fix padding issues * Fix padding * update outstream prod url (#4104) * support pubcid and uids (#4143) * Fix misspelling and minor cleanup of schain docs (#4150) * Prebid 2.31.0 Release * Increment pre version * Rubicon: tuning logged messages (#4157) * Rubicon: tuning logged messages * Update rubiconBidAdapter.js * fixed indentation * Rubicon Video COPPA fix (#4155) * Rubicon Video COPPA fix * Unit test for Rubicon Video COPPA fix * Playground XYZ adapter - iframe usersync bug fix (#4141) * corrected user sync type * removed support for iframe usersync * added unit tests for getUserSyncs * update nvmrc file (#4162) * update gulp-footer package (#4160) * Datablocks bid/analytics adapter (#4128) * add datablocks Analytics and Bidder Adapters * remove preload param * remove preloadid * better coverage of tests * better coverage * IE doesn't support array.find * lint test * update example host * native asset id should be integer * update logic of ad_types field in appnexusBidAdapter (#4065) * Shorten SomoAudience to just Somo (#4163) * Shorten SomoAudience to just Somo * Make package-lock return * Quantcast: Fix for empty video parameters (#4145) * Copy params from bid.params.video. * Added test for missing video parameters. * Include mimes from adunit. * One Video adding Rewarded Video Feature (#4142) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * Module to pass User Ids to DFP (#4140) * first commit * renamed * minor doc change * documentation * small change * EB * removed unused imports * minor changes * reanmaed a const * adding more methods to test shareUserIds module * unit tets cases for shareUserIds * indentation * renamed DFP to GAM * renamed shareUserIds to userIdTargeting * Update userIdTargeting.md * trying to restart CI * digitrust userId case handled * minor comment change * using auctionEnd event instead of requestBids.before * using events.on * Buzzoola bid adapter (#4127) * initial commit for buzzoola adapter * leave only banners for now * fix bid validation * change endpoint url * add video type * restore renderer * fix renderer * add fixed player sizes * switch bids * convert dimentions to strings * write tests * 100% tests * remove new DOM element creation in tests * handle empty response from server * change description * E2e tests for Native and Outstream video Ad formats. (#4116) * reorganize e2e/ tests into separate directories * new test page for e2e-banner testing * add test to check if Banner Ad is getting loaded * change location of the spec files to reflect change in test/e2e directory structure * add test case to check for generation of valid targeting keys * create Native Ad test page * add test case to check validity of the targeting keys and correct rendering of the Ad * update old browser versions to new * update browser version * update title * remove console.log statements * add basic functional test for e2e outstream video ad format * Update LockerDome adUnitId bid param (#4176) This is not a breaking change * fix several issues in appnexus video bids (#4154) * S2s testing disable client side (#4123) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * New testServerOnly flag * Tests and a bug fix * Removed dead code * Fixes requested in review * Check each adUnit * isTestingServerOnly changes per Eric * Fixed IE 11 bug * More tests * improved test case names * New option to Include deal KVPs when enableSendAllBids === false (#4136) * new option to include KVPs which have deals when enableSendAllBids === false * updating tests to be more realistic * Prebid 2.32.0 Release * increment pre version * Rubicon doc: changing video test zone (#4187) * added schain support to sonobi adapter (#4173) * if schain config is not defined then error should not be thrown (#4165) * if schain config is not defiend then error should not be thrown * relaxed mode nodes param not defined error handled * added test cases for config validation * a curly bracket was missing in the example * Rubicon: updating test params (#4190) * myTargetBidAdapter: support currency config (#4188) * Update README.md (#4193) * Update README.md * Update README.md * cedato bid adapter instream video support (#4153) * Added adxpremium prebid analytics adapter (#4181) * feat(OAFLO-186): added support for schain (#4194) * Sonobi - send entire userid payload (#4196) * added userid param to pass the entire userId payload to sonobis bid request endpoint * removed console log git p * fixed lint * OpenX Adapter fix: updating outdated video examples (#4198) * userId - Add support for refreshing the cached user id (#4082) * [userId] Added support for refreshing the cached user id: refreshInSeconds storage parameter, related tests and implementation in id5 module * [userId] Added support for refreshing the cached user id: refreshInSeconds storage parameter, related tests and implementation in id5 module * UserId - ID5 - Updated doc with new contact point for partners * UserId - Merged getStoredValue and getStoredDate * [UserId] - ID5 - Moved back ID5 in ./modules * UserId - ID5 - Fixed incorrect GDPR condition * [UserId] - Doc update and test cleanup * Prebid 2.33.0 Release * Increment pre version * SupplyChainObject support and fires a pixel onTimeout (#4152) * - Implemented the 'onTimeout' callback to fire a pixel when there's a timeout. - Added the ability to serialize an schain object according to the description provided here: https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md * some mods to the schain tag generation * - added tests for schain param checking. * - fixed a malformed url for timeouts * - Removed a trailing ',' while generating a schain param. * - Using the schain object from validBidRequest if present. Reverting to checking if params has it if not. * - reverting changes to merge with master * - Resolving merge issues * Feature/add profile parameter (#4185) * Add optional profile parameter * EMXDigital Bid Adapter: Add video dimensions in request (#4174) * addressed feedback from #3731 ticket * removed commented code from emx test spec * logging removed from spec * flip h & w values from playerSize for video requests * adding Outstream mediaType to EMX Digital * adding device info. update to grab video param. styling changes. * add video dimensions from playerSize * fix test for video dimensions * Added keywords parameter support in TrustX Bid Adapter (#4183) * Add trustx adapter and tests for it * update integration example * Update trustx adapter * Post-review fixes of Trustx adapter * Code improvement for trustx adapter: changed default price type from gross to net * Update TrustX adapter to support the 1.0 version * Make requested changes for TrustX adapter * Updated markdown file for TrustX adapter * Fix TrustX adapter and spec file * Update TrustX adapter: r parameter was added to ad request as cache buster * Add support of gdpr to Trustx Bid Adapter * Add wtimeout to ad request params for TrustX Bid Adapter * TrustX Bid Adapter: remove last ampersand in the ad request * Update TrustX Bid Adapter to support identical uids in parameters * Update TrustX Bid Adapter to ignore bids that sizes do not match the size of the request * Update TrustX Bid Adapter to support instream and outstream video * Added wrapperType and wrapperVersion parameters in ad request for TrustX Bid Adapter * Update TrustX Bid Adapter to use refererInfo instead depricated function utils.getTopWindowUrl * HOTFIX for referrer encodind in TrustX Bid Adapter * Fix test for TrustX Bid Adapter * TrustX Bid Adapter: added keywords passing support * rubicon: avoid passing unknown position (#4207) * rubicon: not passing pos if not specified * added comment * not sending pos for video when undefined * cleaning up test * fixed unit test * correctly reference bidrequest and determine mediatype of bidresponse (#4204) * GumGum: only send gdprConsent when found (#4205) * adds digitrust module, mods gdpr from bool to int * update unit test * only send gdprconsent if present * LKQD: Use refererInfo.referer as fallback pageurl (#4210) * Refactored URL query parameter passthrough for additional values, changed SSP endpoint to v.lkqd.net, and updated associated unit tests * Use refererInfo.referer as fallback pageurl * Removed logs and testing values * [UserId] - ID5 - Fixed case when consentData is undefined (No CMP) (#4215) * create stubs for localStorage in widespaceBidAdapter test file (#4208) * added adId property to adRenderFailed event (#4097) When no bid (therefore no adUnitCode) is available in the adRenderFailed event it can be difficult to identify the erroring slot.But in almost all cases the given slot still has the adId targeting. * OpenX Adapter: Forcing https requests and adding UserID module support for LiveRamp and TTD (#4182) * OpenX Adapter: Updated requests to force https * OpenX Adapter: Added support for TTD's UnifiedID and LiveRamp's IDL * PubMatic to support userId sub-modules (#4191) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * TripleLift support for UnifiedId and IdentityLink (#4197) * Add IdentityLink support and fix UnifiedId. It appears we've been looking for UnifiedId userIds on the bidderRequest object, when they are found on bidRequests. This commit fixes that error, and adds support for IdentityLink. * change maintainer email to group * Added lemma adapter (#4126) * lemmaBidAdapter.js Added lemma bid adapter file * lemmaBidAdapter.md Added lemma bid adapter md file * lemmaBidAdapter_spec.js Added lemma bid adapter test spec file * Update lemmaBidAdapter.js Fixed automated code review alert comparison between inconvertible types * Update lemmaBidAdapter.js Fixed review changes * Update lemmaBidAdapter.md Correct parameter value. * Adkernel adapter new alias (#4221) * Force https scheme for Criteo Bidder (#4227) * assign adapter version number * Ensure that Criteo's bidder is always called through https * Add Video Support for Datablocks Bid Adapter (#4195) * add datablocks Analytics and Bidder Adapters * remove preload param * remove preloadid * better coverage of tests * better coverage * IE doesn't support array.find * lint test * update example host * native asset id should be integer * add datablocks Video * remove isInteger * skip if empty * update adUnit, bidRequest and bidResponse object (#4180) * update adUnit, bidRequest and bidResponse object * add test for mediaTypes object * 3 display banner and video vast support for rads (#4209) * add stv adapter * remove comments from adapter file * start rads adapter * fix adapter and tests * fixes * fix adapter and doc * fix adapter * fix tests * little fix * add ip param * fix dev url * #3 radsBidAdapter.md * #3 radsBidAdapter.md: cleanup * fix code and doc * UserId - Add SameSite and server-side pubcid support (#3869) * Add SameSite and server-side pubcid support * Fix emoteevBidAdapter unit test * added schain to appnexus bid adapter (#4229) * added schain to appnexus bid adapter * semicolon * update doubleclick url (#4179) * Prebid 2.34.0 release * increment pre version * Rubi Analytics handles > 1 bidResponse per bidRequest (#4224) * videoNow bid adapter (#4088) * -- first commit * -- cors and bidder's name fixed * -- almost ready * -- added docs * -- added nurl tracking * -- bid params * -- tests added * -- test fixed * -- replace placeholder in the onBidWon pixel's url * -- commit for restart tests * -- change response data format for display ad * -- tests updated * -- 100% tests coverage * -- a few clean the test's code * -- custom urls from localStorage * -- tests updated * -- a few clean the test's code * -- new init model * -- spec for new init model * -- fix for new init model * -- code cleaned * -- 100% tests coverage * -- 100% tests coverage * -- fixed test * -- commit for restart tests * djax new bidder adapter (#4192) * djax bidder adapter * djax bidder adapter * Update hello_world.html * Added Turk Telekom Bid Adapter (#4203) * Added Turk Telekom Bid Adapter * Fix md file for Turk Telekom Bid Adapter * MicroAd: Use HTTPS in all requests (#4220) * Always use HTTPS endpoint in MicroAd * Update code * Fixed a broken test in MicroAd * Schain: avoiding Object.values as it is breaking on IE11 (#4238) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * avoiding use of Object.values * 3952 delay auction for ids (#4115) * 3952 delay auction for user ids * 3952 add integration example * 3952 add tests * 3952 fix html example * add todos * 3952 continue auction if ids received * 3952 add tests for auction delay * increase test coverage * set config for test * remove todo * add a few more checks to tests * add comment, force tests to rerun * Feature: adUnitBidLimit (#3906) * added new feature to config to limit bids when sendallbids is enabled * cleaned up code. removed extra spaces etc * removed trailing spaces in config * remove .flat() and replaced with spread operator * removed flat function and instead pushing using spread operator * updated to use sendBidsControl instead * updated targeting_spec to test bidLimit * removed trailing spaces from targeting_spec * Update Rubicon Adapter netRevenue default (#4242) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Removed AdastaMadia from alias (#4255) * Update appnexusBidAdapter.js (#4251) * IdentityLink - change expiration time to 30 days (#4239) * Add coppa support for AppNexus adapter (#4253) * Add coppa support for AppNexus adapter * test name * add new longform e2e tests (#4206) * Konduit module (#4184) * Adding Konduit module * Removed superfluous arguments passed to obtainVastUrl function * Removed superfluous arguments passed to obtainVastUrl function. * Build trigger (empty commit) * Module documentation updated according to the comments * Logic in obtainVastUrl function updated according to the review comment. * Removed hook, enabled eslint * Circle CI runs e2e tests on every push (#4200) * run functional tests on circle ci on push to any remote branch * remove extraneous key from config file * add test.localhost as alias to 127.0.0.1 * check 0: execute circle-ci * move /etc/config to a separate command * change bid partner to rubicon * test appnexus bid adapter in ci * comment browserstack command * remove console.log statement * test1: circle-ci * change reference dev -> prod while loading prebid * add console.log statement * check-2: circle-ci * comment browserstack testing * change bid adapter * change bid adapter * remove test case for checking targeting keys * remove the ci flag * uncomment test for checking correct generation of targeting keys * swap AN -> Rubicon for testing targeting keys * Outcon bid adapter. (#4161) * Outcon bid adapter. * Fix identation * Fixes * Fixes * Fixes * Spec fixes * Fixes * Fix urls * Fix * Fix parameters * Fix space operators * Fix bidder timeout * Update * Fix whitespace * no message * Outcon unit test * no message * no message * no message * no message * Fixes * Fixes * Change url * no message * no message * no message * Added bidId * no message * no message * no message * no message * Wrapping url with html * no message * no message * no message * Adding workflow to run end to end tests (#4230) * Adding workflow to run end to end tests * trying self branch * Update to run at 12 every day * cleanup config using aliases * update branch and cron time * add command * update prebid path for e2e test pages (#4274) * Prebid 2.35.0 release * Increment pre version * Add usersync to adpone adapter (#4245) * add user sync to adpone adapter * move adpone usersync to global variable * added withcredentials to http request * fix http request options * fix http request options * add withCredentials: true * add withCredentials: true * added test coverage to usersync * update sync function * add test coverage * adpone adapter * package lock * add more testing * add more testing * testing for onBidWon fucntion * test onbidwon function * trigger build * Revert GumGum Adapter 2.28 resizing changes (#4277) * changed resizing unit tests to return the first size dimensions in the sizes array * added some changes * reverted adapter changes * SpotX Bid Adapter: Support schain, ID5 object, Google consent object, and hide_skin (#4281) * Add SpotXBidAdapter * Minor updates * Undo testing changes to shared files * Fix relative imports * Remove superfluous imports and write a few more tests * Formatting, ID5 object, Google consent objects - Added ID5 object support - Added Google Consent object - Reformatted indentaiton on spec file * Revert content_width and content_height changes in docs - not sure how these got moved, lets put them back * Remove click_to_replay flag in example - no reason to use this one in the example * Spotx adapter - Add schain support and update unit tests * Update schain path in ORTB 2.3 request body - schain object is now added to ortb request body at request.ext.source.ext.schain * Add hide_skin to documentation - whoops, this got removed, let's add it back * Update Rubicon Analytics Adapter `bidId` to match PBS (#4156) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update for rubicon analytics to send seat[].bid.id for PBS video and banner * fixed conditional for server and video or banner * updated with optimized value test for bidid * update changed default value of netRevenue to true * remove var declaration for rightSlot to correct lgtm error for unused variable * update defineSlot div id to match div id defined in html body * update test ad unit test props * revert lock to match remote master * add seatBidId to bidObj in rpBidAdapter interpretResponse * update setTargeting to execute in the bids back handler * remove dev integration test page * meaningless commit to get lgtm to re-run * SmartRTB adapter update (#4246) * modules: Implement SmartRTB adapter and spec. * Fix for-loop syntax to support IE; refactor getDomain out of exported set. * Remove debugs, update doc * Update test for video support * Handle missing syncs. Add video to media types in sample ad unit * Add null response check, update primary endpoint * Note smrtb video requires renderer * Support Vast Track (#4276) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Add parameters if config.cache.vasttrack is true * Use requestId instead of adId * Test new vasttrack payload params * Removed commented out code * Relaxed conditional check per review * Removed commented out line * Added 1000x250 size (#4295) * prepare vidazoo adapter for v3.0 (#4291) * Improve Digital adapter: support schain (#4286) * LiveIntent Identity Module. (#4178) * LiveIntentIdSystem. Initial implementation. * LiveIntentIdSystem. Removed whitespace. * Fixed typo * Renamed variables, cookiesm added md. * Changed the default identity url. * Composite id, with having more than just the lipbid passed around. * Composite id. * Merge conflict resolution. * Changed docs and param description. * Added typedoc & mentioned liveIntentIdSystem in submodule.json. * Extracted the LiveIntentIdSystem under modules, removed it from default userId modules. * Fixing the 204 + no body scenario. * Added liveIntent to submodule.json * Fixing docs indentation. * Updated prebidServer & specs. * Minor specs update. * updating liveintent eids source (#4300) * updating liveintent eids source these are supposed to be domains * updating unit test * fix appnexusBidAdapter view-script regex (#4289) * fix an view script regex * minor syntax update * 33Across adding bidder specific extension field (#4298) * - add 33across specific ext field for statedAt * - fix unit test for 33Across adapter * PubMatic to support LiveIntent User Id sub-module (#4306) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * supporting LiveIntent Id in PubMatic adapter * updated source for liveintent * Finteza Analytics Adapter: fix cookies (#4292) * fix reading and sending cookies * fix lint errors * clear comments * add unit tests * fix calling of setCookies for IE * clear cookies after test * use own setCookie method inside tests * Update LockerDome adapter to support Prebid 3.0 (#4301) * Returning the `IdResponse` type with an obj + callback. Fix for 4304 (#4305) * Returning the `IdResponse` type with an obj + callback. * Renamed resp -> result. * Removed whitespace. * ShowHeroes adapter - expanded outstream support (#4222) * add ShowHeroes Adapter * ShowHeroes adapter - expanded outstream support * Revert "ShowHeroes adapter - expanded outstream support" This reverts commit bfcdb913b52012b5afbf95a84956b906518a4b51. * ShowHeroes adapter - expanded outstream support * ShowHeroes adapter - fixes (#4222) * ShowHeroes adapter - banner and outstream fixes (#4222) * ShowHeroes adapter - description and outstream changes (#4222) * ShowHeroes adapter - increase test coverage and small fix * [Orbidder-Adapter] Add bidRequestCount and remove bid.params.keyValues (#4264) * initial orbidder version in personal github repo * use adUnits from orbidder_example.html * replace obsolete functions * forgot to commit the test * check if bidderRequest object is available * try to fix weird safari/ie issue * ebayK: add more params * update orbidderBidAdapter.md * use spec. instead of this. for consistency reasons * add bidfloor parameter to params object * fix gdpr object handling * default to consentRequired: false when not explicitly given * wip - use onSetTargeting callback * add tests for onSetTargeting callback * fix params and respective tests * remove not used bid.params.keyValues * add bidRequestCount to orbidder.otto.de/bid Post request * add bidRequestCount to test object defaultBidRequest * PulsePoint: remove usage of deprecated utils method / prep for 3.0 (#4257) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * Removing usage of deprecated utils method * minor refactor * Use isArray method (#4288) * Add Parrable ID submodule (#4266) * add parrable id submodule * fix integration test config * fix var name * always refresh sotredId for parrable * add submodulesThatAlwaysRefresh concept * remove comment * add parrable url as one string * add parrable prod endpoint * use .indexOf instead of .includes * add params to test config * comment failing test * uncomment failing assertion * add parrable ID to prebid server adapter * add parrableIdSystem to .submodules.json * extract parrableId unit tests from userId spec * remove breakline between imports * remove unused param * remove userId generic feature from parrableId module * remove trailing space * fix failing test due to none merged conflict * Prebid 2.36.0 Release * Increment pre version * Support schain module and send bidfloor param in Sharethrough adapter (#4271) * Add support for supply chain object module Story: [#168742394](https://www.pivotaltracker.com/story/show/168742394) Co-authored-by: Josh Becker * Add bidfloor parameter to bid request sent to STX Story: [#168742573](https://www.pivotaltracker.com/story/show/168742573) * Platform One Analytics Adapter (#4233) * Added Y1 Analytics Adapter * rename y1AnalyticsAdapter in yieldoneAnalyticsAdapter * Yieldone Bid Adapter: fixes from lint check * Yieldone Analytics Adapter: fix endpoint protocol * Added spec file for yieldone Analytics Adapter * Fix parrable id integration example (#4317) * fix parrableId integration example * add parentheses * Improve Digital adapter: support for video (#4318) * Bid floor, https, native ad update * Update the ad server protocol module * Adding referrer * Improve Digital support for video * Improve Digital adapter: video * adapter version -> 6.0.0 * Gamoshi: Update aliases list. Add support for userSync. (#4319) * Add support for multi-format ad units. Add favoredMediaType property to params. * Add tests for gdpr consent. * Add adId to outbids * Modify media type resolving * Refactor multi-format ad units handler. * Modify the way of sending GDPR data. Update aliases. * Add new consent fields. Add unit test. * Add new consent fields. Add unit test. * Add support for id5 and unified id cookie sync. * Add support for id5 and unified id cookie sync. * Add restricted check for gdpr consent. * fix for userSync endpoint getting called with bidder alias names, instead of actual bidder names (#4265) * modify ixBidAdapater to always use the secure endpoint (#4323) * PubMatic to support Parrable User Id sub-module (#4324) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * PubMatic to support parrable id * VISX: currency validation & fix double escape of referer (#4299) * PubMatic to support coppa (#4336) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * added coppa compliance * vuble: outstream has fullscreen option (#4320) * Mod: vuble oustream has fullscreen option * Add: vuble test for outstream scenario * EMXDigital: hotfix to resolve URIError from decodeURIComponent (#4333) * hotfix to resolve URIError from decodeURIComponent * added unit for decoding adm * Specify second parameter for parseInt for pubmaticBidAdapter (#4347) * Remove usage of getTopWindowUrl in Prebid Adapter (#4341) * Conversant Bid Adapter update for 3.0 (#4284) * Add cpmDistribution function for Google Analytics adapter (#4240) * Add cpmDistribution function for Google Analytics adapter * Add test for the cpmDistribution function * Remove half written comment * fixing SRA p_pos (#4337) * In Sonobi Adapter, only read sizes from bid.mediaTypes (#4311) * Fix mediaTypes (#4332) * Outcon bid adapter. * Fix identation * Fixes * Fixes * Fixes * Spec fixes * Fixes * Fix urls * Fix * Fix parameters * Fix space operators * Fix bidder timeout * Update * Fix whitespace * no message * Outcon unit test * no message * no message * no message * no message * Fixes * Fixes * Change url * no message * no message * no message * Added bidId * no message * no message * no message * no message * Wrapping url with html * no message * no message * no message * Fix mediaTypes * no message * Update outconBidAdapter_spec.js * Adding VAS response * no message * no message * no message * Fix * Changed ttl * no message * supportedMediaTypes * no message * no message * Prebid 2.37.0 release * increment pre version * Add vast xml support and other minor changes to Beachfront adapter (#4350) * Add support for vast xml in the bid response * add secure protocol to outstream player url * add device connection type * add player setting for poster color * add new value for creative Id * Update smartrtbBidAdapter (#4362) * modules: Implement SmartRTB adapter and spec. * Fix for-loop syntax to support IE; refactor getDomain out of exported set. * Remove debugs, update doc * Update test for video support * Handle missing syncs. Add video to media types in sample ad unit * Add null response check, update primary endpoint * Note smrtb video requires renderer * Remove old params checks, fix documentation playerSize field name * Revert "Update smartrtbBidAdapter (#4362)" (#4368) This reverts commit be6704bcec65a28d80b6d09a8d1c51ef9a8ba824. * Add userSync in onetagBidAdapter (#4358) * Minor bug fixing in onetagBidAdapter.js Fixed a minor bug. Updated TTL in response to align the correct specifications. * Update onetagBidAdapter Added additional page info and user sync function. * Update onetagBidAdapter_spec.js Added the test for getUserSyncs function. * Fix about userSync * getUserSyncs: test update with gdpr params * Sovrn adapter updates: schain, digitrust, pixel syncing, and 3.0 upgrades (#4335) * schain and digitrust * pixel beacons * unit tests and fixes from testing * Prebid 3.0 updates * review fix * Add bid adapter for ablida (#4256) * Add ablida adapter * rename category parameter, add documentation * AdKernel: added waardex_ak alias (#4290) * added alias Added a new alias * fixing unit test * Revert "Sovrn adapter updates: schain, digitrust, pixel syncing, and 3.0 upgrades (#4335)" (#4376) This reverts commit 6114a3dba93815dcfb535707d7b4d84f1adb2bc7. * Vrtcal Markets Inc. Bid Adapter Addition (#4259) * Added 3 key Vrtcal Adapter files: adapter,markdown,unit tests * Removed unused getUserSyncs;Added mediaTypes.banner.sizes support;Raised test coverage to 85% * lint formatting errors corrected * Update schain path in ORTB path for spotxBidAdapter (#4377) - Move schain object from request.ext.source.ext.schain to request.source.ext.schain * Update Grid Bid Adapter (#4379) * Added Grid Bid Adapter * remove priceType from TheMediaGrid Bid Adapter * Add video support in Grid Bid Adapter * Added test parameter for video slot * update Grid Bid Adapter to set size in response bid * Update Grid Bid Adapter to support identical uids in parameters * Fix typo in test file for Grid Bid Adapter * Update The Grid Media Bidder Adapter to send refererInfo.referer as 'u' parameter in ad request * Hotfix for referrer in Grid Bid Adapter * Grid Bid Adapter: added wrapperType and wrappweVersion to the ad request * TripleLift: Sending schain (#4375) * Add IdentityLink support and fix UnifiedId. It appears we've been looking for UnifiedId userIds on the bidderRequest object, when they are found on bidRequests. This commit fixes that error, and adds support for IdentityLink. * change maintainer email to group * TripleLift: Sending schain (#1) * Sending schain * null -> undefined * DistrictmDMX: adding support for schain and remove content type to default to prebid selection (#4366) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * Support for ID5 + receive meta data (#4352) * Livewrapped bid and analytics adapter * Fixed some tests for browser compatibility * Fixed some tests for browser compatibility * Changed analytics adapter code name * Fix double quote in debug message * modified how gdpr is being passed * Added support for Publisher Common ID Module * Corrections for ttr in analytics * ANalytics updates * Auction start time stamp changed * Detect recovered ad blocked requests Make it possible to pass dynamic parameters to adapter * Collect info on ad units receiving any valid bid * Support for ID5 Pass metadata from adapter * Typo in test + eids on wrong level * Rubicon Adapter: Always make requests using HTTPS (#4380) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Always make bids requests using https * rp_secure and imp.secure should always be 1 * 7xbid adapter (#4328) * 7xbid adapter * fix error when cli build * - update 33across adapter cookie sync end point (#4345) - update unit test for 33across adapter * Adform adapter: add renderer for outstream bids (#4363) * Prebid 2.38.0 Release * Increment pre version * Adagio: update with external js (#4217) * Add external loader in AdagioBidAdapter * Change adagioAnalyticsAdapter to "endpoint" type * Change _setPredictions for a generic method * Improve AdagioBidAdapter test coverage * Add features detection in Adagio adapter * Fix adagioBidAdapter tests * Add featuresVersion field to bidRequest * Refacto adagio.queue * Expose versions in ADAGIO namespace * Generate a ADAGIO.pageviewId if missing * Move ad-server events tracking to adagioBidAdapter * Store adUnitCodes in ADAGIO namespace * Update documentation Better description of test parameters. * Add internal array to prevent empty pbjs.adUnits * Be sure to access to window.top - does not work in safe-frame env * Add PrintNumber feature * Be sure to compute features on window.top * Bump versions * Add Post-Bid support - ad-server events are listen in current window (instead of window.top) - a new "outerAdUnitElementId" property is set to ADAGIO.pbjsAdUnits array in case of Post-Bid scenario. This property is the 1st parent element id attribute of the iframe in window.top. * Set pagetype param as optional * Add AdThink ad-server support * Improve internal `pbjsAdUnits.sizes` detection Use the adUnit `mediaTypes.banner.sizes` property if exists to build the `ADAGIO.pbjsAdUnits.sizes`. The use of the `sizes` root property is deprecated. * adagioAnalyticsAdapter: add and improve tests * adagioBidAdapter: add and improve tests # Conflicts: # modules/adagioBidAdapter.js # test/spec/modules/adagioBidAdapter_spec.js * adagioBidAdapter: Bump version 1.5 * Adagio: fix import path * PostBid: insure window.top is accessible for specifics functions * Consistency: use Prebid.js utils and fix deprecated * PostBid: do not build a request if in safeframe * Bump version 2.0.0 * Try to fix tests without UA stubing * Try to fix adagioAnalytics failling tests on CI * Consistency: use Prebid loadExternalScript() * Add "adagio" to Prebid.js adloader vendor whitelist * Remove proprietary ad-server listeners * Add RSA validation to adagio external script * add viewdeosDX whitelabel (#4231) * add viewdeosDX hitelabel * Fixed tests and support for sizes * Fix strings * Fix strings * remove only * Fix tests * fix codereview * Fix test + Code review * code review + tests * One video display ad (#4344) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * testing display ad * adding banner * validating banner object * display=1 changes * checking whether diplsy == 1 * html page change * reverting video.html * adding more test cases * spaces * md file change * updated working oneVideoBidAdapter.md file * Update oneVideoBidAdapter.md * Update oneVideoBidAdapter.md * updated the file with both video params and banner * Update video.html * fix double-urlecoded referrer (#4386) * fix double-urlecoded referer (#4388) * PulsePoint Adapter - update for ttl logic (#4400) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * Using the TTL from the bid.ext * Minor refactor * IdentityLink - add logic for sending consent string (#4346) * Fix adagio analytics adapter circleci (#4409) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * update to skip broken circleci tests * skip all * Feature/7xbid remove unneeded params (#4402) * 7xbid adapter * fix error when cli build * remove unneeded params * Empty commit * Empty commit * Remove none ssl (#4406) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * remove none ssl element from all request] * fixed reference to global object (#4412) * ucfunnel adapter support supply chain (#4383) * Add a new ucfunnel Adapter and test page * Add a new ucfunnel Adapter and test page * 1. Use prebid lib in the repo to keep updated 2. Replace var with let 3. Put JSON.parse(JSON.stringify()) into try catch block * utils.getTopWindowLocation is a function * Change to modules from adapters * Migrate to module design * [Dev Fix] Remove width and height which can be got from ad unit id * Update ucfunnelBidAdapter to fit into new spec * Correct the endpoint. Fix the error of query string * Add test case for ucfunnelBidAdapter * Fix lint error * Update version number * Combine all checks on bid request * Add GDPR support for ucfunnel adapter * Add in-stream video and native support for ucfunnel adapter * Remove demo page. Add more test cases. * Change request method from POST to GET * Remove unnecessary comment * Support vastXml and vastUrl for video request * update TTL to 30 mins * Avoid using arrow function which is not discuraged in mocha * ucfunnel tdid support * ucfunnel adapter support supply chain * LiveIntent support in RP Adapter and PBS Adapter update to pass segments (#4303) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * added semi-colon * update eid source to use domain * update video oRTB with liveintent segments * update pbs adapter with liveintent segments support * update rp adapter liveintent support for fastlane * reverted package lock, fix for unintentional update * added unit tests for fastlane.json and ortb, fix to join segments with commas * fix obj property path data.tpid * update remove unnecessary function call * re-ordering query string params * Rubicon Adapter: Add multiple sizes to sizeMap (#4407) * Add Utils to remove item in LocalStorage (#4355) * Making originalCpm and originalCurrency fields in bid object always available (#4396) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * moving originalCurrency declaration from currency to bidderFactory * added a comment * trying to re-run the CI job * added unit test case * trying to re-run the CI job * Placement and inventory (#4353) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * inventory_id and placement * removed unnecessary file * lint error * Update oneVideoBidAdapter.js * lint error fix * Fixes for Platform One Analytics Adapter (#4359) * Added Y1 Analytics Adapter * rename y1AnalyticsAdapter in yieldoneAnalyticsAdapter * Yieldone Bid Adapter: fixes from lint check * Yieldone Analytics Adapter: fix endpoint protocol * Added spec file for yieldone Analytics Adapter * Add adUnitName to analytics data for Yieldone Analytics Adapter * Fix yieldone Analytics Adapter to log only id from adUnitPath * Fix bug with timeout event in Yieldone Analytics Adapter * Added protocol to url (#4395) * initial commit * updated contact and tag details * changes ti support the renderers * changes to pass dimId * fixed names of internal mapping * added comment * added gdpr param to request and other fixes * modified api url * fix * fixed the secure api call * rolled back video event callback till we support it * updated doc with video details * added bid won and timeout pixel * added testcase for bid events * modified testcase * fixed the url logged * tag param values passed ot renderer * added a conditioal check * changes to support new param to adserver for purpose of tracking * passed param to renderer * missing variable defined * added protocol to url * fixed test for protocol * changed urls to secure only * Update emoteev endpoints (#4329) * JustPremium: Update to Prebid 3.0 (#4410) * Update underdogmedia adapter for pbjs 3.0 (#4390) * Update underdogmedia adapter for pbjs 3.0 * Ensure request to endpoint is secure * Update prebid version * Lint fix * Update Consumable adapter for Prebid.js 3.0 (#4401) * Consumable: Clean up tests. * Consumable: Update use of deprecated function. * Consumable: Read sizes from mediaTypes.banner.sizes. * Consumable: Fix lint violation. * CriteoId User Module (#4287) * Add CriteoId module * Update the return type of getId in Criteo Id module Changes: - Use of url parsing function from url lib - Update the return type of getId() - Update the jsdoc to reflect the real return types * Fix failing tests for Criteo user module * Add CriteoIdSystem submodule to .submodule.json. * 2019/10/18 Create Mobsmart bidder adapter (#4339) * Adpod deal support (#4389) * Adpod deal support * Replacing filterBids with minTier * fix potential issue * remove querystringify package (#4422) * Browsi real time data module (#4114) * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * Prebid 2.39.0 Release * increment pre version * OpenX Adapter: Prebid 3.0 Compatibility Update (#4413) * Removed usage of deprecated functions * Removed beacons * Banner sizes now reads from bidRequest.mediaTypes.banner.sizes instead of bidRequest.sizes * Updated tests to reflect changes. * GumGum: use mediaTypes.banner.sizes (#4416) * adds digitrust module, mods gdpr from bool to int * update unit test * only send gdprconsent if present * uses mediaTypes before trying bidRequest sizes * removes use of deprecated method * RTBhouse Bid Adapter update for 3.0 (#4428) * add viewable rendering format (#4201) * Feature/adapter (#4219) * feat(bidrequest): code for making bidrequest * feat(bidresponse): format and return the response * feat(tests): added tests for adapter * feat(docs): added docs for the adapter * refactor(url): changed adserver url * test(user sync): added unit tests for the user syncs * refactor(endpoint): changed endpoint for prebid * refactor(endpoint): changed endpoint for prebid * doc(tagid): mandatory param definition added * fix(imp id): fix for correct impression id * fix(width/height): fix for correct width and height sequence * PulsePoint Bid Adapter: Support for schain (#4433) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * ET-5938 SupplyChain Object Support * Formatting * Code review * Code review * Fix to currency parsing on response * Add supply chain support for Teads adapter (#4420) * Rubicon: support SupplyChain (schain) (#4315) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Starting schain * More tests for banner schain support * Video tests * Encoding tweaks, required fields and comments * Removed .only() from tests * Change requests per Bret * Add 1ad4good bidder (#4081) * adding bidder code and A bidder for non-profit free ads. more info about this bidder project can be found on project site http://1ad4good.org * removed unused code test coverage is improved to >80% tested for instream video support * removed some legacy code, unused params * hardcoding https to endpoint * Improve Digital adapter fix: don't send sizes for instream video (#4427) * Bid floor, https, native ad update * Update the ad server protocol module * Adding referrer * Improve Digital support for video * Improve Digital adapter: video * adapter version -> 6.0.0 * Improve Digital adapter: don't send sizes for video * Fix a typo in code comment (#4450) * Inventory id and schain support for display (#4426) * supporting schain * Update coinzillaBidAdapter.js (#4438) Update sizes const. * Support schain in ZEDO adapter (#4441) * changes to pass schain * PubMatic supporting updated Criteo User Id module (#4431) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * PubMatic supporting updated Criteo User Id module * added a comment to re-start CI * Remove duplicate param to fix unit tests (#4459) * Brightcom Bid Adapter update for 3.0 (#4343) * add support for min_height field in pbs native requests (#4434) * Supporting Alias via Video Requests (#4460) * New adapter Proxistore (#4365) * add test adapter and documentation * integration test with hello_world * reset package-lock.json * delete useless conditionnal * make integrate test work * revert hello-world * revert hello_world * fix descriptor * change adUnits for integration test * remove proxistore widget * uncomment file * change sizes * remove useless script tag * Implementation of setBidderConfig and bidder-specific data (#4334) * initial implementation of setBidderConfig * fix ie11 test errors * Support new setBidderConfig format. Include props from both config and bidderConfig in _getConfig * Use core-js Set to avoid issues with IE * Fix tests in IE * put registerSyncs back on bidderFactory * run bidder event methods with bidder config enabled * Prebid 2.40.0 Release * Increment pre version * Conversant Bid Adapter checks pubcid directly (#4430) * Cookie Sync functionality (#4457) * changing PID param value for testing * cookie sync integration * merge from upstream * Staq Adapter: update with meta envelope (#4372) * initial dev * fix staq adapter name * fix hello world staq call * get hello world working again * add user agent collection * fix some unite tests * Add STAQ Analytics Adapter doc * clean up hello world * fix tests to play nice with browserstack * fix around issues with browserstack and deep equals of objects * dump variable env testing since we can't mod user agent stuff in browserstack * Update STAQ adapter to stop using deprecated utils for referrer * remove package-lock.json changes via master rebase * improve call frequency for ref util * change ajax content type * adjust ajax request to not expect whitelisting * remove superflous commented-out code * update event package to use meta information in envelope rather than per event basis * fix formatting * more formatting fixes * more formatting! * Rhythmone Adapter - schain support (#4414) Circle CI failing tests are not related to this PR. * Media.net Adapter: Support Prebid 3.0 (#4378) * Media.net Adapter: Support Prebid 3.0 * Media.net Adapter: add tests to increase code coverage * Vi Adapter: Passes additional param in the bid request (#4134) * Add focus check (cherry picked from commit 9d6d6dfb83580d6a5ffed8faa5762db48f8fd44d) * Pass focus as numeric value (cherry picked from commit 9fae56a637f87b0d39cc1d24eeb1f9ff9df88f64) * Add unit test (cherry picked from commit 946710f2e9960b3839613d4bdf730e57ba38a964) * Sovrn adapter updates: schain, digitrust, pixel syncing, and 3.0 upgrades (#4385) * schain and digitrust * pixel beacons * unit tests and fixes from testing * Prebid 3.0 updates * review fix * use backwards compatible flatMap impl * update pixel tests * unit test fix * update one more url to ssl * fixed test * review updates * TheMediaGrid Bid Adapter update (#4447) * Added Grid Bid Adapter * remove priceType from TheMediaGrid Bid Adapter * Add video support in Grid Bid Adapter * Added test parameter for video slot * update Grid Bid Adapter to set size in response bid * Update Grid Bid Adapter to support identical uids in parameters * Fix typo in test file for Grid Bid Adapter * Update The Grid Media Bidder Adapter to send refererInfo.referer as 'u' parameter in ad request * Hotfix for referrer in Grid Bid Adapter * Grid Bid Adapter: added wrapperType and wrappweVersion to the ad request * TheMediaGrid Bid Adapter: added sync url * TheMediaGrid Bid Adapter: added GDPR params to sync url * TheMediaGrid Bid Adapter: added tests for getUserSyncs function * Conversant Bid Adapter adds support for extended ids (#4462) * Adkernel 3.0 compatibility (#4477) * Rubicon Adapter pchain support (#4480) * rubicon pchain support * removed describe.only * Implemented changes required to provide support for video in the IX bidding adapter for Instream and Outstream contexts. (#4424) * Default size filter & KVP support (#4452) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * adding logic upto5 * adding support for removing and shuffle sizes * adding array split test * re-assign none standard size to the request * resolve duplicate format inside format array * update .md and adaptor file for KVP support * remove array helper includes * inforce two digit after decimal * RUn error check nothing on my side but error form another adapter * add id5id to prebid server bid adapter (#4468) * Added _pbjsGlobals for tracking renames. Resolves #4254 (#4419) * Feature/smart video (#4367) * Adding outstream video support. * Fixing unit test. * Adding video instream support. * Handling video startDelay parameter. * Improving unit tests. * Fixing indent. * Handling the request when videoMediaType context is not supported. * Changing maintainer mail address. * Remove video outstream specific code. * Unit test updated. * do not select element that gets removed after dfp render (#4423) * add smms adapter (#4439) * add smms adapter * re-run ci, why adigo adapter failed?? * review comments fix, remove deprecated functions, fix unit test * Prebid 2.41.0 release * Increment pre version * adds schain param (#4442) * Create newborntownWeb adapter (#4455) * Create newborntownWeb adapter * only https protocol * Provide criteoId to server by user.ext.eids (#4478) * ucfunnel adapter fix error message in debug mode (#4338) * Add a new ucfunnel Adapter and test page * Add a new ucfunnel Adapter and test page * 1. Use prebid lib in the repo to keep updated 2. Replace var with let 3. Put JSON.parse(JSON.stringify()) into try catch block * utils.getTopWindowLocation is a function * Change to modules from adapters * Migrate to module design * [Dev Fix] Remove width and height which can be got from ad unit id * Update ucfunnelBidAdapter to fit into new spec * Correct the endpoint. Fix the error of query string * Add test case for ucfunnelBidAdapter * Fix lint error * Update version number * Combine all checks on bid request * Add GDPR support for ucfunnel adapter * Add in-stream video and native support for ucfunnel adapter * Remove demo page. Add more test cases. * Change request method from POST to GET * Remove unnecessary comment * Support vastXml and vastUrl for video request * update TTL to 30 mins * Avoid using arrow function which is not discuraged in mocha * ucfunnel tdid support * ucfunnel fix error message in debug mode * explicitly check undefined to allow falsey values in getConfig (#4486) * Conversant Bid Adapter handles vast xml (#4492) * [feature] Add a config list of submodules that require refreshing the stored ID after each bid request (#4325) * add a feature to always refresh stored id on each bid request for submodules that require that * update test comments * Prebid 2.42.0 Release * Increment pre version * Make adhese adapter prebid 3.0 compatible (#4507) * Added 'adhese' attribute to bid that contains meta data - Jira AD-2642 * added DALE to adhese determination * extra config option: no format, but use size array as format string * Read sizes from mediaTypes.banner.sizes + Apply Eslint suggestions * Use map and join, add originData to response * properly use originData obj * Remove duplicated ids * Update tests * BugFix: Site id missing (#4467) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * adding site id * adding placement and siteis * site id param test case * removing deprecated functions * correcting test cases * indentation * test cases fix * Add new bid adaptor alias Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: koji-eguchi <50477903+DAC-KOJI-EGUCHI@users.noreply.github.com> Co-authored-by: Telaria Engineering <36203956+telariaEng@users.noreply.github.com> Co-authored-by: Mike Chowla Co-authored-by: robdubois <53589945+robdubois@users.noreply.github.com> Co-authored-by: sumit116 Co-authored-by: nwlosinski Co-authored-by: Bret Gorsline Co-authored-by: bretg Co-authored-by: Artem Seryak Co-authored-by: Jonathan Mullins Co-authored-by: htang555 Co-authored-by: Bryan DeLong Co-authored-by: dpapworth-qc <50959025+dpapworth-qc@users.noreply.github.com> Co-authored-by: DeepthiNeeladri Co-authored-by: Harshad Mane Co-authored-by: Roman Co-authored-by: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Co-authored-by: Margaret Liu Co-authored-by: TJ Eastmond Co-authored-by: Robert Ray Martinez III Co-authored-by: Jason Snellbaker Co-authored-by: JonGoSonobi Co-authored-by: Vladimir Fedoseev Co-authored-by: DJ Rosenbaum Co-authored-by: Alex Khmelnitsky Co-authored-by: adxpremium <55161519+adxpremium@users.noreply.github.com> Co-authored-by: Jimmy Tu Co-authored-by: Pierre-Antoine Durgeat Co-authored-by: Eric Harper Co-authored-by: ujuettner Co-authored-by: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Co-authored-by: PWyrembak Co-authored-by: susyt Co-authored-by: Max Crawford Co-authored-by: Pascal S Co-authored-by: Will Chapin Co-authored-by: Lemma Dev <54662130+lemmadev@users.noreply.github.com> Co-authored-by: Denis Logachov Co-authored-by: Léonard Labat Co-authored-by: onlsol <48312668+onlsol@users.noreply.github.com> Co-authored-by: Paul Yang Co-authored-by: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Co-authored-by: Mike Sperone Co-authored-by: sdbaron Co-authored-by: djaxbidder <55269794+djaxbidder@users.noreply.github.com> Co-authored-by: turktelssp <54801433+turktelssp@users.noreply.github.com> Co-authored-by: nkmt <45026101+strong-zero@users.noreply.github.com> Co-authored-by: Mutasem Aldmour Co-authored-by: r-schweitzer <50628828+r-schweitzer@users.noreply.github.com> Co-authored-by: Isaac A. Dettman Co-authored-by: Adasta Media <55529969+Adasta2019@users.noreply.github.com> Co-authored-by: mamatic <52153441+mamatic@users.noreply.github.com> Co-authored-by: Konduit <55142865+konduit-dev@users.noreply.github.com> Co-authored-by: TinchoF <50110327+TinchoF@users.noreply.github.com> Co-authored-by: Jaimin Panchal <7393273+jaiminpanchal27@users.noreply.github.com> Co-authored-by: Jaimin Panchal Co-authored-by: Sergio Co-authored-by: Wayne Yang Co-authored-by: Cody Bonney Co-authored-by: evanmsmrtb Co-authored-by: hdeodhar <35999856+hdeodhar@users.noreply.github.com> Co-authored-by: Oz Weiss Co-authored-by: Janko Ulaga Co-authored-by: thomas-33across <44033452+thomas-33across@users.noreply.github.com> Co-authored-by: Finteza Analytics <45741245+finteza@users.noreply.github.com> Co-authored-by: Vadim Mazzherin Co-authored-by: Hendrik Iseke <39734979+hiseke@users.noreply.github.com> Co-authored-by: Anand Venkatraman Co-authored-by: Eyas Ranjous Co-authored-by: Michael Co-authored-by: hbanalytics <55453525+hbanalytics@users.noreply.github.com> Co-authored-by: Index Exchange 3 Prebid Team Co-authored-by: Michael Kuryshev Co-authored-by: Roffray Co-authored-by: rumesh Co-authored-by: oasis <2394426+bmwcmw@users.noreply.github.com> Co-authored-by: Nepomuk Seiler Co-authored-by: John Salis Co-authored-by: OneTagDevOps <38786435+OneTagDevOps@users.noreply.github.com> Co-authored-by: Ankit Prakash Co-authored-by: Dan Co-authored-by: romanantropov <45817046+romanantropov@users.noreply.github.com> Co-authored-by: msm0504 <51493331+msm0504@users.noreply.github.com> Co-authored-by: vrtcal-dev <50931150+vrtcal-dev@users.noreply.github.com> Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: colbertk <50499465+colbertk@users.noreply.github.com> Co-authored-by: Steve Alliance Co-authored-by: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Co-authored-by: 7XBID00 <52267720+7XBID00@users.noreply.github.com> Co-authored-by: Tomas Kovtun Co-authored-by: Olivier Co-authored-by: Gena Co-authored-by: Jonathan Mullins Co-authored-by: ucfunnel <39581136+ucfunnel@users.noreply.github.com> Co-authored-by: ACannuniRP <57228257+ACannuniRP@users.noreply.github.com> Co-authored-by: Hugo Duthil Co-authored-by: skazedo Co-authored-by: 胡雨軒 Петр Co-authored-by: Konrad Dulemba Co-authored-by: Mariya Mego <31904600+mash-a@users.noreply.github.com> Co-authored-by: Daniel Cassidy Co-authored-by: kpis-msa <50609476+kpis-msa@users.noreply.github.com> Co-authored-by: omerBrowsi <54346241+omerBrowsi@users.noreply.github.com> Co-authored-by: Marcian123 Co-authored-by: koji-eguchi <50477903+koji-eguchi@users.noreply.github.com> Co-authored-by: sourabhg Co-authored-by: Alexis Andrieu Co-authored-by: Vlad Gurgov Co-authored-by: Richard Lee <14349+dlackty@users.noreply.github.com> Co-authored-by: Alex Co-authored-by: Vladislav Yatsun Co-authored-by: vincentproxistore <56686565+vincentproxistore@users.noreply.github.com> Co-authored-by: Rich Snapp Co-authored-by: Rade Popovic <32302052+nanointeractive@users.noreply.github.com> Co-authored-by: Matt Quirion Co-authored-by: rhythmonebhaines <49991465+rhythmonebhaines@users.noreply.github.com> Co-authored-by: binoy-chitale Co-authored-by: Alex Pashkov Co-authored-by: harpere Co-authored-by: Scott Co-authored-by: tadam75 Co-authored-by: Veronica Kim <43146383+vkimcm@users.noreply.github.com> Co-authored-by: songtungmtp <57524426+songtungmtp@users.noreply.github.com> Co-authored-by: z-sunshine <33084773+z-sunshine@users.noreply.github.com> Co-authored-by: Sander Co-authored-by: Moshe Moses --- modules/gamoshiBidAdapter.js | 2 +- modules/gamoshiBidAdapter.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index 2e09cf55d0a..48a142a66a6 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -42,7 +42,7 @@ export const helper = { export const spec = { code: 'gamoshi', - aliases: ['gambid', 'cleanmedia', '9MediaOnline'], + aliases: ['gambid', 'cleanmedia', '9MediaOnline', 'MobfoxX'], supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { diff --git a/modules/gamoshiBidAdapter.md b/modules/gamoshiBidAdapter.md index 6e930375059..49b727cecae 100644 --- a/modules/gamoshiBidAdapter.md +++ b/modules/gamoshiBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Gamoshi Bid Adapter Module Type: Bidder Adapter -Maintainer: salomon@gamoshi.com +Maintainer: dev@gamoshi.com ``` # Description From 7d9772009919eea9f72b54f18e7aa7fceac8ab5d Mon Sep 17 00:00:00 2001 From: Maxime Lequain Date: Wed, 11 Nov 2020 14:37:24 +0100 Subject: [PATCH 155/190] adot: add publisher id retrieval from bidder config (#5928) Co-authored-by: Maxime Lequain --- modules/adotBidAdapter.js | 7 ++++++- modules/adotBidAdapter.md | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index 3d0af864a31..54bd9156b48 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {isStr, isArray, isNumber, isPlainObject, isBoolean, logError, replaceAuctionPrice} from '../src/utils.js'; import find from 'core-js-pure/features/array/find.js'; +import { config } from '../src/config.js'; const ADAPTER_VERSION = 'v1.0.0'; const BID_METHOD = 'POST'; @@ -334,13 +335,17 @@ function generateSiteFromAdUnitContext(adUnitContext) { if (!adUnitContext || !adUnitContext.refererInfo) return null; const domain = extractSiteDomainFromURL(adUnitContext.refererInfo.referer); + const publisherId = config.getConfig('adot.publisherId'); if (!domain) return null; return { page: adUnitContext.refererInfo.referer, domain: domain, - name: domain + name: domain, + publisher: { + id: publisherId + } }; } diff --git a/modules/adotBidAdapter.md b/modules/adotBidAdapter.md index 7fc1d84ee60..e1388311e23 100644 --- a/modules/adotBidAdapter.md +++ b/modules/adotBidAdapter.md @@ -214,3 +214,20 @@ const adUnit = { }] } ``` + +### PublisherId + +You can set a publisherId using `pbjs.setBidderConfig` for the bidder `adot` + +#### Example + +```javascript +pbjs.setBidderConfig({ + bidders: ['adot'], + config: { + adot: { + publisherId: '__MY_PUBLISHER_ID__' + } + } +}); +``` \ No newline at end of file From 2b0bd53604563b51165bf96af08b31f00ad8c2da Mon Sep 17 00:00:00 2001 From: Lemma Dev <54662130+lemmadev@users.noreply.github.com> Date: Thu, 12 Nov 2020 22:06:09 +0530 Subject: [PATCH 156/190] Lemma bid adapter: Add user sync support (#5934) * 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. --- modules/lemmaBidAdapter.js | 34 ++++++++++++++++++++--- test/spec/modules/lemmaBidAdapter_spec.js | 34 +++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/modules/lemmaBidAdapter.js b/modules/lemmaBidAdapter.js index 1ad660e5916..5941802f97d 100644 --- a/modules/lemmaBidAdapter.js +++ b/modules/lemmaBidAdapter.js @@ -5,10 +5,13 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; var BIDDER_CODE = 'lemma'; var LOG_WARN_PREFIX = 'LEMMA: '; var ENDPOINT = 'https://ads.lemmatechnologies.com/lemma/servad'; +var USER_SYNC = 'https://sync.lemmatechnologies.com/js/usersync.html?'; var DEFAULT_CURRENCY = 'USD'; var AUCTION_TYPE = 2; var DEFAULT_TMAX = 300; var DEFAULT_NET_REVENUE = false; +var pubId = 0; +var adunitId = 0; export var spec = { @@ -57,6 +60,29 @@ export var spec = { interpretResponse: (response, request) => { return parseRTBResponse(request, response.body); }, + 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', + url: syncurl + }]; + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.'); + } + }, }; function _initConf(refererInfo) { @@ -167,8 +193,8 @@ function _getImpressionArray(request) { function endPointURL(request) { var params = request && request[0].params ? request[0].params : null; if (params) { - var pubId = params.pubId ? params.pubId : 0; - var adunitId = params.adunitId ? params.adunitId : 0; + pubId = params.pubId ? params.pubId : 0; + adunitId = params.adunitId ? params.adunitId : 0; return ENDPOINT + '?pid=' + pubId + '&aid=' + adunitId; } return null; @@ -183,7 +209,7 @@ function _getDomain(url) { function _getSiteObject(request, conf) { var params = request && request.params ? request.params : null; if (params) { - var pubId = params.pubId ? params.pubId : '0'; + pubId = params.pubId ? params.pubId : '0'; var siteId = params.siteId ? params.siteId : '0'; var appParams = params.app; if (!appParams) { @@ -204,7 +230,7 @@ function _getSiteObject(request, conf) { function _getAppObject(request) { var params = request && request.params ? request.params : null; if (params) { - var pubId = params.pubId ? params.pubId : 0; + pubId = params.pubId ? params.pubId : 0; var appParams = params.app; if (appParams) { return { diff --git a/test/spec/modules/lemmaBidAdapter_spec.js b/test/spec/modules/lemmaBidAdapter_spec.js index a236ac17d71..a00c25d126c 100644 --- a/test/spec/modules/lemmaBidAdapter_spec.js +++ b/test/spec/modules/lemmaBidAdapter_spec.js @@ -331,5 +331,39 @@ describe('lemmaBidAdapter', function() { }); }); }); + describe('getUserSyncs', function() { + const syncurl_iframe = 'https://sync.lemmatechnologies.com/js/usersync.html?pid=1001'; + let sandbox; + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + }); + + it('execute as per config', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl_iframe + }]); + }); + + it('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 954acdb21f10fc14198f6c135364f81eee04d48c Mon Sep 17 00:00:00 2001 From: SKOCHERI <37454420+SKOCHERI@users.noreply.github.com> Date: Thu, 12 Nov 2020 10:33:13 -0800 Subject: [PATCH 157/190] ID Library (#5863) * ID Library * build fix * build fix * build fix * build fix * Fixing review comments * Fixing review comments (debounce, full body scan option * Fixing review comments (var declaration, strict check) * Fixing space Co-authored-by: skocheri --- modules/idLibrary.js | 243 ++++++++++++++++++++++++++++ modules/idLibrary.md | 24 +++ test/spec/modules/idLibrary_spec.js | 61 +++++++ 3 files changed, 328 insertions(+) create mode 100644 modules/idLibrary.js create mode 100644 modules/idLibrary.md create mode 100644 test/spec/modules/idLibrary_spec.js diff --git a/modules/idLibrary.js b/modules/idLibrary.js new file mode 100644 index 00000000000..ba3cc0b5efb --- /dev/null +++ b/modules/idLibrary.js @@ -0,0 +1,243 @@ +import {getGlobal} from '../src/prebidGlobal.js'; +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import * as utils from '../src/utils.js'; +import MD5 from 'crypto-js/md5.js'; + +let email; +let conf; +const LOG_PRE_FIX = 'ID-Library: '; +const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250; +const CONF_DEFAULT_FULL_BODY_SCAN = true; +const OBSERVER_CONFIG = { + subtree: true, + attributes: true, + attributeOldValue: false, + childList: true, + attirbuteFilter: ['value'], + characterData: true, + characterDataOldValue: false +}; +const logInfo = createLogInfo(LOG_PRE_FIX); +const logError = createLogError(LOG_PRE_FIX); + +function createLogInfo(prefix) { + return function (...strings) { + utils.logInfo(prefix + ' ', ...strings); + } +} + +function createLogError(prefix) { + return function (...strings) { + utils.logError(prefix + ' ', ...strings); + } +} + +function getEmail(value) { + const matched = value.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi); + if (!matched) { + return null; + } + logInfo('Email found' + matched[0]); + return matched[0]; +} + +function bodyAction(mutations, observer) { + logInfo('BODY observer on debounce called'); + // If the email is found in the input element, disconnect the observer + if (email) { + observer.disconnect(); + logInfo('Email is found, body observer disconnected'); + return; + } + + const body = document.body.innerHTML; + email = getEmail(body); + if (email !== null) { + logInfo(`Email obtained from the body ${email}`); + observer.disconnect(); + logInfo('Post data on email found in body'); + postData(); + } +} + +function targetAction(mutations, observer) { + logInfo('Target observer called'); + for (const mutation of mutations) { + for (const node of mutation.addedNodes) { + email = node.textContent; + + if (email) { + logInfo('Email obtained from the target ' + email); + observer.disconnect(); + logInfo('Post data on email found in target'); + postData(); + return; + } + } + } +} + +function addInputElementsElementListner(conf) { + logInfo('Adding input element listeners'); + const inputs = document.querySelectorAll('input[type=text], input[type=email]'); + + for (var i = 0; i < inputs.length; i++) { + logInfo(`Original Value in Input = ${inputs[i].value}`); + inputs[i].addEventListener('change', event => processInputChange(event)); + inputs[i].addEventListener('blur', event => processInputChange(event)); + } +} + +function removeInputElementsElementListner() { + logInfo('Removing input element listeners'); + const inputs = document.querySelectorAll('input[type=text], input[type=email]'); + + for (var i = 0; i < inputs.length; i++) { + inputs[i].removeEventListener('change', event => processInputChange(event)); + inputs[i].removeEventListener('blur', event => processInputChange(event)); + } +} + +function processInputChange(event) { + const value = event.target.value; + logInfo(`Modified Value of input ${event.target.value}`); + email = getEmail(value); + if (email !== null) { + logInfo('Email found in input ' + email); + postData(); + removeInputElementsElementListner(); + } +} + +function debounce(func, wait, immediate) { + var timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + if (callNow) { + func.apply(context, args); + } else { + logInfo('Debounce wait time ' + wait); + timeout = setTimeout(later, wait); + } + }; +}; + +function handleTargetElement() { + const targetObserver = new MutationObserver(debounce(targetAction, conf.debounce, false)); + + const targetElement = document.getElementById(conf.target); + if (targetElement) { + email = targetElement.innerText; + + if (!email) { + logInfo('Finding the email with observer'); + targetObserver.observe(targetElement, OBSERVER_CONFIG); + } else { + logInfo('Target found with target ' + email); + logInfo('Post data on email found in target with target'); + postData(); + } + } +} + +function handleBodyElements() { + if (doesInputElementsHaveEmail()) { + logInfo('Email found in input elements ' + email); + logInfo('Post data on email found in target without'); + postData(); + return; + } + email = getEmail(document.body.innerHTML); + if (email !== null) { + logInfo('Email found in body ' + email); + logInfo('Post data on email found in the body without observer'); + postData(); + return; + } + addInputElementsElementListner(); + if (conf.fullscan === true) { + const bodyObserver = new MutationObserver(debounce(bodyAction, conf.debounce, false)); + bodyObserver.observe(document.body, OBSERVER_CONFIG); + } +} + +function doesInputElementsHaveEmail() { + const inputs = document.getElementsByTagName('input'); + + for (let index = 0; index < inputs.length; ++index) { + const curInput = inputs[index]; + email = getEmail(curInput.value); + if (email !== null) { + return true; + } + } + return false; +} + +function syncCallback() { + return { + success: function () { + logInfo('Data synced successfully.'); + }, + error: function () { + logInfo('Data sync failed.'); + } + } +} + +function postData() { + (getGlobal()).refreshUserIds(); + const userIds = (getGlobal()).getUserIds(); + if (Object.keys(userIds).length === 0) { + logInfo('No user ids'); + return; + } + logInfo('Users' + JSON.stringify(userIds)); + const syncPayload = {}; + syncPayload.hid = MD5(email).toString(); + syncPayload.uids = JSON.stringify(userIds); + const payloadString = JSON.stringify(syncPayload); + logInfo(payloadString); + ajax(conf.url, syncCallback(), payloadString, {method: 'POST', withCredentials: true}); +} + +function associateIds() { + if (window.MutationObserver || window.WebKitMutationObserver) { + if (conf.target) { + handleTargetElement(); + } else { + handleBodyElements(); + } + } +} + +export function setConfig(config) { + if (!config) { + logError('Required confirguration not provided'); + return; + } + if (!config.url) { + logError('The required url is not configured'); + return; + } + if (typeof config.debounce !== 'number') { + config.debounce = CONF_DEFAULT_OBSERVER_DEBOUNCE_MS; + logInfo('Set default observer debounce to ' + CONF_DEFAULT_OBSERVER_DEBOUNCE_MS); + } + if (typeof config.fullscan !== 'boolean') { + config.fullscan = CONF_DEFAULT_FULL_BODY_SCAN; + logInfo('Set default fullscan ' + CONF_DEFAULT_FULL_BODY_SCAN); + } + conf = config; + associateIds(); +} + +config.getConfig('idLibrary', config => setConfig(config.idLibrary)); diff --git a/modules/idLibrary.md b/modules/idLibrary.md new file mode 100644 index 00000000000..69b63dc466b --- /dev/null +++ b/modules/idLibrary.md @@ -0,0 +1,24 @@ +## ID Library Configuration Example + + +|Param |Required |Description | +|----------------|-------------------------------|-----------------------------| +|url |Yes | The url endpoint is used to post the hashed email and user ids. | +|target |No |It should contain the element id from which the email can be read. | +|debounce |No | Time in milliseconds before the email and ids are fetched | +|fullscan |No | Option to enable/disable full body scan to get email. By default the full body scan is enabled. | + +### Example +``` + pbjs.setConfig({ + idLibrary:{ + url: , + debounce: 250, + target: 'username', + fullscan: false + }, + }); +``` + + +``` diff --git a/test/spec/modules/idLibrary_spec.js b/test/spec/modules/idLibrary_spec.js new file mode 100644 index 00000000000..da61850f29b --- /dev/null +++ b/test/spec/modules/idLibrary_spec.js @@ -0,0 +1,61 @@ +import * as utils from 'src/utils.js'; +import * as idlibrary from 'modules/idLibrary.js'; + +var expect = require('chai').expect; + +describe('currency', function () { + let fakeCurrencyFileServer; + let sandbox; + let clock; + + let fn = sinon.spy(); + + beforeEach(function () { + fakeCurrencyFileServer = sinon.fakeServer.create(); + sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + utils.logInfo.restore(); + utils.logError.restore(); + fakeCurrencyFileServer.restore(); + idlibrary.setConfig({}); + }); + + describe('setConfig', function () { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(1046952000000); // 2003-03-06T12:00:00Z + }); + + afterEach(function () { + sandbox.restore(); + clock.restore(); + }); + + it('results when no config available', function () { + idlibrary.setConfig({}); + sinon.assert.called(utils.logError); + }); + it('results with config available', function () { + idlibrary.setConfig({ 'url': 'URL' }); + sinon.assert.called(utils.logInfo); + }); + it('results with config default debounce ', function () { + let config = { 'url': 'URL' } + idlibrary.setConfig(config); + expect(config.debounce).to.be.equal(250); + }); + it('results with config default fullscan ', function () { + let config = { 'url': 'URL' } + idlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(true); + }); + it('results with config fullscan ', function () { + let config = { 'url': 'URL', 'fullscan': false } + idlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(false); + }); + }); +}); From 17beca2fb045e29fe9dfcb722825eeffccf26852 Mon Sep 17 00:00:00 2001 From: mwehr-zeta <70167335+mwehr-zeta@users.noreply.github.com> Date: Thu, 12 Nov 2020 14:09:48 -0500 Subject: [PATCH 158/190] Zeta-Prebid (#5813) * Submit Zeta Adapter to Prebid * comments addressed * demo changes * additional polishing * additional polishing * Update hello_world.html * remove extraneous changes to hello_world.html * no, really this time * additional polishing * add unit test * stop tracking package-lock.json * stop tracking package-lock.json * stop tracking, please * Submit Zeta Adapter * refactor bidder_code value * fix markdown bidder code and include definerId in request --- modules/zetaBidAdapter.js | 161 +++++++++++++++++++++++ modules/zetaBidAdapter.md | 40 ++++++ test/spec/modules/zetaBidAdapter_spec.js | 79 +++++++++++ 3 files changed, 280 insertions(+) create mode 100644 modules/zetaBidAdapter.js create mode 100644 modules/zetaBidAdapter.md create mode 100644 test/spec/modules/zetaBidAdapter_spec.js diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js new file mode 100644 index 00000000000..f60e8946799 --- /dev/null +++ b/modules/zetaBidAdapter.js @@ -0,0 +1,161 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +const BIDDER_CODE = 'zeta_global'; +const ENDPOINT_URL = 'https://prebid.rfihub.com/prebid'; +const USER_SYNC_URL = 'https://p.rfihub.com/cm?pub=42770&in=1'; +const DEFAULT_CUR = 'USD'; +const TTL = 200; +const NET_REV = true; + +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) { + // check for all required bid fields + let isValid = !!( + bid && + bid.bidId && + bid.params && + bid.params.ip && + bid.params.user && + bid.params.user.buyeruid && + bid.params.definerId + ); + if (!isValid) { + utils.logWarn('Invalid bid request'); + } + return isValid; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {Bids[]} validBidRequests - an array of bidRequest objects + * @param {BidderRequest} bidderRequest - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const secure = 1; // treat all requests as secure + const request = validBidRequests[0]; + const params = request.params; + let impData = { + id: request.bidId, + secure: secure, + banner: buildBanner(request) + }; + let isMobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; + let payload = { + id: bidderRequest.auctionId, + cur: [DEFAULT_CUR], + imp: [impData], + site: { + mobile: isMobile, + page: bidderRequest.refererInfo.referer + }, + device: { + ua: navigator.userAgent, + ip: params.ip + }, + user: { + buyeruid: params.user.buyeruid, + uid: params.user.uid + }, + ext: { + definerId: params.definerId + } + }; + if (params.test) { + payload.test = params.test; + } + if (request.gdprConsent) { + payload.regs = { + ext: { + gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + } + if (request.gdprConsent && request.gdprConsent.gdprApplies) { + payload.user = { + ext: { + consent: request.gdprConsent.consentString + } + }; + } + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(payload), + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest The payload from the server's response. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + let bidResponse = []; + if (Object.keys(serverResponse.body).length !== 0) { + let zetaResponse = serverResponse.body; + let zetaBid = zetaResponse.seatbid[0].bid[0]; + let bid = { + requestId: zetaBid.impid, + cpm: zetaBid.price, + currency: zetaResponse.cur, + width: zetaBid.w, + height: zetaBid.h, + ad: zetaBid.adm, + ttl: TTL, + creativeId: zetaBid.crid, + netRevenue: NET_REV + }; + bidResponse.push(bid); + } + return bidResponse; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @param gdprConsent The GDPR consent parameters + * @param uspConsent The USP consent parameters + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: USER_SYNC_URL + }); + } + return syncs; + } +} + +function buildBanner(request) { + let sizes = request.sizes; + if (request.mediaTypes && + request.mediaTypes.banner && + request.mediaTypes.banner.sizes) { + sizes = request.mediaTypes.banner.sizes; + } + return { + w: sizes[0][0], + h: sizes[0][1] + }; +} + +registerBidder(spec); diff --git a/modules/zetaBidAdapter.md b/modules/zetaBidAdapter.md new file mode 100644 index 00000000000..ce19b831d4d --- /dev/null +++ b/modules/zetaBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Zeta Bidder Adapter +Module Type: Bidder Adapter +Maintainer: DL-ZetaDSP-Supply-Engineering@zetaglobal.com +``` + +# Description + +Module that connects to Zeta's demand sources + +# Test Parameters +``` + var adUnits = [ + { + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: 'zeta_global', + bidId: 12345, + params: { + placement: 12345, + user: { + uid: 12345, + buyeruid: 12345 + }, + ip: '111.222.33.44', + definerId: 1, + test: 1 + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/zetaBidAdapter_spec.js b/test/spec/modules/zetaBidAdapter_spec.js new file mode 100644 index 00000000000..0d11614c926 --- /dev/null +++ b/test/spec/modules/zetaBidAdapter_spec.js @@ -0,0 +1,79 @@ +import { spec } from '../../../modules/zetaBidAdapter.js' + +describe('Zeta Bid Adapter', function() { + const bannerRequest = [{ + bidId: 12345, + auctionId: 67890, + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + refererInfo: { + referer: 'zetaglobal.com' + }, + params: { + placement: 12345, + user: { + uid: 12345, + buyeruid: 12345 + }, + ip: '111.222.33.44', + definerId: 1, + test: 1 + } + }]; + + it('Test the bid validation function', function() { + const validBid = spec.isBidRequestValid(bannerRequest[0]); + const invalidBid = spec.isBidRequestValid(null); + + expect(validBid).to.be.true; + expect(invalidBid).to.be.false; + }); + + it('Test the request processing function', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + expect(request).to.not.be.empty; + + const payload = request.data; + expect(payload).to.not.be.empty; + }); + + const responseBody = { + id: '12345', + seatbid: [ + { + bid: [ + { + id: 'auctionId', + impid: 'impId', + price: 0.0, + adm: 'adMarkup', + crid: 'creativeId', + h: 250, + w: 300 + } + ] + } + ], + cur: 'USD' + }; + + it('Test the response parsing function', function () { + const receivedBid = responseBody.seatbid[0].bid[0]; + const response = {}; + response.body = responseBody; + + const bidResponse = spec.interpretResponse(response, null); + expect(bidResponse).to.not.be.empty; + + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.ad).to.equal(receivedBid.adm); + expect(bid.cpm).to.equal(receivedBid.price); + expect(bid.height).to.equal(receivedBid.h); + expect(bid.width).to.equal(receivedBid.w); + expect(bid.requestId).to.equal(receivedBid.impid); + }); +}); From d5c2c8fb70df5111aeff22eb9902829f273e7085 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Thu, 12 Nov 2020 12:36:28 -0800 Subject: [PATCH 159/190] Prebid 4.16.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8dc3535cc21..c61e97d0adf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.16.0-pre", + "version": "4.16.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ef7fb8673535d3f2718248ca75702472f5f9899f Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Thu, 12 Nov 2020 13:08:10 -0800 Subject: [PATCH 160/190] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c61e97d0adf..4b62de09e1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.16.0", + "version": "4.17.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 09468bf036ad44a28de867f1baf8a81e90b03b80 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Fri, 13 Nov 2020 10:49:46 -0500 Subject: [PATCH 161/190] [AD-1043] JW Player RTD - add targeting info to bid.rtd (#5950) * adds targeting to rtd * updates tmg module * updates unit tests * shortens realTimeData * updates grid adapter for rtd Co-authored-by: karimJWP --- modules/gridBidAdapter.js | 3 +- modules/jwplayerRtdProvider.js | 11 +- modules/jwplayerRtdProvider.md | 14 ++- test/spec/modules/gridBidAdapter_spec.js | 10 +- test/spec/modules/jwplayerRtdProvider_spec.js | 115 +++++++++++++++--- 5 files changed, 124 insertions(+), 29 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 5436a18c6cb..db1402ea9ad 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -72,11 +72,12 @@ export const spec = { if (!userId) { userId = bid.userId; } - const {params: {uid, keywords, bidFloor}, mediaTypes, bidId, adUnitCode, jwTargeting} = bid; + const {params: {uid, keywords, bidFloor}, mediaTypes, bidId, adUnitCode, rtd} = bid; bidsMap[bidId] = bid; if (!pageKeywords && !utils.isEmpty(keywords)) { pageKeywords = utils.transformBidderParamKeywords(keywords); } + const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; if (jwTargeting) { if (!jwpseg && jwTargeting.segments) { jwpseg = jwTargeting.segments; diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 17cb978aea3..197c3c192c8 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -247,9 +247,14 @@ function addTargetingToBids(bids, targeting) { return; } - bids.forEach(bid => { - bid.jwTargeting = targeting; - }); + bids.forEach(bid => addTargetingToBid(bid, targeting)); +} + +export function addTargetingToBid(bid, targeting) { + const rtd = bid.rtd || {}; + const jwRtd = {}; + jwRtd[SUBMODULE_NAME] = Object.assign({}, rtd[SUBMODULE_NAME], { targeting }); + bid.rtd = Object.assign({}, rtd, jwRtd); } function getPlayer(playerID) { diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 3c83b6f521c..ae09277979a 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -86,11 +86,15 @@ Each bid for which targeting information was found will conform to the following adUnitCode: 'xyz', bidId: 'abc', ..., - jwTargeting: { - segments: ['123', '456'], - content: { - id: 'jw_abc123' - } + rtd: { + jwplayer: { + targeting: { + segments: ['123', '456'], + content: { + id: 'jw_abc123' + } + } + } } } ``` diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 9cd7d100318..640bfda1fee 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -411,9 +411,13 @@ describe('TheMediaGrid Adapter', function () { const jsSegments = ['test_seg_1', 'test_seg_2']; const bidRequestsWithUserIds = bidRequests.map((bid) => { return Object.assign({ - jwTargeting: { - segments: jsSegments, - content: jsContent + rtd: { + jwplayer: { + targeting: { + segments: jsSegments, + content: jsContent + } + } } }, bid); }); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 6e0fd8eb8d7..48b432b6bb4 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -1,5 +1,5 @@ import { fetchTargetingForMediaId, getVatFromCache, extractPublisherParams, - formatTargetingResponse, getVatFromPlayer, enrichAdUnits, + formatTargetingResponse, getVatFromPlayer, enrichAdUnits, addTargetingToBid, fetchTargetingInformation, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js'; import { server } from 'test/mocks/xhr.js'; @@ -252,7 +252,7 @@ describe('jwplayerRtdProvider', function() { }; jwplayerSubmodule.getBidRequestData({ adUnits: [adUnit] }, bidRequestSpy); expect(bidRequestSpy.calledOnce).to.be.true; - expect(bid).to.have.deep.property('jwTargeting', expectedTargeting); + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); fakeServer.respond(); expect(bidRequestSpy.calledOnce).to.be.true; }); @@ -313,8 +313,8 @@ describe('jwplayerRtdProvider', function() { enrichAdUnits([adUnit]); const bid1 = bids[0]; const bid2 = bids[1]; - expect(bid1).to.not.have.property('jwTargeting'); - expect(bid2).to.not.have.property('jwTargeting'); + expect(bid1).to.not.have.property('rtd'); + expect(bid2).to.not.have.property('rtd'); const request = fakeServer.requests[0]; request.respond( @@ -330,8 +330,8 @@ describe('jwplayerRtdProvider', function() { }) ); - expect(bid1).to.have.deep.property('jwTargeting', expectedTargetingForSuccess); - expect(bid2).to.have.deep.property('jwTargeting', expectedTargetingForSuccess); + expect(bid1.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); + expect(bid2.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); }); it('immediately adds cached targeting', function () { @@ -373,8 +373,8 @@ describe('jwplayerRtdProvider', function() { enrichAdUnits([adUnit]); const bid1 = bids[0]; const bid2 = bids[1]; - expect(bid1).to.have.deep.property('jwTargeting', expectedTargetingForSuccess); - expect(bid2).to.have.deep.property('jwTargeting', expectedTargetingForSuccess); + expect(bid1.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); + expect(bid2.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForSuccess); }); it('adds content block when segments are absent and no request is pending', function () { @@ -407,8 +407,8 @@ describe('jwplayerRtdProvider', function() { enrichAdUnits([adUnit]); const bid1 = bids[0]; const bid2 = bids[1]; - expect(bid1).to.have.deep.property('jwTargeting', expectedTargetingForFailure); - expect(bid2).to.have.deep.property('jwTargeting', expectedTargetingForFailure); + expect(bid1.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForFailure); + expect(bid2.rtd.jwplayer).to.have.deep.property('targeting', expectedTargetingForFailure); }); }); @@ -456,6 +456,87 @@ describe('jwplayerRtdProvider', function() { }) }); + describe('Add Targeting to Bid', function () { + const targeting = {foo: 'bar'}; + + it('creates realTimeData when absent from Bid', function () { + const targeting = {foo: 'bar'}; + const bid = {}; + addTargetingToBid(bid, targeting); + expect(bid).to.have.property('rtd'); + expect(bid).to.have.nested.property('rtd.jwplayer.targeting', targeting); + }); + + it('adds to existing realTimeData', function () { + const otherRtd = { + targeting: { + seg: 'rtd seg' + } + }; + + const bid = { + rtd: { + otherRtd + } + }; + + addTargetingToBid(bid, targeting); + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + + expect(rtd).to.have.deep.property('otherRtd', otherRtd); + }); + + it('adds to existing realTimeData.jwplayer', function () { + const otherInfo = { seg: 'rtd seg' }; + const bid = { + rtd: { + jwplayer: { + otherInfo + } + } + }; + addTargetingToBid(bid, targeting); + + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.otherInfo', otherInfo); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + }); + + it('overrides existing jwplayer.targeting', function () { + const otherInfo = { seg: 'rtd seg' }; + const bid = { + rtd: { + jwplayer: { + targeting: { + otherInfo + } + } + } + }; + addTargetingToBid(bid, targeting); + + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + }); + + it('creates jwplayer when absent from realTimeData', function () { + const bid = { rtd: {} }; + addTargetingToBid(bid, targeting); + + expect(bid).to.have.property('rtd'); + const rtd = bid.rtd; + expect(rtd).to.have.property('jwplayer'); + expect(rtd).to.have.nested.property('jwplayer.targeting', targeting); + }); + }); + describe('jwplayerSubmodule', function () { it('successfully instantiates', function () { expect(jwplayerSubmodule.init()).to.equal(true); @@ -578,7 +659,7 @@ describe('jwplayerRtdProvider', function() { }; jwplayerSubmodule.getBidRequestData({ adUnits: [adUnitWithMediaId, adUnitEmpty] }, bidRequestSpy); expect(bidRequestSpy.calledOnce).to.be.true; - expect(bid).to.have.deep.property('jwTargeting', expectedTargeting); + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); }); it('excludes segments when absent', function () { @@ -605,9 +686,9 @@ describe('jwplayerRtdProvider', function() { jwplayerSubmodule.getBidRequestData({ adUnits: [ adUnit ] }, bidRequestSpy); expect(bidRequestSpy.calledOnce).to.be.true; - expect(bid.jwTargeting).to.not.have.property('segments'); - expect(bid.jwTargeting).to.not.have.property('segments'); - expect(bid).to.have.deep.property('jwTargeting', expectedTargeting); + expect(bid.rtd.jwplayer.targeting).to.not.have.property('segments'); + expect(bid.rtd.jwplayer.targeting).to.not.have.property('segments'); + expect(bid.rtd.jwplayer).to.have.deep.property('targeting', expectedTargeting); }); it('does not modify bid when jwTargeting block is absent', function () { @@ -637,9 +718,9 @@ describe('jwplayerRtdProvider', function() { jwplayerSubmodule.getBidRequestData({ adUnits: [adUnitWithMediaId, adUnitEmpty, adUnitEmptyfpd] }, bidRequestSpy); expect(bidRequestSpy.calledOnce).to.be.true; - expect(bid1).to.not.have.property('jwTargeting'); - expect(bid2).to.not.have.property('jwTargeting'); - expect(bid3).to.not.have.property('jwTargeting'); + expect(bid1).to.not.have.property('rtd'); + expect(bid2).to.not.have.property('rtd'); + expect(bid3).to.not.have.property('rtd'); }); }); }); From 5f36be90bb816e84a68cff58ebc6999967dc3739 Mon Sep 17 00:00:00 2001 From: jdwieland8282 Date: Fri, 13 Nov 2020 12:24:57 -0700 Subject: [PATCH 162/190] Update pubCommonIdSystem.js adding Prebid GVLID to getStorageManager function --- modules/pubCommonIdSystem.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index 919e735d34e..acdfaefd5d0 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -20,8 +20,9 @@ const SHAREDID_URL = 'https://id.sharedid.org/id'; const SHAREDID_SUFFIX = '_sharedid'; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; const SHAREDID_DEFAULT_STATE = false; +const GLVID = 887; -const storage = getStorageManager(null, 'pubCommonId'); +const storage = getStorageManager(GLVID, 'pubCommonId'); /** * Store sharedid in either cookie or local storage From 92a9a09b5509f266c7f69761b7c652f9586cc61f Mon Sep 17 00:00:00 2001 From: jdwieland8282 Date: Sun, 15 Nov 2020 06:01:35 -0700 Subject: [PATCH 163/190] Update pubCommonIdSystem.js (#5974) * Update pubCommonIdSystem.js adding GVLID to pubCommonIdSubmodule definition * Update pubCommonIdSystem.js adding GVLID to pubCommonIdSubmodule definition * Update pubCommonIdSystem.js removing duplicate reference to GVLID in pubCommonIdSubmodule defintion * Update pubCommonIdSystem.js fixing typo on ln 23 GLVID --> GVLID * Update pubCommonIdSystem.js fixing typos, * Update pubCommonIdSystem.js removing trailing white space * Update pubCommonIdSystem.js removing trailing white space --- modules/pubCommonIdSystem.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js index acdfaefd5d0..cb0c07cefa8 100644 --- a/modules/pubCommonIdSystem.js +++ b/modules/pubCommonIdSystem.js @@ -20,9 +20,9 @@ const SHAREDID_URL = 'https://id.sharedid.org/id'; const SHAREDID_SUFFIX = '_sharedid'; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; const SHAREDID_DEFAULT_STATE = false; -const GLVID = 887; +const GVLID = 887; -const storage = getStorageManager(GLVID, 'pubCommonId'); +const storage = getStorageManager(GVLID, 'pubCommonId'); /** * Store sharedid in either cookie or local storage @@ -160,7 +160,11 @@ export const pubCommonIdSubmodule = { * @type {string} */ name: MODULE_NAME, - + /** + * Vendor id of prebid + * @type {Number} + */ + gvlid: GVLID, /** * Return a callback function that calls the pixelUrl with id as a query parameter * @param pixelUrl From 840c04d07e3bb7b2b569d4479c10751f7d1e1773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Alberto=20Polo=20Garc=C3=ADa?= Date: Mon, 16 Nov 2020 09:22:01 +0100 Subject: [PATCH 164/190] [CriteoId] Add local storage check and migrate cookie check to use StorageManager API (#5953) * [CriteoId] Check if local storage is writable. * [CriteoId] Check cookies are writable using StorageManager Co-authored-by: Jesus Alberto Polo Garcia --- modules/criteoIdSystem.js | 17 ++++++----------- test/spec/modules/criteoIdSystem_spec.js | 3 +-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index 83bc773cb30..ac26d34d529 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -17,19 +17,11 @@ export const storage = getStorageManager(gvlid, bidderCode); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; -const cookieWriteableKey = 'cto_test_cookie'; const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; const pastDateString = new Date(0).toString(); const expirationString = new Date(utils.timestamp() + cookiesMaxAge).toString(); -function areCookiesWriteable() { - storage.setCookie(cookieWriteableKey, '1'); - const canWrite = storage.getCookie(cookieWriteableKey) === '1'; - storage.setCookie(cookieWriteableKey, '', pastDateString); - return canWrite; -} - function extractProtocolHost (url, returnOnlyHost = false) { const parsedUrl = utils.parseUrl(url, {noDecodeWholeURL: true}) return returnOnlyHost @@ -60,20 +52,22 @@ function getCriteoDataFromAllStorages() { } } -function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isPublishertagPresent, gdprString) { +function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) { const url = 'https://gum.criteo.com/sid/json?origin=prebid' + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + `${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` + `${areCookiesWriteable ? '&cw=1' : ''}` + - `${isPublishertagPresent ? '&pbt=1' : ''}` + `${isPublishertagPresent ? '&pbt=1' : ''}` + + `${isLocalStorageWritable ? '&lsw=1' : ''}`; return url; } function callCriteoUserSync(parsedCriteoData, gdprString) { - const cw = areCookiesWriteable(); + const cw = storage.cookiesAreEnabled(); + const lsw = storage.localStorageIsEnabled(); const topUrl = extractProtocolHost(getRefererInfo().referer); const domain = extractProtocolHost(document.location.href, true); const isPublishertagPresent = typeof criteo_pubtag !== 'undefined'; // eslint-disable-line camelcase @@ -83,6 +77,7 @@ function callCriteoUserSync(parsedCriteoData, gdprString) { domain, parsedCriteoData.bundle, cw, + lsw, isPublishertagPresent, gdprString ); diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index aa5807da0da..65e5aaf741d 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -71,7 +71,6 @@ describe('CriteoId module', function () { }); it('should call user sync url with the right params', function () { - getCookieStub.withArgs('cto_test_cookie').returns('1'); getCookieStub.withArgs('cto_bundle').returns('bundle'); window.criteo_pubtag = {} @@ -80,7 +79,7 @@ describe('CriteoId module', function () { ajaxBuilderStub.callsFake(mockResponse(undefined, ajaxStub)) criteoIdSubmodule.getId(); - const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&cw=1&pbt=1`; + const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&cw=1&pbt=1&lsw=1`; expect(ajaxStub.calledWith(expectedUrl)).to.be.true; From fb32955e72a99f1aea0b9c26b50f54da40dd5751 Mon Sep 17 00:00:00 2001 From: Anthony Lauzon Date: Mon, 16 Nov 2020 04:01:54 -0500 Subject: [PATCH 165/190] Audigent RTD configurable per-bidder segment mappings (#5903) * configurable per bidder segment mappings, optional segment caching, documentation update * rubicon->appnexus in docs * apply fpd convention, remove publisher defined mappers, add unit tests, fixes * segment mapping arr->dict * add fpd data to bids * update audigent tests * openrtb fpd data descriptions, update docs, more tests * update docs, update integration example * integration update * documentation table update * add examples of adding and overriding segment mappers * fix documentation * add contact verbiage * requestParams object type * brand rtd provider * update appnexus segment function * support numerical segments for appnexus * no default appnexus segments * update url * update gpt example * conditionally set bid segments for generic * fixes for integration example * update integration example * add appnexus handler override --- .../gpt/audigentSegments_example.html | 301 ------------------ .../gpt/haloRtdProvider_example.html | 149 +++++++++ modules/.submodules.json | 2 +- modules/audigentRtdProvider.js | 145 --------- modules/audigentRtdProvider.md | 76 ----- modules/haloRtdProvider.js | 198 ++++++++++++ modules/haloRtdProvider.md | 132 ++++++++ test/spec/modules/haloRtdProvider_spec.js | 220 +++++++++++++ 8 files changed, 700 insertions(+), 523 deletions(-) delete mode 100644 integrationExamples/gpt/audigentSegments_example.html create mode 100644 integrationExamples/gpt/haloRtdProvider_example.html delete mode 100644 modules/audigentRtdProvider.js delete mode 100644 modules/audigentRtdProvider.md create mode 100644 modules/haloRtdProvider.js create mode 100644 modules/haloRtdProvider.md create mode 100644 test/spec/modules/haloRtdProvider_spec.js diff --git a/integrationExamples/gpt/audigentSegments_example.html b/integrationExamples/gpt/audigentSegments_example.html deleted file mode 100644 index 1536ece9ab7..00000000000 --- a/integrationExamples/gpt/audigentSegments_example.html +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - - - - - - - - - - -

Audigent Segments Prebid

- -
- -
- -TDID: -
-
- -Audigent Segments: -
-
- - diff --git a/integrationExamples/gpt/haloRtdProvider_example.html b/integrationExamples/gpt/haloRtdProvider_example.html new file mode 100644 index 00000000000..7f9a34e55ee --- /dev/null +++ b/integrationExamples/gpt/haloRtdProvider_example.html @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + +

Audigent Segments Prebid

+ +
+ +
+ +Halo Id: +
+
+ +Audigent Segments (Appnexus): +
+
+ + diff --git a/modules/.submodules.json b/modules/.submodules.json index 9b523a0c73a..53caf7a0671 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -26,7 +26,7 @@ ], "rtdModule": [ "browsiRtdProvider", - "audigentRtdProvider", + "haloRtdProvider", "jwplayerRtdProvider", "reconciliationRtdProvider" ] diff --git a/modules/audigentRtdProvider.js b/modules/audigentRtdProvider.js deleted file mode 100644 index 09b76cac0df..00000000000 --- a/modules/audigentRtdProvider.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * This module adds audigent provider to the real time data module - * The {@link module:modules/realTimeData} module is required - * The module will fetch segments from audigent server - * @module modules/audigentRtdProvider - * @requires module:modules/realTimeData - */ -import {getGlobal} from '../src/prebidGlobal.js'; -import * as utils from '../src/utils.js'; -import {submodule} from '../src/hook.js'; -import {ajax} from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const storage = getStorageManager(); - -/** @type {string} */ -const MODULE_NAME = 'realTimeData'; -const SUBMODULE_NAME = 'audigent'; -const HALOID_LOCAL_NAME = 'auHaloId'; -const SEG_LOCAL_NAME = '__adgntseg'; - -/** - * decorate adUnits with segment data - * @param {adUnit[]} adUnits - * @param {Object} data - */ -function addSegmentData(adUnits, data) { - adUnits.forEach(adUnit => { - if (adUnit.hasOwnProperty('bids')) { - adUnit.bids.forEach(bid => { - bid.audigent_segments = data; - }) - } - }) - - return adUnits; -} - -/** - * segment retrieval from audigent's backends - * @param {Object} reqBidsConfigObj - * @param {function} onDone - * @param {Object} config - * @param {Object} userConsent - */ -function getSegments(reqBidsConfigObj, onDone, config, userConsent) { - const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; - - let jsonData = storage.getDataFromLocalStorage(SEG_LOCAL_NAME); - if (jsonData) { - let data = JSON.parse(jsonData); - if (data.audigent_segments) { - addSegmentData(adUnits, data.audigent_segments); - onDone(); - return; - } - } - - const userIds = (getGlobal()).getUserIds(); - if (typeof userIds == 'undefined' || userIds == null) { - onDone(); - return; - } - - let haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); - if (haloId) { - userIds.haloId = haloId; - getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); - } else { - var script = document.createElement('script') - script.type = 'text/javascript'; - - script.onload = function() { - userIds.haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); - getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); - } - - script.src = 'https://id.halo.ad.gt/api/v1/haloid'; - document.getElementsByTagName('head')[0].appendChild(script); - } -} - -/** - * async segment retrieval from audigent's backends - * @param {adUnit[]} adUnits - * @param {function} onDone - * @param {Object} config - * @param {Object} userConsent - * @param {Object} userIds - */ -function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) { - let reqParams = {} - if (typeof config == 'object' && config != null && Object.keys(config).length > 0) { - reqParams = config.params - } - - const url = `https://seg.halo.ad.gt/api/v1/rtb_segments`; - ajax(url, { - success: function (response, req) { - if (req.status === 200) { - try { - const data = JSON.parse(response); - if (data && data.audigent_segments) { - addSegmentData(adUnits, data.audigent_segments); - onDone(); - storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); - } else { - onDone(); - } - } catch (err) { - utils.logError('unable to parse audigent segment data'); - onDone(); - } - } else if (req.status === 204) { - // unrecognized partner config - onDone(); - } - }, - error: function () { - onDone(); - utils.logError('unable to get audigent segment data'); - } - }, - JSON.stringify({'userIds': userIds, 'config': reqParams}), - {contentType: 'application/json'} - ); -} - -/** - * module init - * @param {Object} config - * @return {boolean} - */ -export function init(config) { - return true; -} - -/** @type {RtdSubmodule} */ -export const audigentSubmodule = { - name: SUBMODULE_NAME, - getBidRequestData: getSegments, - init: init -}; - -submodule(MODULE_NAME, audigentSubmodule); diff --git a/modules/audigentRtdProvider.md b/modules/audigentRtdProvider.md deleted file mode 100644 index 03e647f651d..00000000000 --- a/modules/audigentRtdProvider.md +++ /dev/null @@ -1,76 +0,0 @@ -## Audigent Real-time Data Submodule - -Audigent is a next-generation data management platform and a first-of-a-kind -"data agency" containing some of the most exclusive content-consuming audiences -across desktop, mobile and social platforms. - -This real-time data module provides quality user segmentation that can be -attached to bid request objects destined for different SSPs in order to optimize -targeting. Audigent maintains a large database of first-party Tradedesk Unified -ID, Audigent Halo ID and other id provider mappings to various third-party -segment types that are utilizable across different SSPs. With this module, -these segments can be retrieved and supplied to the SSP in real-time during -the bid request cycle. - -### Usage - -Compile the audigent RTD module into your Prebid build: - -`gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` - -Configure Prebid to add the Audigent RTD Segment Handler: -``` -pbjs.setConfig( - ... - realTimeData: { - auctionDelay: 1000, - dataProviders: [ - { - name: "audigent", - waitForIt: true - } - ] - } - ... -} -``` - -Audigent segments will then be attached to each bid request objects in -`bid.realTimeData.audigent_segments` - -The format of the segments is a per-SSP mapping: - -``` -{ - 'appnexus': ['anseg1', 'anseg2'], - 'google': ['gseg1', 'gseg2'] -} -``` - -If a given SSP's API backend supports segment fields, they can then be -attached prior to the bid request being sent: - -``` -pbjs.requestBids({bidsBackHandler: addAudigentSegments}); - -function addAudigentSegments() { - for (i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - for (j = 0; j < adUnit.bids.length; j++) { - adUnit.bids[j].userId.lipb.segments = adUnit.bids[j].audigent_segments['rubicon']; - } - } -} -``` - -### Testing - -To view an example of available segments returned by Audigent's backends: - -`gulp serve --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter` - -and then point your browser at: - -`http://localhost:9999/integrationExamples/gpt/audigentSegments_example.html` - - diff --git a/modules/haloRtdProvider.js b/modules/haloRtdProvider.js new file mode 100644 index 00000000000..fff9e43ea2a --- /dev/null +++ b/modules/haloRtdProvider.js @@ -0,0 +1,198 @@ +/** + * This module adds audigent provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch segments from audigent server + * @module modules/audigentRtdProvider + * @requires module:modules/realTimeData + */ +import {getGlobal} from '../src/prebidGlobal.js'; +import * as utils from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'halo'; + +export const HALOID_LOCAL_NAME = 'auHaloId'; +export const SEG_LOCAL_NAME = '__adgntseg'; + +const set = (obj, path, val) => { + const keys = path.split('.'); + const lastKey = keys.pop(); + const lastObj = keys.reduce((obj, key) => obj[key] = obj[key] || {}, obj); + lastObj[lastKey] = lastObj[lastKey] || val; +}; + +/** bid adapter format segment augmentation functions */ +const segmentMappers = { + appnexus: function(bid, segments) { + set(bid, 'params.user.segments', []); + let appnexusSegments = []; + segments.forEach(segment => { + if (typeof segment.value != 'undefined' && segment.value != null) { + let appnexusSegment = {'id': segment.id, 'value': segment.value}; + appnexusSegments.push(appnexusSegment); + } + }) + bid.params.user.segments = bid.params.user.segments.concat(appnexusSegments); + }, + generic: function(bid, segments) { + bid.segments = bid.segments || []; + if (Array.isArray(bid.segments)) { + bid.segments = bid.segments.concat(segments); + } + } +} + +/** + * decorate adUnits with segment data + * @param {adUnit[]} adUnits + * @param {Object} data + */ +export function addSegmentData(adUnits, segmentData, config) { + adUnits.forEach(adUnit => { + if (adUnit.hasOwnProperty('bids')) { + adUnit.bids.forEach(bid => { + try { + set(bid, 'fpd.user.data', []); + if (Array.isArray(bid.fpd.user.data)) { + bid.fpd.user.data.forEach(fpdData => { + let segments = segmentData[fpdData.id] || segmentData[fpdData.name] || []; + fpdData.segment = (fpdData.segment || []).concat(segments); + }); + } + } catch (err) { + utils.logError(err.message); + } + + try { + if (config.params.mapSegments && config.params.mapSegments[bid.bidder] && segmentData[bid.bidder]) { + if (typeof config.params.mapSegments[bid.bidder] == 'function') { + config.params.mapSegments[bid.bidder](bid, segmentData[bid.bidder]); + } else if (segmentMappers[bid.bidder]) { + segmentMappers[bid.bidder](bid, segmentData[bid.bidder]); + } + } + } catch (err) { + utils.logError(err.message); + } + }); + } + }); + + return adUnits; +} + +/** + * segment retrieval from audigent's backends + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} config + * @param {Object} userConsent + */ +export function getSegments(reqBidsConfigObj, onDone, config, userConsent) { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + + if (config.params.segmentCache) { + let jsonData = storage.getDataFromLocalStorage(SEG_LOCAL_NAME); + + if (jsonData) { + let data = JSON.parse(jsonData); + + if (data.audigent_segments) { + addSegmentData(adUnits, data.audigent_segments, config); + onDone(); + return; + } + } + } + + const userIds = (getGlobal()).getUserIds(); + + let haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); + if (haloId) { + userIds.haloId = haloId; + getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); + } else { + var script = document.createElement('script') + script.type = 'text/javascript'; + + script.onload = function() { + userIds.haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); + getSegmentsAsync(adUnits, onDone, config, userConsent, userIds); + } + + script.src = 'https://id.halo.ad.gt/api/v1/haloid'; + document.getElementsByTagName('head')[0].appendChild(script); + } +} + +/** + * async segment retrieval from audigent's backends + * @param {adUnit[]} adUnits + * @param {function} onDone + * @param {Object} config + * @param {Object} userConsent + * @param {Object} userIds + */ +export function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) { + let reqParams = {}; + if (typeof config == 'object' && config != null) { + set(config, 'params.requestParams', {}); + reqParams = config.params.requestParams; + } + + const url = `https://seg.halo.ad.gt/api/v1/rtb_segments`; + ajax(url, { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.audigent_segments) { + addSegmentData(adUnits, data.audigent_segments, config); + onDone(); + storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); + } else { + onDone(); + } + } catch (err) { + utils.logError('unable to parse audigent segment data'); + onDone(); + } + } else if (req.status === 204) { + // unrecognized partner config + onDone(); + } + }, + error: function () { + onDone(); + utils.logError('unable to get audigent segment data'); + } + }, + JSON.stringify({'userIds': userIds, 'config': reqParams}), + {contentType: 'application/json'} + ); +} + +/** + * module init + * @param {Object} provider + * @param {Objkect} userConsent + * @return {boolean} + */ +function init(provider, userConsent) { + return true; +} + +/** @type {RtdSubmodule} */ +export const haloSubmodule = { + name: SUBMODULE_NAME, + getBidRequestData: getSegments, + init: init +}; + +submodule(MODULE_NAME, haloSubmodule); diff --git a/modules/haloRtdProvider.md b/modules/haloRtdProvider.md new file mode 100644 index 00000000000..2897a5917fa --- /dev/null +++ b/modules/haloRtdProvider.md @@ -0,0 +1,132 @@ +## Audigent Halo Real-time Data Submodule + +Audigent is a next-generation data management platform and a first-of-a-kind +"data agency" containing some of the most exclusive content-consuming audiences +across desktop, mobile and social platforms. + +This real-time data module provides quality user segmentation that can be +attached to bid request objects destined for different SSPs in order to optimize +targeting. Audigent maintains a large database of first-party Tradedesk Unified +ID, Audigent Halo ID and other id provider mappings to various third-party +segment types that are utilizable across different SSPs. With this module, +these segments can be retrieved and supplied to the SSP in real-time during +the bid request cycle. + +### Publisher Usage + +Compile the Halo RTD module into your Prebid build: + +`gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,appnexusBidAdapter` + +Add the Halo RTD provider to your Prebid config. For any adapters +that you would like to retrieve segments for, add a mapping in the 'mapSegments' +parameter. In this example we will configure publisher 1234 to retrieve +appnexus segments from Audigent. See the "Parameter Descriptions" below for +more detailed information of the configuration parameters. Currently, +OpenRTB compatible fpd data will be added for any bid adapter in the +"mapSegments" objects. Automated bid augmentation exists for some bidders. +Please work with your Audigent Prebid support team (prebid@audigent.com) on +which version of Prebid.js supports which bidders automatically. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: auctionDelay, + dataProviders: [ + { + name: "halo", + waitForIt: true, + params: { + mapSegments: { + appnexus: true, + }, + segmentCache: false, + requestParams: { + publisherId: 1234 + } + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the Halo `dataProviders` Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Always 'halo' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.mapSegments | Boolean | Dictionary of bidders you would like to supply Audigent segments for. Maps to boolean values, but also allows functions for custom mapping logic. The function signature is (bid, segments) => {}. | Required | +| params.segmentCache | Boolean | This parameter tells the Halo RTD module to attempt reading segments from a local storage cache instead of always requesting them from the Audigent server. | Optional. Defaults to false. | +| params.requestParams | Object | Publisher partner specific configuration options, such as optional publisher id and other segment query related metadata to be submitted to Audigent's backend with each request. Contact prebid@audigent.com for more information. | Optional | + +### Overriding & Adding Segment Mappers +As indicated above, it is possible to provide your own bid augmentation +functions. This is useful if you know a bid adapter's API supports segment +fields which aren't specifically being added to request objects in the Prebid +bid adapter. You can also override segment mappers by passing a function +instead of a boolean to the Halo RTD segment module. This might be useful +if you'd like to use custom logic to determine which segments are sent +to a specific backend. + +Please see the following example, which provides a function to modify bids for +a bid adapter called adBuzz and overrides the appnexus segment mapper. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: auctionDelay, + dataProviders: [ + { + name: "halo", + waitForIt: true, + params: { + mapSegments: { + // adding an adBuzz segment mapper + adBuzz: function(bid, segments) { + bid.params.adBuzzCustomSegments = []; + for (var i = 0; i < segments.length; i++) { + bid.params.adBuzzCustomSegments.push(segments[i].id); + } + }, + // overriding the appnexus segment mapper to exclude certain segments + appnexus: function(bid, segments) { + for (var i = 0; i < segments.length; i++) { + if (segments[i].id != 'exclude_segment') { + bid.params.user.segments.push(segments[i].id); + } + } + } + }, + segmentCache: false, + requestParams: { + publisherId: 1234 + } + } + } + ] + } + ... +} +``` + +More examples can be viewed in the haloRtdAdapter_spec.js tests. + +### Testing + +To view an example of available segments returned by Audigent's backends: + +`gulp serve --modules=userId,unifiedIdSystem,rtdModule,haloRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/haloRtdProvider_example.html` + + + + diff --git a/test/spec/modules/haloRtdProvider_spec.js b/test/spec/modules/haloRtdProvider_spec.js new file mode 100644 index 00000000000..17c2e19e1a6 --- /dev/null +++ b/test/spec/modules/haloRtdProvider_spec.js @@ -0,0 +1,220 @@ +import { HALOID_LOCAL_NAME, SEG_LOCAL_NAME, addSegmentData, getSegments, haloSubmodule, storage } from 'modules/haloRtdProvider.js'; +import { server } from 'test/mocks/xhr.js'; + +const responseHeader = {'Content-Type': 'application/json'}; + +describe('haloRtdProvider', function() { + describe('haloSubmodule', function() { + it('successfully instantiates', function () { + expect(haloSubmodule.init()).to.equal(true); + }); + }); + + describe('Add Segment Data', function() { + it('adds segment data', function() { + const config = { + params: { + mapSegments: { + 'appnexus': true, + 'generic': true + } + } + }; + + let adUnits = [ + { + bids: [ + // bid with existing segment data in bid obj and fpd + { + bidder: 'appnexus', + fpd: { + user: { + data: [ + { + id: 'appnexus', + segment: [ + { + id: 'apnseg0' + } + ] + } + ] + } + }, + params: { + user: { + segments: [{'id': 'apnseg0', 'value': 0}] + } + } + } + ] + }, + + // bids with fpd data definitions but without existing segment data + { + bids: [ + { + bidder: 'appnexus', + fpd: { + user: { + data: [ + { + id: 'appnexus' + } + ] + } + } + }, + { + bidder: 'generic', + fpd: { + user: { + data: [ + { + id: 'generic' + } + ] + } + } + } + ] + } + ]; + + const data = { + appnexus: [{id: 'apnseg1', value: 0}, {id: 'apnseg2', value: 2}, {id: 'apnseg3'}], + generic: [{id: 'seg1'}, {id: 'seg2'}, {id: 'seg3'}] + }; + + addSegmentData(adUnits, data, config); + + expect(adUnits[0].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'apnseg0'); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'apnseg1'); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'apnseg2'); + expect(adUnits[0].bids[0].fpd.user.data[0].segment[3]).to.have.deep.property('id', 'apnseg3'); + expect(adUnits[0].bids[0].params.user).to.have.deep.property('segments', [{'id': 'apnseg0', 'value': 0}, {'id': 'apnseg1', 'value': 0}, {'id': 'apnseg2', 'value': 2}]); + + expect(adUnits[1].bids[0].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'apnseg1'); + expect(adUnits[1].bids[0].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'apnseg2'); + expect(adUnits[1].bids[0].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'apnseg3'); + expect(adUnits[1].bids[0].params.user).to.have.deep.property('segments', [{'id': 'apnseg1', 'value': 0}, {'id': 'apnseg2', 'value': 2}]); + + expect(adUnits[1].bids[1].fpd.user.data[0].segment[0]).to.have.deep.property('id', 'seg1'); + expect(adUnits[1].bids[1].fpd.user.data[0].segment[1]).to.have.deep.property('id', 'seg2'); + expect(adUnits[1].bids[1].fpd.user.data[0].segment[2]).to.have.deep.property('id', 'seg3'); + expect(adUnits[1].bids[1].segments[0]).to.have.deep.property('id', 'seg1'); + expect(adUnits[1].bids[1].segments[1]).to.have.deep.property('id', 'seg2'); + expect(adUnits[1].bids[1].segments[2]).to.have.deep.property('id', 'seg3'); + }); + + it('allows mapper extensions and overrides', function() { + const config = { + params: { + mapSegments: { + generic: (bid, segments) => { + bid.overrideSegments = segments; + }, + newBidder: (bid, segments) => { + bid.newBidderSegments = segments; + } + } + } + }; + + let adUnits = [ + { + bids: [ {bidder: 'newBidder'}, {bidder: 'generic'} ] + } + ]; + + const data = { + newBidder: [{id: 'nbseg1', name: 'New Bidder Segment 1'}, {id: 'nbseg2', name: 'New Bidder Segment 2'}, {id: 'nbseg3', name: 'New Bidder Segment 3'}], + generic: [{id: 'seg1'}, {id: 'seg2'}, {id: 'seg3'}] + }; + + addSegmentData(adUnits, data, config); + + expect(adUnits[0].bids[0].newBidderSegments[0]).to.have.deep.property('id', 'nbseg1'); + expect(adUnits[0].bids[0].newBidderSegments[0]).to.have.deep.property('name', 'New Bidder Segment 1'); + expect(adUnits[0].bids[0].newBidderSegments[1]).to.have.deep.property('id', 'nbseg2'); + expect(adUnits[0].bids[0].newBidderSegments[1]).to.have.deep.property('name', 'New Bidder Segment 2'); + expect(adUnits[0].bids[0].newBidderSegments[2]).to.have.deep.property('id', 'nbseg3'); + expect(adUnits[0].bids[0].newBidderSegments[2]).to.have.deep.property('name', 'New Bidder Segment 3'); + + expect(adUnits[0].bids[1].overrideSegments[0]).to.have.deep.property('id', 'seg1'); + expect(adUnits[0].bids[1].overrideSegments[1]).to.have.deep.property('id', 'seg2'); + expect(adUnits[0].bids[1].overrideSegments[2]).to.have.deep.property('id', 'seg3'); + }); + }); + + describe('Get Segments', function() { + it('gets segment data from local storage cache', function() { + const config = { + params: { + segmentCache: true, + mapSegments: { + 'generic': true + } + } + }; + + let reqBidsConfigObj = { + adUnits: [ + { + bids: [{bidder: 'generic'}] + } + ] + }; + + const data = { + audigent_segments: { + generic: [{id: 'seg1'}] + } + }; + + storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data)); + + getSegments(reqBidsConfigObj, () => {}, config, {}); + + expect(reqBidsConfigObj.adUnits[0].bids[0].segments[0]).to.have.deep.property('id', 'seg1'); + }); + + it('gets segment data via async request', function() { + const config = { + params: { + segmentCache: false, + mapSegments: { + 'generic': true + }, + requestParams: { + 'publisherId': 1234 + } + } + }; + + let reqBidsConfigObj = { + adUnits: [ + { + bids: [{bidder: 'generic'}] + } + ] + }; + const data = { + audigent_segments: { + generic: [{id: 'seg1'}] + } + }; + + storage.setDataInLocalStorage(HALOID_LOCAL_NAME, 'haloid'); + getSegments(reqBidsConfigObj, () => {}, config, {}); + + let request = server.requests[0]; + let postData = JSON.parse(request.requestBody); + expect(postData.config).to.have.deep.property('publisherId', 1234); + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(reqBidsConfigObj.adUnits[0].bids[0].segments[0]).to.have.deep.property('id', 'seg1'); + }); + }); +}); From ac686e5c0e65cc9a8babf90cb8fabc9694064a94 Mon Sep 17 00:00:00 2001 From: r-schweitzer <50628828+r-schweitzer@users.noreply.github.com> Date: Mon, 16 Nov 2020 18:43:14 +0100 Subject: [PATCH 166/190] added feature/getNoBidsForAdUnitCode function (#5932) * added feature/getNoBidsForAdUnitCode function * added unit test --- src/prebid.js | 12 ++++++++++++ test/spec/api_spec.js | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/prebid.js b/src/prebid.js index 8bfb6024d7a..0f72ca878e5 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -258,6 +258,18 @@ $$PREBID_GLOBAL$$.getNoBids = function () { return getBids('getNoBids'); }; +/** + * This function returns the bids requests involved in an auction but not bid on or the specified adUnitCode + * @param {string} adUnitCode adUnitCode + * @alias module:pbjs.getNoBidsForAdUnitCode + * @return {Object} bidResponse object + */ + +$$PREBID_GLOBAL$$.getNoBidsForAdUnitCode = function (adUnitCode) { + const bids = auctionManager.getNoBids().filter(bid => bid.adUnitCode === adUnitCode); + return { bids }; +}; + /** * This function returns the bid responses at the given moment. * @alias module:pbjs.getBidResponses diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index 6f21eba7aaf..6d67565056f 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -43,10 +43,14 @@ describe('Publisher API', function () { assert.isFunction($$PREBID_GLOBAL$$.getBidResponses); }); - it('should have function $$PREBID_GLOBAL$$.getBidResponses', function () { + it('should have function $$PREBID_GLOBAL$$.getNoBids', function () { assert.isFunction($$PREBID_GLOBAL$$.getNoBids); }); + it('should have function $$PREBID_GLOBAL$$.getNoBidsForAdUnitCode', function () { + assert.isFunction($$PREBID_GLOBAL$$.getNoBidsForAdUnitCode); + }); + it('should have function $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode', function () { assert.isFunction($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode); }); From a4a194c0086ed3ea3a13315798fc7f05abef1442 Mon Sep 17 00:00:00 2001 From: Mehmet Can Kurt Date: Mon, 16 Nov 2020 13:51:29 -0800 Subject: [PATCH 167/190] update quantcastBidAdapter to make user sync flag non-constant (#5949) --- modules/quantcastBidAdapter.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 894bb991a71..347ebc2ff55 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -101,6 +101,8 @@ function checkTCFv2(tcData) { return !!(vendorConsent && purposeConsent); } +let hasUserSynced = false; + /** * The documentation for Prebid.js Adapter 1.0 can be found at link below, * http://prebid.org/dev-docs/bidder-adapter-1.html @@ -109,7 +111,6 @@ export const spec = { code: BIDDER_CODE, GVLID: 11, supportedMediaTypes: ['banner', 'video'], - hasUserSynced: false, /** * Verify the `AdUnits.bids` response with `true` for valid request and `false` @@ -271,7 +272,7 @@ export const spec = { }, getUserSyncs(syncOptions, serverResponses) { const syncs = [] - if (!this.hasUserSynced && syncOptions.pixelEnabled) { + if (!hasUserSynced && syncOptions.pixelEnabled) { const responseWithUrl = find(serverResponses, serverResponse => utils.deepAccess(serverResponse.body, 'userSync.url') ); @@ -283,12 +284,12 @@ export const spec = { url: url }); } - this.hasUserSynced = true; + hasUserSynced = true; } return syncs; }, resetUserSync() { - this.hasUserSynced = false; + hasUserSynced = false; } }; From a126dcf06fc320f180e97a8ffef87c5b1d0744fb Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Tue, 17 Nov 2020 04:52:26 +0700 Subject: [PATCH 168/190] Update for Qwarry bid adapter: onBidWon hotfix (#5955) * 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 Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 4 +- test/spec/modules/qwarryBidAdapter_spec.js | 67 +++++++++++++--------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index 0c6dee9be69..7c2ec0f085b 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -66,7 +66,9 @@ export const spec = { onBidWon: function (bid) { if (bid.winUrl) { - ajax(bid.winUrl, null); + const cpm = bid.cpm; + const winUrl = bid.winUrl.replace(/\$\{AUCTION_PRICE\}/, cpm); + ajax(winUrl, null); return true; } return false; diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 02fe9c4538b..91e3cf4bfdf 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -11,33 +11,37 @@ const REQUEST = { } } -const BIDDER_BANNER_RESPONSE = {'prebidResponse': [{ - 'ad': '
test
', - 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46d', - 'cpm': 900.5, - 'currency': 'USD', - 'width': 640, - 'height': 480, - 'ttl': 300, - 'creativeId': 1, - 'netRevenue': true, - 'winUrl': 'http://test.com', - 'format': 'banner' -}]} - -const BIDDER_VIDEO_RESPONSE = {'prebidResponse': [{ - 'ad': 'vast', - 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46z', - 'cpm': 800.4, - 'currency': 'USD', - 'width': 1024, - 'height': 768, - 'ttl': 200, - 'creativeId': 2, - 'netRevenue': true, - 'winUrl': 'http://test.com', - 'format': 'video' -}]} +const BIDDER_BANNER_RESPONSE = { + 'prebidResponse': [{ + 'ad': '
test
', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46d', + 'cpm': 900.5, + 'currency': 'USD', + 'width': 640, + 'height': 480, + 'ttl': 300, + 'creativeId': 1, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'format': 'banner' + }] +} + +const BIDDER_VIDEO_RESPONSE = { + 'prebidResponse': [{ + 'ad': 'vast', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46z', + 'cpm': 800.4, + 'currency': 'USD', + 'width': 1024, + 'height': 768, + 'ttl': 200, + 'creativeId': 2, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'format': 'video' + }] +} const BIDDER_NO_BID_RESPONSE = '' @@ -120,4 +124,13 @@ describe('qwarryBidAdapter', function () { expect(result).to.deep.equal([]) }) }) + + describe('onBidWon', function () { + it('handles banner win: should get true', function () { + const win = BIDDER_BANNER_RESPONSE.prebidResponse[0] + const bidWonResult = spec.onBidWon(win) + + expect(bidWonResult).to.equal(true) + }) + }) }) From 1664f4e2648ea90a93b25c76510858c0de30fa69 Mon Sep 17 00:00:00 2001 From: jsut Date: Mon, 16 Nov 2020 16:53:02 -0500 Subject: [PATCH 169/190] FIX typo's in CONTRIBUTING (#5956) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 962e057fbc5..606d26cd25a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Contributions are always welcome. To contribute, [fork](https://help.github.com/ commit your changes, and [open a pull request](https://help.github.com/articles/using-pull-requests/) against the master branch. -Pull requests must have 80% code coverage before beign considered for merge. +Pull requests must have 80% code coverage before being considered for merge. Additional details about the process can be found [here](./PR_REVIEW.md). There are more details available if you'd like to contribute a [bid adapter](https://docs.prebid.org/dev-docs/bidder-adaptor.html) or [analytics adapter](https://docs.prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html). @@ -59,7 +59,7 @@ When you are adding code to Prebid.js, or modifying code that isn't covered by a Prebid.js already has many tests. Read them to see how Prebid.js is tested, and for inspiration: - Look in `test/spec` and its subdirectories -- Tests for bidder adaptors are located in `test/spec/modules` +- Tests for bidder adapters are located in `test/spec/modules` A test module might have the following general structure: From 5aa0fe65061578446c59d6659e799c5c3df5b3bc Mon Sep 17 00:00:00 2001 From: jsut Date: Mon, 16 Nov 2020 16:54:03 -0500 Subject: [PATCH 170/190] Add a note to the readme about adapter aliases (#5968) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 44882570d89..40df62ccee4 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ This runs some code quality checks, starts a web server at `http://localhost:999 ### Build Optimization -The standard build output contains all the available modules from within the `modules` folder. +The standard build output contains all the available modules from within the `modules` folder. Note, however that there are bid adapters which support multiple bidders through aliases, so if you don't see a file in modules for a bid adapter, you may need to grep the repository to find the name of the module you need to include. You might want to exclude some/most of them from the final bundle. To make sure the build only includes the modules you want, you can specify the modules to be included with the `--modules` CLI argument. From c04bd6d70c1107ebfd69f7c838e94485408e454f Mon Sep 17 00:00:00 2001 From: Mehmet Can Kurt Date: Mon, 16 Nov 2020 17:07:08 -0800 Subject: [PATCH 171/190] update quantcastBidAdapter to pass quantcast fpa in the bid request (#5947) * update quantcastBidAdapter to pass quantcast fpa in the bid request * remove empty line from lunamediahBidAdapter * pass quantcast vendor id when obtaining storage manager --- modules/quantcastBidAdapter.js | 14 ++++++++-- test/spec/modules/quantcastBidAdapter_spec.js | 27 ++++++++++++++----- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 347ebc2ff55..e9541edb534 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -1,6 +1,7 @@ import * as utils from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import find from 'core-js-pure/features/array/find.js'; @@ -18,6 +19,9 @@ export const QUANTCAST_TEST_PUBLISHER = 'test-publisher'; export const QUANTCAST_TTL = 4; export const QUANTCAST_PROTOCOL = 'https'; export const QUANTCAST_PORT = '8443'; +export const QUANTCAST_FPA = '__qca'; + +export const storage = getStorageManager(QUANTCAST_VENDOR_ID, BIDDER_CODE); function makeVideoImp(bid) { const video = {}; @@ -101,6 +105,11 @@ function checkTCFv2(tcData) { return !!(vendorConsent && purposeConsent); } +function getQuantcastFPA() { + let fpa = storage.getCookie(QUANTCAST_FPA) + return fpa || '' +} + let hasUserSynced = false; /** @@ -109,7 +118,7 @@ let hasUserSynced = false; */ export const spec = { code: BIDDER_CODE, - GVLID: 11, + GVLID: QUANTCAST_VENDOR_ID, supportedMediaTypes: ['banner', 'video'], /** @@ -189,7 +198,8 @@ export const spec = { uspSignal: uspConsent ? 1 : 0, uspConsent, coppa: config.getConfig('coppa') === true ? 1 : 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: getQuantcastFPA() }; const data = JSON.stringify(requestData); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index caa554c8cd8..5b4e7963e60 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -7,7 +7,8 @@ import { QUANTCAST_TEST_PUBLISHER, QUANTCAST_PROTOCOL, QUANTCAST_PORT, - spec as qcSpec + spec as qcSpec, + storage } from '../../../modules/quantcastBidAdapter.js'; import { newBidder } from '../../../src/adapters/bidderFactory.js'; import { parseUrl } from 'src/utils.js'; @@ -42,6 +43,8 @@ describe('Quantcast adapter', function () { canonicalUrl: 'http://example.com/hello.html' } }; + + storage.setCookie('__qca', '', 'Thu, 01 Jan 1970 00:00:00 GMT'); }); function setupVideoBidRequest(videoParams) { @@ -140,7 +143,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; it('sends banner bid requests contains all the required parameters', function () { @@ -208,7 +212,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); @@ -244,7 +249,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); @@ -276,7 +282,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); @@ -340,7 +347,8 @@ describe('Quantcast adapter', function () { gdprSignal: 0, uspSignal: 0, coppa: 0, - prebidJsVersion: '$prebid.version$' + prebidJsVersion: '$prebid.version$', + fpa: '' }; expect(requests[0].data).to.equal(JSON.stringify(expectedBidRequest)); @@ -584,6 +592,13 @@ describe('Quantcast adapter', function () { expect(parsed.uspConsent).to.equal('consentString'); }); + it('propagates Quantcast first-party cookie (fpa)', function() { + storage.setCookie('__qca', 'P0-TestFPA'); + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + expect(parsed.fpa).to.equal('P0-TestFPA'); + }); + describe('propagates coppa', function() { let sandbox; beforeEach(() => { From fe7acf8a62e9f253c66a23358e4f5bc7906a1536 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Tue, 17 Nov 2020 06:33:57 +0100 Subject: [PATCH 172/190] improve console ogging of user id module by listing all user id modules that have been enabled (#5975) --- modules/userId/index.js | 2 +- test/spec/modules/userId_spec.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 9ef4da0f96f..0923a92f516 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -667,7 +667,7 @@ function updateSubmodules() { if (!addedUserIdHook && submodules.length) { // priority value 40 will load after consentManagement with a priority of 50 getGlobal().requestBids.before(requestBidsHook, 40); - utils.logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules`); + utils.logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); addedUserIdHook = true; } } diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index d5ed96a5bc1..887e1f45640 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -443,7 +443,7 @@ describe('User ID', function () { setSubmoduleRegistry([pubCommonIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); }); @@ -506,7 +506,7 @@ describe('User ID', function () { init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); it('config with 13 configurations should result in 13 submodules add', function () { @@ -553,7 +553,7 @@ describe('User ID', function () { }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 13 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 13 submodules'); }); it('config syncDelay updates module correctly', function () { From f6e34657e2b1347677b87afbdadd73a6195b1cb3 Mon Sep 17 00:00:00 2001 From: John Salis Date: Tue, 17 Nov 2020 01:00:18 -0500 Subject: [PATCH 173/190] Add IdentityLink support to Beachfront adapter (#5977) * add IdentityLink support to beachfront adapter * bump adapter version Co-authored-by: John Salis --- modules/beachfrontBidAdapter.js | 52 ++++++++++++++----- .../spec/modules/beachfrontBidAdapter_spec.js | 28 ++++++++++ 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index f0e28956e5d..5f0a4b03a04 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -6,7 +6,7 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; -const ADAPTER_VERSION = '1.12'; +const ADAPTER_VERSION = '1.13'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; @@ -17,6 +17,11 @@ export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/l export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'skip', 'skipmin', 'skipafter']; export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; +export const SUPPORTED_USER_IDS = [ + { key: 'tdid', source: 'adserver.org', rtiPartner: 'TDID', queryParam: 'tdid' }, + { key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' } +]; + let appId = ''; export const spec = { @@ -257,6 +262,29 @@ function getTopWindowReferrer() { } } +function getEids(bid) { + return SUPPORTED_USER_IDS + .map(getUserId(bid)) + .filter(x => x); +} + +function getUserId(bid) { + return ({ key, source, rtiPartner }) => { + let id = bid.userId && bid.userId[key]; + return id ? formatEid(id, source, rtiPartner) : null; + }; +} + +function formatEid(id, source, rtiPartner) { + return { + source, + uids: [{ + id, + ext: { rtiPartner } + }] + }; +} + function getVideoTargetingParams(bid) { const result = {}; const excludeProps = ['playerSize', 'context', 'w', 'h']; @@ -281,6 +309,7 @@ function createVideoRequestData(bid, bidderRequest) { let bidfloor = getVideoBidParam(bid, 'bidfloor'); let tagid = getVideoBidParam(bid, 'tagid'); let topLocation = getTopWindowLocation(bidderRequest); + let eids = getEids(bid); let payload = { isPrebid: true, appId: appId, @@ -329,16 +358,8 @@ function createVideoRequestData(bid, bidderRequest) { payload.user.ext.consent = consentString; } - if (bid.userId && bid.userId.tdid) { - payload.user.ext.eids = [{ - source: 'adserver.org', - uids: [{ - id: bid.userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }]; + if (eids.length > 0) { + payload.user.ext.eids = eids; } let connection = navigator.connection || navigator.webkitConnection; @@ -385,9 +406,12 @@ function createBannerRequestData(bids, bidderRequest) { payload.gdprConsent = consentString; } - if (bids[0] && bids[0].userId && bids[0].userId.tdid) { - payload.tdid = bids[0].userId.tdid; - } + SUPPORTED_USER_IDS.forEach(({ key, queryParam }) => { + let id = bids[0] && bids[0].userId && bids[0].userId[key]; + if (id) { + payload[queryParam] = id; + } + }); return payload; } diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index 587596eaa5c..aa952d088a7 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -295,6 +295,24 @@ describe('BeachfrontAdapter', function () { }] }); }); + + it('must add the IdentityLink ID to the request', () => { + const idl_env = '4321'; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + bidRequest.userId = { idl_env }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.user.ext.eids[0]).to.deep.equal({ + source: 'liveramp.com', + uids: [{ + id: idl_env, + ext: { + rtiPartner: 'idl' + } + }] + }); + }); }); describe('for banner bids', function () { @@ -435,6 +453,16 @@ describe('BeachfrontAdapter', function () { const data = requests[0].data; expect(data.tdid).to.equal(tdid); }); + + it('must add the IdentityLink ID to the request', () => { + const idl_env = '4321'; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + bidRequest.userId = { idl_env }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.idl).to.equal(idl_env); + }); }); describe('for multi-format bids', function () { From f47287eef645c9c20b62aeaedf96a7893c602036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Udi=20Talias=20=E2=9A=9B=EF=B8=8F?= Date: Tue, 17 Nov 2020 08:18:40 +0200 Subject: [PATCH 174/190] Vidazoo Adapter: Feature/spec-gvlid (#5980) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): expose spec gvlid Co-authored-by: roman --- modules/vidazooBidAdapter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 4b3b1767cec..7fc6e3a5395 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -3,7 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; -const GLVID = 744; +const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; const BIDDER_CODE = 'vidazoo'; const BIDDER_VERSION = '1.0.0'; @@ -24,7 +24,7 @@ export const SUPPORTED_ID_SYSTEMS = { 'pubcid': 1, 'tdid': 1, }; -const storage = getStorageManager(GLVID); +const storage = getStorageManager(GVLID); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.cootlogix.com`; @@ -266,6 +266,7 @@ export function tryParseJSON(value) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, version: BIDDER_VERSION, supportedMediaTypes: [BANNER], isBidRequestValid, From 842f21c86fc3d7dba5bf5707140621c91243f3f6 Mon Sep 17 00:00:00 2001 From: jdwieland8282 Date: Tue, 17 Nov 2020 03:26:16 -0700 Subject: [PATCH 175/190] Update sharedIdSystem.js with GVLID (#5988) adding GVLID variable and spec definition, will come back and add it to getStorageManager once if I find that function referenced in the sharedid module. --- modules/sharedIdSystem.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index cdd840c4f54..49cac46f1df 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -21,6 +21,7 @@ const TIME_MAX = Math.pow(2, 48) - 1; const TIME_LEN = 10; const RANDOM_LEN = 16; const id = factory(); +const GVLID = 887; /** * Constructs cookie value * @param value @@ -283,6 +284,11 @@ export const sharedIdSubmodule = { */ name: MODULE_NAME, + /** + * Vendor id of Prebid + * @type {Number} + */ + gvlid: GVLID, /** * decode the stored id value for passing to bid requests * @function From 18afadd0ed90cf16f4989f300ae9468623f56176 Mon Sep 17 00:00:00 2001 From: Elijah Valenciano Date: Tue, 17 Nov 2020 06:51:07 -0500 Subject: [PATCH 176/190] FreeWheel SSP - Added GDPR to userSync (#5969) --- modules/freewheel-sspBidAdapter.js | 13 ++++++++-- .../modules/freewheel-sspBidAdapter_spec.js | 26 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index dce678362cb..53f490a0a3c 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -407,11 +407,20 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { + var gdprParams = ''; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + } + } + if (syncOptions && syncOptions.pixelEnabled) { return [{ type: 'image', - url: USER_SYNC_URL + url: USER_SYNC_URL + gdprParams }]; } else { return []; diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 3047b635d13..c44d7908ba8 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -152,6 +152,19 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.playerSize).to.equal('300x600'); expect(payload._fw_gdpr_consent).to.exist.and.to.be.a('string'); expect(payload._fw_gdpr_consent).to.equal(gdprConsentString); + + let gdprConsent = { + 'gdprApplies': true, + 'consentString': gdprConsentString + } + let syncOptions = { + 'pixelEnabled': true + } + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + expect(userSyncs).to.deep.equal([{ + type: 'image', + url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' + }]); }); }) @@ -226,6 +239,19 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.playerSize).to.equal('300x600'); expect(payload._fw_gdpr_consent).to.exist.and.to.be.a('string'); expect(payload._fw_gdpr_consent).to.equal(gdprConsentString); + + let gdprConsent = { + 'gdprApplies': true, + 'consentString': gdprConsentString + } + let syncOptions = { + 'pixelEnabled': true + } + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + expect(userSyncs).to.deep.equal([{ + type: 'image', + url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' + }]); }); }) From b73ab25d9a5d1d33a6a377bf2413b07fa1fca5bf Mon Sep 17 00:00:00 2001 From: relaido <63339139+relaido@users.noreply.github.com> Date: Tue, 17 Nov 2020 21:03:31 +0900 Subject: [PATCH 177/190] Fix request size validate (#5951) * add relaido adapter * remove event listener * fixed UserSyncs and e.data * fix conflicts * fixed request flow use cookie instead of local storage validate video size Co-authored-by: ishigami_shingo Co-authored-by: cmertv-sishigami Co-authored-by: t_bun --- modules/relaidoBidAdapter.js | 38 +++++---------------- test/spec/modules/relaidoBidAdapter_spec.js | 14 ++++---- 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index bc2854de40b..c77afbe6ec5 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -6,17 +6,13 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.0.1'; +const ADAPTER_VERSION = '1.0.2'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; const storage = getStorageManager(); function isBidRequestValid(bid) { - if (!utils.isSafariBrowser() && !hasUuid()) { - utils.logWarn('uuid is not found.'); - return false; - } if (!utils.deepAccess(bid, 'params.placementId')) { utils.logWarn('placementId param is reqeuired.'); return false; @@ -64,7 +60,7 @@ function buildRequests(validBidRequests, bidderRequest) { }; if (hasVideoMediaType(bidRequest)) { - const playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + const playerSize = getValidSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize')); payload.width = playerSize[0][0]; payload.height = playerSize[0][1]; } else if (hasBannerMediaType(bidRequest)) { @@ -101,10 +97,6 @@ function interpretResponse(serverResponse, bidRequest) { return []; } - if (body.uuid) { - storage.setDataInLocalStorage(UUID_KEY, body.uuid); - } - const playerUrl = bidRequest.player || body.playerUrl; const mediaType = bidRequest.mediaType || VIDEO; @@ -141,7 +133,6 @@ function getUserSyncs(syncOptions, serverResponses) { if (serverResponses.length > 0) { syncUrl = utils.deepAccess(serverResponses, '0.body.syncUrl') || syncUrl; } - receiveMessage(); return [{ type: 'iframe', url: syncUrl @@ -219,17 +210,6 @@ function outstreamRender(bid) { }); } -function receiveMessage() { - window.addEventListener('message', setUuid); -} - -function setUuid(e) { - if (utils.isPlainObject(e.data) && e.data.relaido_uuid) { - storage.setDataInLocalStorage(UUID_KEY, e.data.relaido_uuid); - window.removeEventListener('message', setUuid); - } -} - function isBannerValid(bid) { if (!isMobile()) { return false; @@ -242,8 +222,8 @@ function isBannerValid(bid) { } function isVideoValid(bid) { - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - if (playerSize && utils.isArray(playerSize) && playerSize.length > 0) { + const playerSize = getValidSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize')); + if (playerSize.length > 0) { const context = utils.deepAccess(bid, 'mediaTypes.video.context'); if (context && context === 'outstream') { return true; @@ -252,12 +232,12 @@ function isVideoValid(bid) { return false; } -function hasUuid() { - return !!storage.getDataFromLocalStorage(UUID_KEY); -} - function getUuid() { - return storage.getDataFromLocalStorage(UUID_KEY) || ''; + const id = storage.getCookie(UUID_KEY) + if (id) return id; + const newId = utils.generateUUID(); + storage.setCookie(UUID_KEY, newId); + return newId; } export function isMobile() { diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 42818232cda..65dcd9b7db7 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -1,16 +1,21 @@ import { expect } from 'chai'; import { spec } from 'modules/relaidoBidAdapter.js'; import * as utils from 'src/utils.js'; +import { getStorageManager } from '../../../src/storageManager.js'; const UUID_KEY = 'relaido_uuid'; const DEFAULT_USER_AGENT = window.navigator.userAgent; const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1'; +const relaido_uuid = 'hogehoge'; const setUADefault = () => { window.navigator.__defineGetter__('userAgent', function () { return DEFAULT_USER_AGENT }) }; const setUAMobile = () => { window.navigator.__defineGetter__('userAgent', function () { return MOBILE_USER_AGENT }) }; +const storage = getStorageManager(); +storage.setCookie(UUID_KEY, relaido_uuid); + describe('RelaidoAdapter', function () { - const relaido_uuid = 'hogehoge'; + window.document.cookie = `${UUID_KEY}=${relaido_uuid}` let bidRequest; let bidderRequest; let serverResponse; @@ -65,7 +70,6 @@ describe('RelaidoAdapter', function () { height: bidRequest.mediaTypes.video.playerSize[0][1], mediaType: 'video', }; - localStorage.setItem(UUID_KEY, relaido_uuid); }); describe('spec.isBidRequestValid', function () { @@ -140,12 +144,6 @@ describe('RelaidoAdapter', function () { setUADefault(); }); - it('should return false when the uuid are missing', function () { - localStorage.removeItem(UUID_KEY); - const result = !!(utils.isSafariBrowser()); - expect(spec.isBidRequestValid(bidRequest)).to.equal(result); - }); - it('should return false when the placementId params are missing', function () { bidRequest.params.placementId = undefined; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); From 96989ca4b8d36917e87caec1208dbf0a92e87537 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 17 Nov 2020 06:29:42 -0800 Subject: [PATCH 178/190] Prebid Server Bid Adapter: Expose errors and server response times always (#5866) * Expose pbs reported errors Expose serverLatencyMillis always * clean up logic for other weird edge case * review comment for make function * not sure how parenthesis got there --- modules/prebidServerBidAdapter/index.js | 17 ++++++++++ modules/rubiconAnalyticsAdapter.js | 32 ++++++++++++++++--- .../modules/rubiconAnalyticsAdapter_spec.js | 1 + 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index b153d0bf8db..7c7962781d2 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -369,6 +369,18 @@ function addWurl(auctionId, adId, wurl) { } } +function getPbsResponseData(bidderRequests, response, pbsName, pbjsName) { + const bidderValues = utils.deepAccess(response, `ext.${pbsName}`); + if (bidderValues) { + Object.keys(bidderValues).forEach(bidder => { + let biddersReq = find(bidderRequests, bidderReq => bidderReq.bidderCode === bidder); + if (biddersReq) { + biddersReq[pbjsName] = bidderValues[bidder]; + } + }); + } +} + /** * @param {string} auctionId * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() @@ -676,6 +688,9 @@ const OPEN_RTB_PROTOCOL = { interpretResponse(response, bidderRequests) { const bids = []; + [['errors', 'serverErrors'], ['responsetimemillis', 'serverResponseTimeMs']] + .forEach(info => getPbsResponseData(bidderRequests, response, info[0], info[1])) + if (response.seatbid) { // a seatbid object contains a `bid` array and a `seat` string response.seatbid.forEach(seatbid => { @@ -698,6 +713,8 @@ const OPEN_RTB_PROTOCOL = { bidObject.cpm = cpm; + // temporarily leaving attaching it to each bidResponse so no breaking change + // BUT: this is a flat map, so it should be only attached to bidderRequest, a the change above does let serverResponseTimeMs = utils.deepAccess(response, ['ext', 'responsetimemillis', seatbid.seat].join('.')); if (bidRequest && serverResponseTimeMs) { bidRequest.serverResponseTimeMs = serverResponseTimeMs; diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 4011232ae3b..6f00e9536d9 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -13,6 +13,14 @@ const COOKIE_NAME = 'rpaSession'; const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours +const pbsErrorMap = { + 1: 'timeout-error', + 2: 'input-error', + 3: 'connect-error', + 4: 'request-error', + 999: 'generic-error' +} + let prebidGlobal = getGlobal(); const { EVENTS: { @@ -631,10 +639,22 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { bid.bidResponse = parseBidResponse(args, bid.bidResponse); break; case BIDDER_DONE: + const serverError = utils.deepAccess(args, 'serverErrors.0'); + const serverResponseTimeMs = args.serverResponseTimeMs; args.bids.forEach(bid => { let cachedBid = cache.auctions[bid.auctionId].bids[bid.bidId || bid.requestId]; if (typeof bid.serverResponseTimeMs !== 'undefined') { cachedBid.serverLatencyMillis = bid.serverResponseTimeMs; + } else if (serverResponseTimeMs && bid.source === 's2s') { + cachedBid.serverLatencyMillis = serverResponseTimeMs; + } + // if PBS said we had an error, and this bid has not been processed by BID_RESPONSE YET + if (serverError && (!cachedBid.status || ['no-bid', 'error'].indexOf(cachedBid.status) !== -1)) { + cachedBid.status = 'error'; + cachedBid.error = { + code: pbsErrorMap[serverError.code] || pbsErrorMap[999], + description: serverError.message + } } if (!cachedBid.status) { cachedBid.status = 'no-bid'; @@ -675,10 +695,14 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { args.forEach(badBid => { let auctionCache = cache.auctions[badBid.auctionId]; let bid = auctionCache.bids[badBid.bidId || badBid.requestId]; - bid.status = 'error'; - bid.error = { - code: 'timeout-error' - }; + // might be set already by bidder-done, so do not overwrite + if (bid.status !== 'error') { + bid.status = 'error'; + bid.error = { + code: 'timeout-error', + message: 'marked by prebid.js as timeout' // will help us diff if timeout was set by PBS or PBJS + }; + } }); break; } diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 7c552570da6..d65083ce480 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -310,6 +310,7 @@ const MOCK = { ], BIDDER_DONE: { 'bidderCode': 'rubicon', + 'serverResponseTimeMs': 42, 'bids': [ BID, Object.assign({}, BID2, { From 71e9cc7b48b736ab6671acf6a5ad7c17ac4460e5 Mon Sep 17 00:00:00 2001 From: Ignat Khaylov Date: Wed, 18 Nov 2020 09:29:05 +0300 Subject: [PATCH 179/190] Between: schain support was added (#5982) Co-authored-by: Ignat Khaylov --- modules/betweenBidAdapter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index fb3fcdb8d89..0ed05717391 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -59,6 +59,10 @@ export const spec = { } } + if (i.schain) { + params.schain = encodeToBase64WebSafe(JSON.stringify(i.schain)); + } + if (refInfo && refInfo.referer) params.ref = refInfo.referer; if (gdprConsent) { @@ -166,6 +170,10 @@ function getTz() { return new Date().getTimezoneOffset(); } +function encodeToBase64WebSafe(string) { + return btoa(string).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +} + /* function get_pubdata(adds) { if (adds !== undefined && adds.pubdata !== undefined) { From efbc9d308b6e6451d234c7548e6eb1821044e413 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 17 Nov 2020 22:38:58 -0800 Subject: [PATCH 180/190] fix source lowercase bug (#5989) --- modules/rubiconAnalyticsAdapter.js | 4 ++-- test/spec/modules/rubiconAnalyticsAdapter_spec.js | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 6f00e9536d9..85b6596ba12 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -125,7 +125,7 @@ function sendMessage(auctionId, bidWonId) { if (source) { return source; } - return serverConfig && Array.isArray(serverConfig.bidders) && serverConfig.bidders.indexOf(bid.bidder) !== -1 + return serverConfig && Array.isArray(serverConfig.bidders) && serverConfig.bidders.some(s2sBidder => s2sBidder.toLowerCase() === bid.bidder) !== -1 ? 'server' : 'client' }, 'clientLatencyMillis', @@ -533,7 +533,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { 'bidder', bidder => bidder.toLowerCase(), 'bidId', 'status', () => 'no-bid', // default a bid to no-bid until response is recieved or bid is timed out - 'finalSource as source', + 'source', () => formatSource(bid.src), 'params', (params, bid) => { switch (bid.bidder) { // specify bidder params we want here diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index d65083ce480..9e343d07dd5 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -255,7 +255,8 @@ const MOCK = { 'sizes': [[640, 480]], 'bidId': '2ecff0db240757', 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client' }, { 'bidder': 'rubicon', @@ -279,7 +280,8 @@ const MOCK = { 'sizes': [[1000, 300], [970, 250], [728, 90]], 'bidId': '3bd4ebb1c900e2', 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 's2s' } ], 'auctionStart': 1519149536560, From da87e57527ea3c26c6678f1a223794555706b4fd Mon Sep 17 00:00:00 2001 From: SmartyAdman <59048845+SmartyAdman@users.noreply.github.com> Date: Wed, 18 Nov 2020 08:44:49 +0200 Subject: [PATCH 181/190] Add consent to sync url (#5981) * Add Adman bid adapter * Add supportedMediaTypes property * Update ADman Media bidder adapter * Remove console.log * Fix typo * revert package-json.lock * Delete package-lock.json * back to original package-lock.json * catch pbjs error * catch pbjs error * catch pbjs error * log * remove eu url * remove eu url * remove eu url * remove eu url * remove eu url * Update admanBidAdapter.js add consnet to sync url * Update admanBidAdapter.js fix import * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js check consent object data availability Co-authored-by: minoru katogi Co-authored-by: minoru katogi Co-authored-by: ADman Media Co-authored-by: SmartyAdman --- modules/admanBidAdapter.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index 5dc3412ee66..2e4091e7a24 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -95,10 +95,21 @@ export const spec = { return response; }, - getUserSyncs: (syncOptions, serverResponses) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncUrl = URL_SYNC + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr==0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } return [{ type: 'image', - url: URL_SYNC + url: syncUrl }]; } From c308898cddc80d7669c3d2d318e13ef5cb366780 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Wed, 18 Nov 2020 07:58:56 +0100 Subject: [PATCH 182/190] add provider as an option in id5 config params to identity prebid identity wrappers (#5983) --- modules/id5IdSystem.js | 1 + modules/id5IdSystem.md | 3 ++- test/spec/modules/id5IdSystem_spec.js | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 5a1fc69a758..a1596e96fcd 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -89,6 +89,7 @@ export const id5IdSubmodule = { 'nbPage': incrementNb(config.params.partner), 'o': 'pbjs', 'pd': config.params.pd || '', + 'provider': config.params.provider || '', 'rf': referer.referer, 's': signature, 'top': referer.reachedTop ? 1 : 0, diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 80ba451b235..e5e3969c19c 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -45,10 +45,11 @@ pbjs.setConfig({ | params | Required | Object | Details for the ID5 Universal ID. | | | params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | | params.pd | Optional | String | Publisher-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/x/BIAZ) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | +| params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | | storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | | storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | | storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | | storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | | storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | -**ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). \ No newline at end of file +**ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index bfc41e5f5e8..adffca6dbe5 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -141,6 +141,7 @@ describe('ID5 ID System', function() { expect(requestBody.o).to.eq('pbjs'); expect(requestBody.pd).to.eq(''); expect(requestBody.s).to.eq(''); + expect(requestBody.provider).to.eq(''); expect(requestBody.v).to.eq('$prebid.version$'); request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); From 8340be830911e904b457950861945d6a34bbeed7 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 18 Nov 2020 15:14:06 -0500 Subject: [PATCH 183/190] Prebid 4.17.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b62de09e1f..d957c0901c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.17.0-pre", + "version": "4.17.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 05eafcf6c9d105620b43f5e2dc59aab9100b3f9a Mon Sep 17 00:00:00 2001 From: pramod pisal Date: Thu, 19 Nov 2020 11:46:58 +0530 Subject: [PATCH 184/190] automate-creation of modules.json file --- modules.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 modules.json diff --git a/modules.json b/modules.json new file mode 100644 index 00000000000..2d5645ed5fe --- /dev/null +++ b/modules.json @@ -0,0 +1 @@ +["appnexusBidAdapter","consentManagement","sekindoUMBidAdapter","pulsepointBidAdapter","audienceNetworkBidAdapter","openxBidAdapter","rubiconBidAdapter","sovrnBidAdapter","pubmaticBidAdapter","adgenerationBidAdapter","pubmaticServerBidAdapter","ixBidAdapter","criteoBidAdapter"] \ No newline at end of file From 7c811776ad675019dd21ff54d25a838b5534ab00 Mon Sep 17 00:00:00 2001 From: Pramod Pisal Date: Thu, 19 Nov 2020 14:45:29 +0530 Subject: [PATCH 185/190] Prebid upgarde automate- test pull request created through git api please ignore it (#421) * Merge Conflicts Resolved and errors fixed * triggering usersyncs manually, to ensure syncup occurs in case of server side enabled partners * changes in native keys from hb_ to pwt_ * added ext in logo and icon * Hook Fix * Support for ext object in icon * automate-creation of modules.json file * Fixed merge issue and test cases fixes * Check for valid sizes only * Unit test cases for the change * automate-creation of modules.json file * Fixed Merge issues * UOE-4404 if adslot and mediatype both contain sizes * automate-creation of modules.json file * Initial User Id Module * Updated modules.json * automate-creation of modules.json file * Server Side throttling based on condition * Condition for all partners throttled * Changed gulp task dependencies * Build Time Optimization * changed unused gulp packages * Updated Package.json * automate-creation of modules.json file * resolved conflixt * automate-creation of modules.json file * IdentityPartners * Changes for Hashed Key and parseAdSlot logic * Fix for custom module * making call secure of ow * Removed implicit customId system and added it as a separated submodule * Making server side calls secure and flag secure to 1 * Changed unifiedId from implicit to explicit * Updated The code to fix text cases * Fixed unit test cases * Took latest for all ids * Fixed the linting issue * Custom Data support and ParseInt for Id5 * Fixed custom data * Updated function call * Changed Event from Auction End to Request Bid * added secure flag * Revert "Open identity" * Revert "Revert "Open identity"" * First Party Id name updated for cookies * PubMatic Handle first Party Id * Pubmatic alias * Handle regex pattern in logger for Hybrid Implementation * Increment pre version * Somo: fix an issue where the requestId was being set to the wrong value (#4596) * Sovrn ccpa support legacy (#4623) * sovrn ccpa support * use array map/join instead of object.entries * TripleLift: CCPA legacy support (#4641) * Add CCPA query param * Simplify check for usPrivacy argument * pbsBidAdapter currency fix for legacy branch (#4642) * pbsBidAdapter currency fix for legacy branch * fixed unit tests * Prebid 2.44.1 Release * Revert "Prebid 2.44.1 Release" This reverts commit 105313b792b79002c0ada3301d154afd49adb7cd. * fix a bug when the iframe locator is not present on page (#4637) * fix a bug when the iframe locator is not present on page * clean up * Prebid 2.44.1 Release * increment pre version * Index Exchange: CCPA support (#4662) * support for us privacy (CCPA) (#4665) * Added CCPA support for legacy (#4663) * Update CCPA v3 (#4677) CCPA support V2 compatibility Sample tag update * automate-creation of modules.json file * Medianet: CCPA support added (#4656) * Ccpa legacy support for OneVideo (#4648) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * ccpa cahnges * ccpa change * test page * test page change * test page change 2 * change the variable * handling the case if both GDPR and CCPA case * handiling both cases * test cases * legacy ccpa support * Update oneVideoBidAdapter.js * Add us privacy 2.X (#4669) * cedato adapter gdpr and usp compliance (#4686) * Fidelity Media Bid Adapter 2.44.x legacy. CCPA support. (#4652) * Fidelity Media Bid Adapter v2.44.x. CCPA support. * Fidelity Media Bid Adapter v2.44.x. CCPA support. * add dh adapter for legacy prebid 2.x (#4670) * Prebid 2.44.2 Release * automate-creation of modules.json file * Support for CCPA * Adding tracker in vast creative before cache * add adform alias adform2 * Updated First Party Module * Updated our adapter to have firstpartyid * automate-creation of modules.json file * Update key value pair for video in openwrap * Server side syncup in accordance with latest filter settings * Support for Eids in PubMaticServerBidAdapter * Fix for pubCommonId * Fix for pubmatic server bid adapter * fix for player size and considering w & h for video * automate-creation of modules.json file * Fixed test issues * automate-creation of modules.json file * fix test cases * Pull changes for dspid and seatid from prebid master * Support for buyerId * Updated location of buyid * automate-creation of modules.json file * Changes for consuming targeting from server side * Fix test cases * Updated rubiconBidAdapter for alias * adding sspId * UOE-5262 : OpenWrap: Add Secondary Ad Generation Bidder * bluebillywig outstream renderer * Fix an issue with replacing Renderer * Replaced Renderee * code review comments * automate-creation of modules.json file * added missing adatpers * updating package.json for prod dependecies * updated namespace * Fix for SSP ID * fixed test cases * took latest * ternay adapters * Update adformBidAdapter.js * automate-creation of modules.json file * updated modules.json removed audienceNetworkBidAdapter * wiid fix * Targeting Keys * dg bid adapter * automate-creation of modules.json file * fixes for dfp * removed audienceNetwork from modules.json * Fix for UOE-5694 * Fix for test cases * regex support * OpenWrap Nightly Release v21.1.0 (#417) * support for video in hybrid profiles * added newBid.mediaType for pubmaticServerBidAdapter * unit test case for video request * reverted debug flag * Changes for UOe-5712/5705 * Manually took the changes for DVC related info * Fix Typo * piid for hybrid profiles * removed fix for piid from staged_nightly * Removing OW PB Same Changes regarding device as it will be releaed in Q1 * Log SSPId in piid for pubmatic Co-authored-by: pm-shashank-jain <40654031+pm-shashank-jain@users.noreply.github.com> Co-authored-by: pm-shashank-jain Co-authored-by: Manasi Moghe Co-authored-by: Jaimin Panchal Co-authored-by: travisbeale Co-authored-by: Ankit Prakash Co-authored-by: David Andersen Co-authored-by: bretg Co-authored-by: Jason Snellbaker Co-authored-by: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Co-authored-by: Index Exchange 3 Prebid Team Co-authored-by: nwlosinski Co-authored-by: JonGoSonobi Co-authored-by: Itay Nave <38345760+itaynave@users.noreply.github.com> Co-authored-by: binoy-chitale Co-authored-by: DeepthiNeeladri Co-authored-by: Valentin Souche Co-authored-by: Alex Khmelnitsky Co-authored-by: Oleg Naydenov Co-authored-by: Montu Thakore Co-authored-by: Deepak Sahu Co-authored-by: PubMatic-OpenWrap Co-authored-by: Harshad Mane --- gulpfile.js | 107 +- .../gpt/gdpr_iframe_hello_world.html | 109 + integrationExamples/gpt/gdpr_iframe_test.html | 10 + modules.json | 2 +- modules/.submodules.json | 1 + modules/adformBidAdapter.js | 1 + modules/adgenerationBidAdapter.js | 2 +- modules/appnexusBidAdapter.js | 1 + modules/customIdSystem.js | 69 + modules/dfpAdServerVideo.js | 12 +- modules/id5IdSystem.js | 1 - modules/ixBidAdapter.js | 1 + modules/pubmaticBidAdapter.js | 196 +- modules/pubmaticBidAdapter.md | 1 + modules/pubmaticServerBidAdapter.js | 730 ++ modules/pubmaticServerBidAdapter.md | 47 + modules/rdnBidAdapter.md | 0 modules/rubiconBidAdapter.js | 1 + modules/teadsBidAdapter.js | 15 +- modules/userId/index.js | 4 +- modules/widespaceBidAdapter.js | 1 - package.json | 31 +- src/ajax.js | 14 +- src/config.js | 2 - src/constants.json | 60 +- src/hook.js | 1 + src/videoCache.js | 7 +- .../modules/appierAnalyticsAdapter_spec.js | 2 +- test/spec/modules/dfpAdServerVideo_spec.js | 14 +- .../modules/fintezaAnalyticsAdapter_spec.js | 8 +- test/spec/modules/id5IdSystem_spec.js | 38 +- .../modules/invisiblyAnalyticsAdapter_spec.js | 3 +- .../modules/pubmaticAnalyticsAdapter_spec.js | 101 + test/spec/modules/pubmaticBidAdapter_spec.js | 1188 ++- .../modules/pubmaticServerBidAdapter_spec.js | 540 + .../modules/richaudienceBidAdapter_spec.js | 4 +- test/spec/modules/userId_spec.js | 10 +- test/spec/native_spec.js | 6 +- test/spec/unit/core/adapterManager_spec.js | 3 - test/spec/unit/core/targeting_spec.js | 4 +- yarn.lock | 9409 +++++++++++++++++ 41 files changed, 12091 insertions(+), 665 deletions(-) create mode 100644 integrationExamples/gpt/gdpr_iframe_hello_world.html create mode 100644 integrationExamples/gpt/gdpr_iframe_test.html create mode 100644 modules/customIdSystem.js create mode 100644 modules/pubmaticServerBidAdapter.js create mode 100644 modules/pubmaticServerBidAdapter.md mode change 100644 => 100755 modules/rdnBidAdapter.md create mode 100644 test/spec/modules/pubmaticServerBidAdapter_spec.js create mode 100644 yarn.lock diff --git a/gulpfile.js b/gulpfile.js index 879e34ae588..bbc2e6eb08a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,31 +1,30 @@ /* eslint-disable no-console */ 'use strict'; - -var _ = require('lodash'); +console.time('Loading Plugins in Prebid'); var argv = require('yargs').argv; var gulp = require('gulp'); -var gutil = require('gulp-util'); -var connect = require('gulp-connect'); -var webpack = require('webpack'); -var webpackStream = require('webpack-stream'); -var uglify = require('gulp-uglify'); -var gulpClean = require('gulp-clean'); -var KarmaServer = require('karma').Server; -var karmaConfMaker = require('./karma.conf.maker'); -var opens = require('opn'); -var webpackConfig = require('./webpack.conf'); -var helpers = require('./gulpHelpers'); +// var gutil = require('gulp-util'); +// var connect = require('gulp-connect'); +// var webpack = require('webpack'); +// var webpackStream = require('webpack-stream'); +// var uglify = require('gulp-uglify'); +// var gulpClean = require('gulp-clean'); +// var KarmaServer = require('karma').Server; +// var karmaConfMaker = require('./karma.conf.maker'); +// var opens = require('opn'); +// var webpackConfig = require('./webpack.conf'); +// var helpers = require('./gulpHelpers'); var concat = require('gulp-concat'); -var header = require('gulp-header'); -var footer = require('gulp-footer'); +// var header = require('gulp-header'); +// var footer = require('gulp-footer'); var replace = require('gulp-replace'); -var shell = require('gulp-shell'); -var eslint = require('gulp-eslint'); -var gulpif = require('gulp-if'); -var sourcemaps = require('gulp-sourcemaps'); -var through = require('through2'); -var fs = require('fs'); -var jsEscape = require('gulp-js-escape'); +// var shell = require('gulp-shell'); +// var eslint = require('gulp-eslint'); +// var gulpif = require('gulp-if'); +// var sourcemaps = require('gulp-sourcemaps'); +// var through = require('through2'); +// var fs = require('fs'); +// var jsEscape = require('gulp-js-escape'); const path = require('path'); const execa = require('execa'); @@ -33,6 +32,7 @@ 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 port = 9999; +console.timeEnd('Loading Plugins in Prebid'); const FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost'; const FAKE_SERVER_PORT = 4444; const { spawn } = require('child_process'); @@ -49,6 +49,7 @@ function bundleToStdout() { bundleToStdout.displayName = 'bundle-to-stdout'; function clean() { + var gulpClean = require('gulp-clean'); return gulp.src(['build'], { read: false, allowEmpty: true @@ -58,6 +59,8 @@ function clean() { // Dependant task for building postbid. It escapes postbid-config file. function escapePostbidConfig() { + var jsEscape = require('gulp-js-escape'); + gulp.src('./integrationExamples/postbid/oas/postbid-config.js') .pipe(jsEscape()) .pipe(gulp.dest('build/postbid/')); @@ -65,6 +68,9 @@ function escapePostbidConfig() { escapePostbidConfig.displayName = 'escape-postbid-config'; function lint(done) { + var eslint = require('gulp-eslint'); + var gulpif = require('gulp-if'); + if (argv.nolint) { return done(); } @@ -80,6 +86,9 @@ function lint(done) { // View the code coverage report in the browser. function viewCoverage(done) { + var connect = require('gulp-connect'); + var opens = require('opn'); + var coveragePort = 1999; var mylocalhost = (argv.host) ? argv.host : 'localhost'; @@ -96,6 +105,8 @@ viewCoverage.displayName = 'view-coverage'; // Watch Task with Live Reload function watch(done) { + var connect = require('gulp-connect'); + var mainWatcher = gulp.watch([ 'src/**/*.js', 'modules/**/*.js', @@ -121,6 +132,13 @@ function watch(done) { }; function makeDevpackPkg() { + var _ = require('lodash'); + var connect = require('gulp-connect'); + var webpack = require('webpack'); + var webpackStream = require('webpack-stream'); + var webpackConfig = require('./webpack.conf'); + var helpers = require('./gulpHelpers'); + var cloned = _.cloneDeep(webpackConfig); cloned.devtool = 'source-map'; var externalModules = helpers.getArgModules(); @@ -136,6 +154,15 @@ function makeDevpackPkg() { } function makeWebpackPkg() { + var _ = require('lodash'); + var webpack = require('webpack'); + var webpackStream = require('webpack-stream'); + var uglify = require('gulp-uglify'); + var webpackConfig = require('./webpack.conf'); + var helpers = require('./gulpHelpers'); + var header = require('gulp-header'); + var gulpif = require('gulp-if'); + var cloned = _.cloneDeep(webpackConfig); delete cloned.devtool; @@ -157,6 +184,8 @@ function gulpBundle(dev) { } function nodeBundle(modules) { + var through = require('through2'); + return new Promise((resolve, reject) => { bundle(false, modules) .on('error', (err) => { @@ -170,9 +199,17 @@ function nodeBundle(modules) { } function bundle(dev, moduleArr) { + // console.time('Loading Plugins for Prebid'); + var _ = require('lodash'); + var gutil = require('gulp-util'); + var helpers = require('./gulpHelpers'); + var footer = require('gulp-footer'); + var gulpif = require('gulp-if'); + var sourcemaps = require('gulp-sourcemaps'); var modules = moduleArr || helpers.getArgModules(); var allModules = helpers.getModuleNames(modules); - + // console.timeEnd('Loading Plugins for Prebid'); + // console.time('Getting Modules for Prebid'); if (modules.length === 0) { modules = allModules.filter(module => explicitModules.indexOf(module) === -1); } else { @@ -184,7 +221,8 @@ function bundle(dev, moduleArr) { }); } } - + // console.timeEnd('Getting Modules for Prebid'); + // console.time('Concating Starts'); var entries = [helpers.getBuiltPrebidCoreFile(dev)].concat(helpers.getBuiltModules(dev, modules)); var outputFileName = argv.bundleName ? argv.bundleName : 'prebid.js'; @@ -194,10 +232,10 @@ function bundle(dev, moduleArr) { outputFileName = outputFileName.replace(/\.js$/, `.${argv.tag}.js`); } - gutil.log('Concatenating files:\n', entries); - gutil.log('Appending ' + prebid.globalVarName + '.processQueue();'); - gutil.log('Generating bundle:', outputFileName); - + // gutil.log('Concatenating files:\n', entries); + // gutil.log('Appending ' + prebid.globalVarName + '.processQueue();'); + // gutil.log('Generating bundle:', outputFileName); + // console.timeEnd('Concating Starts'); return gulp.src( entries ) @@ -221,6 +259,10 @@ function bundle(dev, moduleArr) { // If --notest is given, it will immediately skip the test task (useful for developing changes with `gulp serve --notest`) function test(done) { + var KarmaServer = require('karma').Server; + var karmaConfMaker = require('./karma.conf.maker'); + var helpers = require('./gulpHelpers'); + if (argv.notest) { done(); } else if (argv.e2e) { @@ -292,10 +334,15 @@ function newKarmaCallback(done) { // If --file "" is given, the task will only run tests in the specified file. function testCoverage(done) { + var KarmaServer = require('karma').Server; + var karmaConfMaker = require('./karma.conf.maker'); + new KarmaServer(karmaConfMaker(true, false, false, argv.file), newKarmaCallback(done)).start(); } function coveralls() { // 2nd arg is a dependency: 'test' must be finished + var shell = require('gulp-shell'); + // first send results of istanbul's test coverage to coveralls.io. return gulp.src('gulpfile.js', { read: false }) // You have to give it a file, but you don't // have to read it. @@ -306,6 +353,8 @@ function coveralls() { // 2nd arg is a dependency: 'test' must be finished // More info can be found here http://prebid.org/overview/what-is-post-bid.html function buildPostbid() { + var fs = require('fs'); + var fileContent = fs.readFileSync('./build/postbid/postbid-config.js', 'utf8'); return gulp.src('./integrationExamples/postbid/oas/postbid.js') @@ -314,6 +363,8 @@ function buildPostbid() { } function setupE2e(done) { + var gutil = require('gulp-util'); + if (!argv.host) { throw new gutil.PluginError({ plugin: 'E2E test', diff --git a/integrationExamples/gpt/gdpr_iframe_hello_world.html b/integrationExamples/gpt/gdpr_iframe_hello_world.html new file mode 100644 index 00000000000..4ead5832bc7 --- /dev/null +++ b/integrationExamples/gpt/gdpr_iframe_hello_world.html @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + +

Prebid.js Test

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