From c39030132d937c56c9015c791b45a0d9c43fd0ae Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 18 Feb 2021 12:41:45 +0100 Subject: [PATCH 01/96] remove usersync and add gpdr via querystring --- modules/adponeBidAdapter.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/modules/adponeBidAdapter.js b/modules/adponeBidAdapter.js index f128785afff..cfef5d18faf 100644 --- a/modules/adponeBidAdapter.js +++ b/modules/adponeBidAdapter.js @@ -4,21 +4,9 @@ import {triggerPixel} from '../src/utils.js'; const ADPONE_CODE = 'adpone'; const ADPONE_ENDPOINT = 'https://rtb.adpone.com/bid-request'; -const ADPONE_SYNC_ENDPOINT = 'https://eu-ads.adpone.com'; const ADPONE_REQUEST_METHOD = 'POST'; const ADPONE_CURRENCY = 'EUR'; -function _createSync() { - return { - type: 'iframe', - url: ADPONE_SYNC_ENDPOINT - } -} - -function getUserSyncs(syncOptions) { - return (syncOptions && syncOptions.iframeEnabled) ? _createSync() : ([]); -} - export const spec = { code: ADPONE_CODE, supportedMediaTypes: [BANNER], @@ -29,9 +17,9 @@ export const spec = { return !!bid.params.placementId && !!bid.bidId && bid.bidder === 'adpone' }, - buildRequests: bidRequests => { + buildRequests: (bidRequests, bidderRequest) => { return bidRequests.map(bid => { - const url = ADPONE_ENDPOINT + '?pid=' + bid.params.placementId; + let url = ADPONE_ENDPOINT + '?pid=' + bid.params.placementId; const data = { at: 1, id: bid.bidId, @@ -49,6 +37,11 @@ export const spec = { withCredentials: true }; + if (bidderRequest.gdprConsent) { + url += '&gdpr_applies=' + bidderRequest.gdprConsent.gdprApplies; + url += '&consentString=' + bidderRequest.gdprConsent.consentString; + } + return { method: ADPONE_REQUEST_METHOD, url, From 20cf94534bde777574a8102e49e2d0182629caf7 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 2 Mar 2021 05:27:09 -0700 Subject: [PATCH 02/96] kick off CircleCI tests --- modules/adponeBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/adponeBidAdapter.js b/modules/adponeBidAdapter.js index cfef5d18faf..488717d8007 100644 --- a/modules/adponeBidAdapter.js +++ b/modules/adponeBidAdapter.js @@ -87,3 +87,4 @@ export const spec = { }; registerBidder(spec); + From 94dea84761f78a8055541cb76f127a0305af8589 Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Thu, 18 Feb 2021 12:45:22 +0100 Subject: [PATCH 03/96] mediasquare fix userId (#6321) --- modules/mediasquareBidAdapter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 87f768d3b77..cb9bf5285a1 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -59,7 +59,11 @@ export const spec = { } if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } if (bidderRequest.schain) { payload.schain = bidderRequest.schain; } - if (bidderRequest.userId) { payload.userId = bidderRequest.userId; } + if (bidderRequest.userId) { + payload.userId = bidderRequest.userId; + } else if (bidderRequest.hasOwnProperty('bids') && typeof bidderRequest.bids == 'object' && bidderRequest.bids.length > 0 && bidderRequest.bids[0].hasOwnProperty('userId')) { + payload.userId = bidderRequest.bids[0].userId; + } }; if (test) { payload.debug = true; } const payloadString = JSON.stringify(payload); From 39e33d1cf9267f293caa2a49eba543740b3d292c Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Thu, 18 Feb 2021 11:53:34 +0000 Subject: [PATCH 04/96] Change the EU consent string parameter name to the company-wide standard (#6320) --- modules/verizonMediaIdSystem.js | 2 +- test/spec/modules/verizonMediaIdSystem_spec.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index fdfb8a993d8..ca395087d2d 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -59,7 +59,7 @@ export const verizonMediaIdSubmodule = { '1p': includes([1, '1', true], params['1p']) ? '1' : '0', he: params.he, gdpr: isEUConsentRequired(consentData) ? '1' : '0', - euconsent: isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', + gdpr_consent: isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' }; diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index c5d743235d6..10054b5674c 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -87,7 +87,7 @@ describe('Verizon Media ID Submodule', () => { pixelId: PIXEL_ID, '1p': '0', gdpr: '1', - euconsent: consentData.gdpr.consentString, + gdpr_consent: consentData.gdpr.consentString, us_privacy: consentData.uspConsent }; const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -107,7 +107,7 @@ describe('Verizon Media ID Submodule', () => { he: HASHED_EMAIL, '1p': '0', gdpr: '1', - euconsent: consentData.gdpr.consentString, + gdpr_consent: consentData.gdpr.consentString, us_privacy: consentData.uspConsent }; const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -134,7 +134,7 @@ describe('Verizon Media ID Submodule', () => { const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); expect(requestQueryParams.gdpr).to.equal('1'); - expect(requestQueryParams.euconsent).to.equal(consentData.gdpr.consentString); + expect(requestQueryParams.gdpr_consent).to.equal(consentData.gdpr.consentString); }); it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { @@ -147,7 +147,7 @@ describe('Verizon Media ID Submodule', () => { const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); expect(requestQueryParams.gdpr).to.equal('0'); - expect(requestQueryParams.euconsent).to.equal(''); + expect(requestQueryParams.gdpr_consent).to.equal(''); }); [1, '1', true].forEach(firstPartyParamValue => { From 0d9b088fa849776b668fb9627a3a0d5e4f6d66e1 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Thu, 18 Feb 2021 08:54:12 -0800 Subject: [PATCH 05/96] adding support in pbsAdapter for getFloor (#6273) --- modules/prebidServerBidAdapter/index.js | 17 ++++ .../modules/prebidServerBidAdapter_spec.js | 95 +++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index c8199cd0aaf..2d8acb34225 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -642,6 +642,23 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(imp, 'ext.prebid.storedauctionresponse.id', storedAuctionResponseBid.storedAuctionResponse.toString()); } + const getFloorBid = find(firstBidRequest.bids, bid => bid.adUnitCode === adUnit.code && typeof bid.getFloor === 'function'); + + if (getFloorBid) { + let floorInfo; + try { + floorInfo = getFloorBid.getFloor({ + currency: config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, + }); + } catch (e) { + utils.logError('PBS: getFloor threw an error: ', e); + } + if (floorInfo && floorInfo.currency && !isNaN(parseFloat(floorInfo.floor))) { + imp.bidfloor = parseFloat(floorInfo.floor); + imp.bidfloorcur = floorInfo.currency + } + } + if (imp.banner || imp.video || imp.native) { imps.push(imp); } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 4652bbb63f7..8d88a3e0042 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -829,6 +829,101 @@ describe('S2S Adapter', function () { expect(requestBid.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); }); + describe('price floors module', function () { + function runTest(expectedFloor, expectedCur) { + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[requestCount].requestBody); + expect(requestBid.imp[0].bidfloor).to.equal(expectedFloor); + expect(requestBid.imp[0].bidfloorcur).to.equal(expectedCur); + requestCount += 1; + } + + let getFloorResponse, requestCount; + beforeEach(function () { + getFloorResponse = {}; + requestCount = 0; + }); + + it('should NOT pass bidfloor and bidfloorcur when getFloor not present or returns invalid response', function () { + const _config = { + s2sConfig: CONFIG, + }; + + config.setConfig(_config); + + // if no get floor + runTest(undefined, undefined); + + // if getFloor returns empty object + BID_REQUESTS[0].bids[0].getFloor = () => getFloorResponse; + sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); + + runTest(undefined, undefined); + // make sure getFloor was called + expect( + BID_REQUESTS[0].bids[0].getFloor.calledWith({ + currency: 'USD', + }) + ).to.be.true; + + // if getFloor does not return number + getFloorResponse = {currency: 'EUR', floor: 'not a number'}; + runTest(undefined, undefined); + + // if getFloor does not return currency + getFloorResponse = {floor: 1.1}; + runTest(undefined, undefined); + }); + + it('should correctly pass bidfloor and bidfloorcur', function () { + const _config = { + s2sConfig: CONFIG, + }; + + config.setConfig(_config); + + BID_REQUESTS[0].bids[0].getFloor = () => getFloorResponse; + sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); + + // returns USD and string floor + getFloorResponse = {currency: 'USD', floor: '1.23'}; + runTest(1.23, 'USD'); + // make sure getFloor was called + expect( + BID_REQUESTS[0].bids[0].getFloor.calledWith({ + currency: 'USD', + }) + ).to.be.true; + + // returns non USD and number floor + getFloorResponse = {currency: 'EUR', floor: 0.85}; + runTest(0.85, 'EUR'); + }); + + it('should correctly pass adServerCurrency when set to getFloor not default', function () { + config.setConfig({ + s2sConfig: CONFIG, + currency: { adServerCurrency: 'JPY' }, + }); + + // we have to start requestCount at 1 because a conversion rates fetch occurs when adServerCur is not USD! + requestCount = 1; + + BID_REQUESTS[0].bids[0].getFloor = () => getFloorResponse; + sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); + + // returns USD and string floor + getFloorResponse = {currency: 'JPY', floor: 97.2}; + runTest(97.2, 'JPY'); + // make sure getFloor was called with JPY + expect( + BID_REQUESTS[0].bids[0].getFloor.calledWith({ + currency: 'JPY', + }) + ).to.be.true; + }); + }); + it('adds device.w and device.h even if the config lacks a device object', function () { const _config = { s2sConfig: CONFIG, From b3293e9c794909bcfb1ccd807d684a82588b8021 Mon Sep 17 00:00:00 2001 From: Damyan Date: Thu, 18 Feb 2021 23:14:43 +0200 Subject: [PATCH 06/96] AdHash Bidder Adapter: initial prebid.js integration (#6274) --- modules/adhashBidAdapter.js | 102 ++++++++++++++ modules/adhashBidAdapter.md | 43 ++++++ test/spec/modules/adhashBidAdapter_spec.js | 155 +++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 modules/adhashBidAdapter.js create mode 100644 modules/adhashBidAdapter.md create mode 100644 test/spec/modules/adhashBidAdapter_spec.js diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js new file mode 100644 index 00000000000..b30f9cebbc1 --- /dev/null +++ b/modules/adhashBidAdapter.js @@ -0,0 +1,102 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const VERSION = '1.0'; + +export const spec = { + code: 'adhash', + url: 'https://bidder.adhash.org/rtb?version=' + VERSION + '&prebid=true', + supportedMediaTypes: [ BANNER ], + + isBidRequestValid: (bid) => { + try { + const { publisherId, platformURL } = bid.params; + return ( + includes(Object.keys(bid.mediaTypes), BANNER) && + typeof publisherId === 'string' && + publisherId.length === 42 && + typeof platformURL === 'string' && + platformURL.length >= 13 + ); + } catch (error) { + return false; + } + }, + + buildRequests: (validBidRequests, bidderRequest) => { + const { gdprConsent } = bidderRequest; + const { url } = spec; + const bidRequests = []; + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.referer; + } + for (var i = 0; i < validBidRequests.length; i++) { + var index = Math.floor(Math.random() * validBidRequests[i].sizes.length); + var size = validBidRequests[i].sizes[index].join('x'); + bidRequests.push({ + method: 'POST', + url: url, + bidRequest: validBidRequests[i], + data: { + timezone: new Date().getTimezoneOffset() / 60, + location: referrer, + publisherId: validBidRequests[i].params.publisherId, + size: { + screenWidth: window.screen.width, + screenHeight: window.screen.height + }, + navigator: { + platform: window.navigator.platform, + language: window.navigator.language, + userAgent: window.navigator.userAgent + }, + creatives: [{ + size: size, + position: validBidRequests[i].adUnitCode + }], + blockedCreatives: [], + currentTimestamp: new Date().getTime(), + recentAds: [], + GDPR: gdprConsent + }, + options: { + withCredentials: false, + crossOrigin: true + }, + }); + } + return bidRequests; + }, + + interpretResponse: (serverResponse, request) => { + const responseBody = serverResponse ? serverResponse.body : {}; + + if (!responseBody.creatives || responseBody.creatives.length === 0) { + return []; + } + + const publisherURL = JSON.stringify(request.bidRequest.params.platformURL); + const oneTimeId = request.bidRequest.adUnitCode + Math.random().toFixed(16).replace('0.', '.'); + const bidderResponse = JSON.stringify({ responseText: JSON.stringify(responseBody) }); + const requestData = JSON.stringify(request.data); + + return [{ + requestId: request.bidRequest.bidId, + cpm: responseBody.creatives[0].costEUR, + ad: + `
+ + `, + width: request.bidRequest.sizes[0][0], + height: request.bidRequest.sizes[0][1], + creativeId: request.bidRequest.adUnitCode, + netRevenue: true, + currency: 'EUR', + ttl: 60 + }]; + } +}; + +registerBidder(spec); diff --git a/modules/adhashBidAdapter.md b/modules/adhashBidAdapter.md new file mode 100644 index 00000000000..c1093e0ccd7 --- /dev/null +++ b/modules/adhashBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: AdHash Bidder Adapter +Module Type: Bidder Adapter +Maintainer: damyan@adhash.org +``` + +# Description + +Here is what you need for Prebid integration with AdHash: +1. Register with AdHash. +2. Once registered and approved, you will receive a Publisher ID and Platform URL. +3. Use the Publisher ID and Platform URL as parameters in params. + +Please note that a number of AdHash functionalities are not supported in the Prebid.js integration: +* Cookie-less frequency and recency capping; +* Audience segments; +* Price floors and passback tags, as they are not needed in the Prebid.js setup; +* Reservation for direct deals only, as bids are evaluated based on their price. + +# Test Parameters +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'adhash', + params: { + publisherId: '0x1234567890123456789012345678901234567890', + platformURL: 'https://adhash.org/p/struma/' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/adhashBidAdapter_spec.js b/test/spec/modules/adhashBidAdapter_spec.js new file mode 100644 index 00000000000..ab4df84c093 --- /dev/null +++ b/test/spec/modules/adhashBidAdapter_spec.js @@ -0,0 +1,155 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adhashBidAdapter.js'; + +describe('adhashBidAdapter', function () { + describe('isBidRequestValid', function () { + const validBid = { + bidder: 'adhash', + params: { + publisherId: '0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb', + platformURL: 'https://adhash.org/p/struma/' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '12345678901234', + bidderRequestId: '98765432109876', + auctionId: '01234567891234', + }; + + it('should return true when all mandatory parameters are there', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when there are no params', function () { + const bid = { ...validBid }; + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when unsupported media type is requested', function () { + const bid = { ...validBid }; + bid.mediaTypes = { native: { sizes: [[300, 250]] } }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is not a string', function () { + const bid = { ...validBid }; + bid.params.publisherId = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is not valid', function () { + const bid = { ...validBid }; + bid.params.publisherId = 'short string'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is not a string', function () { + const bid = { ...validBid }; + bid.params.platformURL = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is not valid', function () { + const bid = { ...validBid }; + bid.params.platformURL = 'https://'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequest = { + params: { + publisherId: '0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb' + }, + sizes: [[300, 250]], + adUnitCode: 'adUnitCode' + }; + it('should build the request correctly', function () { + const result = spec.buildRequests( + [ bidRequest ], + { gdprConsent: true, refererInfo: { referer: 'http://example.com/' } } + ); + expect(result.length).to.equal(1); + expect(result[0].method).to.equal('POST'); + expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true'); + expect(result[0].bidRequest).to.equal(bidRequest); + expect(result[0].data).to.have.property('timezone'); + expect(result[0].data).to.have.property('location'); + expect(result[0].data).to.have.property('publisherId'); + expect(result[0].data).to.have.property('size'); + expect(result[0].data).to.have.property('navigator'); + expect(result[0].data).to.have.property('creatives'); + expect(result[0].data).to.have.property('blockedCreatives'); + expect(result[0].data).to.have.property('currentTimestamp'); + expect(result[0].data).to.have.property('recentAds'); + }); + it('should build the request correctly without referer', function () { + const result = spec.buildRequests([ bidRequest ], { gdprConsent: true }); + expect(result.length).to.equal(1); + expect(result[0].method).to.equal('POST'); + expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true'); + expect(result[0].bidRequest).to.equal(bidRequest); + expect(result[0].data).to.have.property('timezone'); + expect(result[0].data).to.have.property('location'); + expect(result[0].data).to.have.property('publisherId'); + expect(result[0].data).to.have.property('size'); + expect(result[0].data).to.have.property('navigator'); + expect(result[0].data).to.have.property('creatives'); + expect(result[0].data).to.have.property('blockedCreatives'); + expect(result[0].data).to.have.property('currentTimestamp'); + expect(result[0].data).to.have.property('recentAds'); + }); + }); + + describe('interpretResponse', function () { + const request = { + data: { some: 'data' }, + bidRequest: { + bidId: '12345678901234', + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + params: { + platformURL: 'https://adhash.org/p/struma/' + } + } + }; + + it('should interpret the response correctly', function () { + const serverResponse = { + body: { + creatives: [{ + costEUR: 1.234 + }] + } + }; + const result = spec.interpretResponse(serverResponse, request); + expect(result.length).to.equal(1); + expect(result[0].requestId).to.equal('12345678901234'); + expect(result[0].cpm).to.equal(1.234); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal('adunit-code'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].currency).to.equal('EUR'); + expect(result[0].ttl).to.equal(60); + }); + + it('should return empty array when there are no creatives returned', function () { + expect(spec.interpretResponse({body: {creatives: []}}, request).length).to.equal(0); + }); + + it('should return empty array when there is no creatives key in the response', function () { + expect(spec.interpretResponse({body: {}}, request).length).to.equal(0); + }); + + it('should return empty array when something is not right', function () { + expect(spec.interpretResponse(null, request).length).to.equal(0); + }); + }); +}); From a397ae109578650c5b89983ecd2fb0347db42ada Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Fri, 19 Feb 2021 07:32:40 -0500 Subject: [PATCH 07/96] PBJS Video Cache Update (#6295) --- src/auction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auction.js b/src/auction.js index 15c6b50ce71..ec9ff7d8209 100644 --- a/src/auction.js +++ b/src/auction.js @@ -458,7 +458,7 @@ function tryAddVideoBid(auctionInstance, bidResponse, bidRequests, afterBidAdded const context = videoMediaType && deepAccess(videoMediaType, 'context'); if (config.getConfig('cache.url') && context !== OUTSTREAM) { - if (!bidResponse.videoCacheKey) { + if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { addBid = false; callPrebidCache(auctionInstance, bidResponse, afterBidAdded, bidderRequest); } else if (!bidResponse.vastUrl) { From 1beebbd80894080b4fa08f1c30fd0891c980d7de Mon Sep 17 00:00:00 2001 From: Catalin Ciocov Date: Fri, 19 Feb 2021 15:13:07 +0200 Subject: [PATCH 08/96] Support bidder aliasing by not using bid.bidder to retrieve configs (which are static anyway) (#6313) --- modules/inskinBidAdapter.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js index 3951e27c870..f06382c6375 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -4,9 +4,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'inskin'; const CONFIG = { - 'inskin': { - 'BASE_URI': 'https://mfad.inskinad.com/api/v2' - } + BASE_URI: 'https://mfad.inskinad.com/api/v2' }; export const spec = { @@ -97,8 +95,7 @@ export const spec = { } validBidRequests.map(bid => { - let config = CONFIG[bid.bidder]; - ENDPOINT_URL = config.BASE_URI; + ENDPOINT_URL = CONFIG.BASE_URI; const placement = Object.assign({ divName: bid.bidId, From 8cbd2738e8fe2923a0a11a5bfaaf45b228e1bce3 Mon Sep 17 00:00:00 2001 From: Ben Anderson Date: Fri, 19 Feb 2021 09:31:23 -0500 Subject: [PATCH 09/96] Fabrick ID System : updates to _setReferrer and appending urls (#6322) * fabrickId updates - encode all url params - dedupe w/out queryString and keep the longest - additionally truncate from % if that ends up being the last (or next to last) char after truncation - truncate 1k instead of 200 - don't send functions along in query (only send strings and numbers) * attempting to fix ie failure - https://app.circleci.com/pipelines/github/prebid/Prebid.js/3945/workflows/4929a5c0-9e57-49e3-a6d8-29691560ca31/jobs/12086 * re-attempting to fix ie failure - https://app.circleci.com/pipelines/github/prebid/Prebid.js/3945/workflows/4929a5c0-9e57-49e3-a6d8-29691560ca31/jobs/12086 * re-attempting to fix ie failure - https://app.circleci.com/pipelines/github/prebid/Prebid.js/3945/workflows/4929a5c0-9e57-49e3-a6d8-29691560ca31/jobs/12086 * re-attempting to fix ie failure - https://app.circleci.com/pipelines/github/prebid/Prebid.js/3945/workflows/4929a5c0-9e57-49e3-a6d8-29691560ca31/jobs/12086 * re-attempting to fix ie failure - https://app.circleci.com/pipelines/github/prebid/Prebid.js/3945/workflows/4929a5c0-9e57-49e3-a6d8-29691560ca31/jobs/12086 * re-attempting to fix ie failure - https://app.circleci.com/pipelines/github/prebid/Prebid.js/3945/workflows/4929a5c0-9e57-49e3-a6d8-29691560ca31/jobs/12086 - attempting to add debugging so I can know what exactly is failing in ie (since I don't have a windows machine) * found the issue having debugged locally - stubbing my error message is what killed me! * not the right way to loop through a map Co-authored-by: Anderson, Ben --- modules/fabrickIdSystem.js | 72 +++++++++++++++++------ test/spec/modules/fabrickIdSystem_spec.js | 46 ++++++++++++--- 2 files changed, 91 insertions(+), 27 deletions(-) diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js index e61b377eefa..bb838788f07 100644 --- a/modules/fabrickIdSystem.js +++ b/modules/fabrickIdSystem.js @@ -46,7 +46,7 @@ export const fabrickIdSubmodule = { if (window.fabrickMod1) { window.fabrickMod1(configParams, consentData, cacheIdObj); } - if (!configParams || typeof configParams.apiKey !== 'string') { + if (!configParams || !configParams.apiKey || typeof configParams.apiKey !== 'string') { utils.logError('fabrick submodule requires an apiKey.'); return; } @@ -55,28 +55,34 @@ export const fabrickIdSubmodule = { let keysArr = Object.keys(configParams); for (let i in keysArr) { let k = keysArr[i]; - if (k === 'url' || k === 'refererInfo') { + if (k === 'url' || k === 'refererInfo' || (k.length > 3 && k.substring(0, 3) === 'max')) { continue; } let v = configParams[k]; if (Array.isArray(v)) { for (let j in v) { - url += `${k}=${v[j]}&`; + if (typeof v[j] === 'string' || typeof v[j] === 'number') { + url += `${k}=${v[j]}&`; + } } - } else { + } else if (typeof v === 'string' || typeof v === 'number') { 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); + const refs = new Map(); + _setReferrer(refs, referer.referer); if (referer.stack && referer.stack[0]) { - url = truncateAndAppend(urls, url, 'r', referer.stack[0]); + _setReferrer(refs, referer.stack[0]); } - url = truncateAndAppend(urls, url, 'r', referer.canonicalUrl); - url = truncateAndAppend(urls, url, 'r', window.location.href); + _setReferrer(refs, referer.canonicalUrl); + _setReferrer(refs, window.location.href); + + refs.forEach(v => { + url = appendUrl(url, 'r', v, configParams); + }); const resp = function (callback) { const callbacks = { @@ -130,18 +136,48 @@ function _getBaseUrl(configParams) { } } -function truncateAndAppend(urls, url, paramName, s) { - if (s && url.length < 2000) { - if (s.length > 200) { - s = s.substring(0, 200); +function _setReferrer(refs, s) { + if (s) { + // store the longest one for the same URI + const url = s.split('?')[0]; + // OR store the longest one for the same domain + // const url = s.split('?')[0].replace('http://','').replace('https://', '').split('/')[0]; + if (refs.has(url)) { + const prevRef = refs.get(url); + if (s.length > prevRef.length) { + refs.set(url, s); + } + } else { + refs.set(url, s); + } + } +} + +export function appendUrl(url, paramName, s, configParams) { + const maxUrlLen = (configParams && configParams.maxUrlLen) || 2000; + const maxRefLen = (configParams && configParams.maxRefLen) || 1000; + const maxSpaceAvailable = (configParams && configParams.maxSpaceAvailable) || 50; + // make sure we have enough space left to make it worthwhile + if (s && url.length < (maxUrlLen - maxSpaceAvailable)) { + let thisMaxRefLen = maxUrlLen - url.length; + if (thisMaxRefLen > maxRefLen) { + thisMaxRefLen = maxRefLen; } - // Don't send the same url in multiple params - if (!urls.has(s)) { - urls.add(s); - return `${url}&${paramName}=${s}` + + s = `&${paramName}=${encodeURIComponent(s)}`; + + if (s.length >= thisMaxRefLen) { + s = s.substring(0, thisMaxRefLen); + if (s.charAt(s.length - 1) === '%') { + s = s.substring(0, thisMaxRefLen - 1); + } else if (s.charAt(s.length - 2) === '%') { + s = s.substring(0, thisMaxRefLen - 2); + } } + return `${url}${s}` + } else { + return url; } - return url; } submodule('userId', fabrickIdSubmodule); diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js index cbd538816ab..c250c8e5e8b 100644 --- a/test/spec/modules/fabrickIdSystem_spec.js +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -1,7 +1,7 @@ import * as utils from '../../../src/utils.js'; import {server} from '../../mocks/xhr.js'; -import * as fabrickIdSystem from 'modules/fabrickIdSystem.js'; +import {fabrickIdSubmodule, appendUrl} from 'modules/fabrickIdSystem.js'; const defaultConfigParams = { apiKey: '123', @@ -10,26 +10,25 @@ const defaultConfigParams = { 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 () { + logErrorStub = sinon.stub(utils, 'logError'); fabrickIdSubmodule.getId(); expect(logErrorStub.calledOnce).to.be.true; + logErrorStub.restore(); }); it('should error on json parsing', function() { + logErrorStub = sinon.stub(utils, 'logError'); let submoduleCallback = fabrickIdSubmodule.getId({ name: 'fabrickId', params: defaultConfigParams @@ -44,11 +43,12 @@ describe('Fabrick ID System', function() { ); expect(callBackSpy.calledOnce).to.be.true; expect(logErrorStub.calledOnce).to.be.true; + logErrorStub.restore(); }); it('should truncate the params', function() { let r = ''; - for (let i = 0; i < 300; i++) { + for (let i = 0; i < 1500; i++) { r += 'r'; } let configParams = Object.assign({}, defaultConfigParams, { @@ -66,7 +66,7 @@ describe('Fabrick ID System', function() { submoduleCallback(callBackSpy); let request = server.requests[0]; r = ''; - for (let i = 0; i < 200; i++) { + for (let i = 0; i < 1000 - 3; i++) { r += 'r'; } expect(request.url).to.match(new RegExp(`r=${r}&r=`)); @@ -76,7 +76,6 @@ describe('Fabrick ID System', function() { JSON.stringify({}) ); expect(callBackSpy.calledOnce).to.be.true; - expect(logErrorStub.calledOnce).to.be.false; }); it('should complete successfully', function() { @@ -101,6 +100,35 @@ describe('Fabrick ID System', function() { JSON.stringify({}) ); expect(callBackSpy.calledOnce).to.be.true; - expect(logErrorStub.calledOnce).to.be.false; + }); + + it('should truncate 2', function() { + let configParams = { + maxUrlLen: 10, + maxRefLen: 5, + maxSpaceAvailable: 2 + }; + + let url = appendUrl('', 'r', '123', configParams); + expect(url).to.equal('&r=12'); + + url = appendUrl('12345', 'r', '678', configParams); + expect(url).to.equal('12345&r=67'); + + url = appendUrl('12345678', 'r', '9', configParams); + expect(url).to.equal('12345678'); + + configParams.maxRefLen = 8; + url = appendUrl('', 'r', '1234&', configParams); + expect(url).to.equal('&r=1234'); + + url = appendUrl('', 'r', '123&', configParams); + expect(url).to.equal('&r=123'); + + url = appendUrl('', 'r', '12&', configParams); + expect(url).to.equal('&r=12%26'); + + url = appendUrl('', 'r', '1&&', configParams); + expect(url).to.equal('&r=1%26'); }); }); From b1f1d17190323e4de76714075b3d85d75beb4dd2 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 23 Feb 2021 03:50:27 -0700 Subject: [PATCH 10/96] Configureable option to delay auction event + fix bug with googletag init (#6344) --- modules/rubiconAnalyticsAdapter.js | 15 ++++-- .../modules/rubiconAnalyticsAdapter_spec.js | 52 ++++++++++++++++++- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index f216cbd6235..f1ba86de30f 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -60,7 +60,8 @@ const cache = { const BID_REJECTED_IPF = 'rejected-ipf'; export let rubiConf = { - pvid: utils.generateUUID().slice(0, 8) + pvid: utils.generateUUID().slice(0, 8), + analyticsEventDelay: 0 }; // 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 @@ -487,7 +488,11 @@ function subscribeToGamSlots() { if (rubiConf.waitForGamSlots && !cache.auctions[auctionId].sent && Object.keys(cache.auctions[auctionId].gamHasRendered).every(adUnitCode => cache.auctions[auctionId].gamHasRendered[adUnitCode])) { clearTimeout(cache.timeouts[auctionId]); delete cache.timeouts[auctionId]; - sendMessage.call(rubiconAdapter, auctionId); + if (rubiConf.analyticsEventDelay > 0) { + setTimeout(() => sendMessage.call(rubiconAdapter, auctionId), rubiConf.analyticsEventDelay) + } else { + sendMessage.call(rubiconAdapter, auctionId) + } } }); }); @@ -568,9 +573,9 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { cache.gpt.registered = true; } else if (!cache.gpt.registered) { cache.gpt.registered = true; - let googletag = window.googletag || {}; - googletag.cmd = googletag.cmd || []; - googletag.cmd.push(function() { + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(function() { subscribeToGamSlots(); }); } diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 71e5446ed06..505a2885404 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -636,6 +636,7 @@ describe('rubicon analytics adapter', function () { } }); expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 0, pvid: '12345678', wrapperName: '1001_general', int_type: 'dmpbjs', @@ -654,6 +655,7 @@ describe('rubicon analytics adapter', function () { } }); expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 0, pvid: '12345678', wrapperName: '1001_general', int_type: 'dmpbjs', @@ -674,6 +676,7 @@ describe('rubicon analytics adapter', function () { } }); expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 0, pvid: '12345678', wrapperName: '1001_general', int_type: 'dmpbjs', @@ -1500,7 +1503,7 @@ describe('rubicon analytics adapter', function () { it('should send request when waitForGamSlots is present but no bidWons are sent', function () { config.setConfig({ rubicon: { - waitForGamSlots: true + waitForGamSlots: true, } }); events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); @@ -1511,7 +1514,7 @@ describe('rubicon analytics adapter', function () { events.emit(AUCTION_END, MOCK.AUCTION_END); events.emit(SET_TARGETING, MOCK.SET_TARGETING); - // should not send if just slotRenderEnded is emmitted for both + // should send if just slotRenderEnded is emmitted for both mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); @@ -1536,6 +1539,51 @@ describe('rubicon analytics adapter', function () { }; expect(message).to.deep.equal(expectedMessage); }); + + it('should delay the event call depending on analyticsEventDelay config', function () { + config.setConfig({ + rubicon: { + waitForGamSlots: true, + analyticsEventDelay: 2000 + } + }); + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + + // should send if just slotRenderEnded is emmitted for both + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); + + // Should not be sent until delay + expect(server.requests.length).to.equal(0); + + // tick the clock and it should fire + clock.tick(2000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + let expectedGam0 = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + let expectedGam1 = { + advertiserId: 4444, + creativeId: 5555, + lineItemId: 6666, + adSlot: '/19968336/header-bid-tag1' + }; + expect(expectedGam0).to.deep.equal(message.auctions[0].adUnits[0].gam); + expect(expectedGam1).to.deep.equal(message.auctions[0].adUnits[1].gam); + }); }); it('should correctly overwrite bidId if seatBidId is on the bidResponse', function () { From 4bfc62a1fd885e6c6e1d0371a3e737857b71fbb7 Mon Sep 17 00:00:00 2001 From: mefjush Date: Tue, 23 Feb 2021 11:53:07 +0100 Subject: [PATCH 11/96] Adhese Bid Adapter: replace id5 with eid (#6339) --- modules/adheseBidAdapter.js | 18 +++++++++++------- test/spec/modules/adheseBidAdapter_spec.js | 18 +++++++++++++----- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 5510db62950..4fae85e82ad 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -24,8 +24,7 @@ export const spec = { const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {}; const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {}; - const id5Params = (getId5Id(validBidRequests)) ? { x5: [getId5Id(validBidRequests)] } : {}; - const commonParams = { ...gdprParams, ...refererParams, ...id5Params }; + const commonParams = { ...gdprParams, ...refererParams }; const slots = validBidRequests.map(bid => ({ slotname: bidToSlotName(bid), @@ -34,8 +33,13 @@ export const spec = { const payload = { slots: slots, - parameters: commonParams - } + parameters: commonParams, + user: { + ext: { + eids: getEids(validBidRequests), + } + } + }; const account = getAccount(validBidRequests); const uri = 'https://ads-' + account + '.adhese.com/json'; @@ -154,9 +158,9 @@ function getAccount(validBidRequests) { return validBidRequests[0].params.account; } -function getId5Id(validBidRequests) { - if (validBidRequests[0] && validBidRequests[0].userId && validBidRequests[0].userId.id5id && validBidRequests[0].userId.id5id.uid) { - return validBidRequests[0].userId.id5id.uid; +function getEids(validBidRequests) { + if (validBidRequests[0] && validBidRequests[0].userIdAsEids) { + return validBidRequests[0].userIdAsEids; } } diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index 0089a403749..2ce40fd7ffb 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -17,10 +17,9 @@ let minimalBid = function() { } }; -let bidWithParams = function(data, userId) { +let bidWithParams = function(data) { let bid = minimalBid(); bid.params.data = data; - bid.userId = userId; return bid; }; @@ -117,10 +116,19 @@ describe('AdheseAdapter', function () { expect(JSON.parse(req.data).parameters).to.deep.include({ 'xf': [ 'aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc3ViamVjdHM_X2Q9MQ' ] }); }); - it('should include id5 id as /x5 param', function () { - let req = spec.buildRequests([ bidWithParams({}, { 'id5id': { 'uid': 'ID5-1234567890' } }) ], bidderRequest); + it('should include eids', function () { + let bid = minimalBid(); + bid.userIdAsEids = [{ source: 'id5-sync.com', uids: [{ id: 'ID5@59sigaS-...' }] }]; + + let req = spec.buildRequests([ bid ], bidderRequest); + + expect(JSON.parse(req.data).user.ext.eids).to.deep.equal(bid.userIdAsEids); + }); + + it('should not include eids field when userid module disabled', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(JSON.parse(req.data).parameters).to.deep.include({ 'x5': [ 'ID5-1234567890' ] }); + expect(JSON.parse(req.data)).to.not.have.key('eids'); }); it('should include bids', function () { From 1f1979476509269e0d3fde4e05d3aa0e7f350979 Mon Sep 17 00:00:00 2001 From: Vladimir Fedoseev Date: Tue, 23 Feb 2021 12:04:02 +0100 Subject: [PATCH 12/96] FID-287: Update Reconciliation RTD delivery id format (#6343) --- modules/reconciliationRtdProvider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/reconciliationRtdProvider.js b/modules/reconciliationRtdProvider.js index bd3d8861c5b..f8636862d4c 100644 --- a/modules/reconciliationRtdProvider.js +++ b/modules/reconciliationRtdProvider.js @@ -65,7 +65,7 @@ function handleAdMessage(e) { adDeliveryId = adSlot.getTargeting('RSDK_ADID'); adDeliveryId = adDeliveryId.length ? adDeliveryId[0] - : utils.generateUUID(); + : `${utils.timestamp()}-${utils.generateUUID()}`; } } } @@ -246,7 +246,7 @@ function getReconciliationData(adUnitsCodes) { const adSlot = getSlotByCode(adUnitCode); const adUnitId = adSlot ? adSlot.getAdUnitPath() : adUnitCode; - const adDeliveryId = utils.generateUUID(); + const adDeliveryId = `${utils.timestamp()}-${utils.generateUUID()}`; dataToReturn[adUnitCode] = { RSDK_AUID: adUnitId, From 420e435ff978daf3797fc8cb4e941bc284a08fc6 Mon Sep 17 00:00:00 2001 From: Catalin Ciocov Date: Tue, 23 Feb 2021 13:13:48 +0200 Subject: [PATCH 13/96] Inskin Bid Adapter: send screen size in the ad call (#6316) --- modules/inskinBidAdapter.js | 6 +++++- test/spec/modules/inskinBidAdapter_spec.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js index f06382c6375..29040205818 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -105,8 +105,12 @@ export const spec = { placement.adTypes.push(5, 9, 163, 2163, 3006); + placement.properties = placement.properties || {}; + + placement.properties.screenWidth = screen.width; + placement.properties.screenHeight = screen.height; + if (restrictions.length) { - placement.properties = placement.properties || {}; placement.properties.restrictions = restrictions; } diff --git a/test/spec/modules/inskinBidAdapter_spec.js b/test/spec/modules/inskinBidAdapter_spec.js index e817b3e3b81..cedaff2a0cf 100644 --- a/test/spec/modules/inskinBidAdapter_spec.js +++ b/test/spec/modules/inskinBidAdapter_spec.js @@ -250,7 +250,7 @@ describe('InSkin BidAdapter', function () { const payload = JSON.parse(request.data); expect(payload.keywords).to.be.an('array').that.is.empty; - expect(payload.placements[0].properties).to.be.undefined; + expect(payload.placements[0].properties.restrictions).to.be.undefined; }); it('should add keywords if TCF v2 purposes are not granted', function () { From 1c293a87dc5ce9cd3e9ddd038213543124a3692b Mon Sep 17 00:00:00 2001 From: afsheenb Date: Wed, 24 Feb 2021 05:12:14 -0500 Subject: [PATCH 14/96] Ozone Bid Adapter: added or updated support for multiple modules (#6324) * ozone 2.5.0 adapter updates * ozone 2.5.0 adapter - fixup for prebidCircleCI tests to remove object values on user.eid Co-authored-by: Afsheen Bigdeli --- modules/ozoneBidAdapter.js | 534 ++++++++++----------- modules/ozoneBidAdapter.md | 4 +- test/spec/modules/ozoneBidAdapter_spec.js | 560 ++++++++-------------- 3 files changed, 461 insertions(+), 637 deletions(-) diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 4246a39bc69..8ada9d59ae2 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -5,108 +5,151 @@ import {config} from '../src/config.js'; import {getPriceBucketString} from '../src/cpmBucketManager.js'; import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'ozone'; -const ALLOWED_LOTAME_PARAMS = ['oz_lotameid', 'oz_lotamepid', 'oz_lotametpid']; // *** PROD *** -const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; -const OZONECOOKIESYNC = 'https://elb.the-ozone-project.com/static/load-cookie.html'; +const ORIGIN = 'https://elb.the-ozone-project.com' // applies only to auction & cookie +const AUCTIONURI = '/openrtb2/auction'; +const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; -const OZONEVERSION = '2.4.0'; +const OZONEVERSION = '2.5.0'; export const spec = { + gvlid: 524, + aliases: [{ code: 'lmc' }], + version: OZONEVERSION, code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], cookieSyncBag: {'publisherId': null, 'siteId': null, 'userIdObject': {}}, // variables we want to make available to cookie sync - propertyBag: {'lotameWasOverridden': 0, 'pageId': null, 'buildRequestsStart': 0, 'buildRequestsEnd': 0}, /* allow us to store vars in instance scope - needs to be an object to be mutable */ - + propertyBag: {'pageId': null, 'buildRequestsStart': 0, 'buildRequestsEnd': 0}, /* allow us to store vars in instance scope - needs to be an object to be mutable */ + whitelabel_defaults: { + 'logId': 'OZONE', + 'bidder': 'ozone', + 'keyPrefix': 'oz', + 'auctionUrl': ORIGIN + AUCTIONURI, + 'cookieSyncUrl': ORIGIN + OZONECOOKIESYNC, + 'rendererUrl': OZONE_RENDERER_URL + }, + /** + * make sure that the whitelabel/default values are available in the propertyBag + * @param bid Object : the bid + */ + loadWhitelabelData(bid) { + if (this.propertyBag.whitelabel) { return; } + this.propertyBag.whitelabel = JSON.parse(JSON.stringify(this.whitelabel_defaults)); + let bidder = bid.bidder || 'ozone'; // eg. ozone + this.propertyBag.whitelabel.logId = bidder.toUpperCase(); + this.propertyBag.whitelabel.bidder = bidder; + let bidderConfig = config.getConfig(bidder) || {}; + if (bidderConfig.kvpPrefix) { + this.propertyBag.whitelabel.keyPrefix = bidderConfig.kvpPrefix; + } + if (bidderConfig.endpointOverride) { + if (bidderConfig.endpointOverride.origin) { + this.propertyBag.whitelabel.auctionUrl = bidderConfig.endpointOverride.origin + AUCTIONURI; + this.propertyBag.whitelabel.cookieSyncUrl = bidderConfig.endpointOverride.origin + OZONECOOKIESYNC; + } + if (bidderConfig.endpointOverride.rendererUrl) { + this.propertyBag.whitelabel.rendererUrl = bidderConfig.endpointOverride.rendererUrl; + } + } + this.logInfo('set propertyBag.whitelabel to', this.propertyBag.whitelabel); + }, + getAuctionUrl() { + return this.propertyBag.whitelabel.auctionUrl; + }, + getCookieSyncUrl() { + return this.propertyBag.whitelabel.cookieSyncUrl; + }, + getRendererUrl() { + return this.propertyBag.whitelabel.rendererUrl; + }, + /** + * wrappers for this.logInfo logWarn & logError, to add the proper prefix + */ + logInfo() { + if (!this.propertyBag.whitelabel) { return; } + let args = arguments; + args[0] = `${this.propertyBag.whitelabel.logId}: ${arguments[0]}`; + utils.logInfo.apply(this, args); + }, + logError() { + if (!this.propertyBag.whitelabel) { return; } + let args = arguments; + args[0] = `${this.propertyBag.whitelabel.logId}: ${arguments[0]}`; + utils.logError.apply(this, args); + }, + logWarn() { + if (!this.propertyBag.whitelabel) { return; } + let args = arguments; + args[0] = `${this.propertyBag.whitelabel.logId}: ${arguments[0]}`; + utils.logWarn.apply(this, args); + }, /** * Basic check to see whether required parameters are in the request. * @param bid * @returns {boolean} */ isBidRequestValid(bid) { - utils.logInfo('OZONE: isBidRequestValid : ', config.getConfig(), bid); + this.loadWhitelabelData(bid); + this.logInfo('isBidRequestValid : ', config.getConfig(), bid); let adUnitCode = bid.adUnitCode; // adunit[n].code if (!(bid.params.hasOwnProperty('placementId'))) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED', adUnitCode); return false; } if (!this.isValidPlacementId(bid.params.placementId)) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); return false; } if (!(bid.params.hasOwnProperty('publisherId'))) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED', adUnitCode); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens', adUnitCode); return false; } if (!(bid.params.hasOwnProperty('siteId'))) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED', adUnitCode); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); return false; } if (bid.params.hasOwnProperty('customParams')) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); return false; } if (bid.params.hasOwnProperty('customData')) { if (!Array.isArray(bid.params.customData)) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is not an Array', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : customData is not an Array', adUnitCode); return false; } if (bid.params.customData.length < 1) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); return false; } if (!(bid.params.customData[0]).hasOwnProperty('targeting')) { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); return false; } if (typeof bid.params.customData[0]['targeting'] != 'object') { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); - return false; - } - } - if (bid.params.hasOwnProperty('lotameData')) { - if (typeof bid.params.lotameData !== 'object') { - utils.logError('OZONE: OZONE BID ADAPTER VALIDATION FAILED : lotameData is not an object', adUnitCode); + this.logError('BID ADAPTER VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); return false; } } if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { - utils.logError('OZONE: No video context key/value in bid. Rejecting bid: ', bid); + this.logError('No video context key/value in bid. Rejecting bid: ', bid); return false; } if (bid.mediaTypes[VIDEO].context !== 'instream' && bid.mediaTypes[VIDEO].context !== 'outstream') { - utils.logError('OZONE: video.context is invalid. Only instream/outstream video is supported. Rejecting bid: ', bid); + this.logError('video.context is invalid. Only instream/outstream video is supported. Rejecting bid: ', bid); return false; } } - // guard against hacks in GET parameters that we might allow - const arrLotameOverride = this.getLotameOverrideParams(); - // lotame override, test params. All 3 must be present, or none. - let lotameKeys = Object.keys(arrLotameOverride); - if (lotameKeys.length === ALLOWED_LOTAME_PARAMS.length) { - utils.logInfo('OZONE: VALIDATION : arrLotameOverride', arrLotameOverride); - for (let i in lotameKeys) { - if (!arrLotameOverride[ALLOWED_LOTAME_PARAMS[i]].toString().match(/^[0-9a-zA-Z]+$/)) { - utils.logError('OZONE: Only letters & numbers allowed in lotame override: ' + i.toString() + ': ' + arrLotameOverride[ALLOWED_LOTAME_PARAMS[i]].toString() + '. Rejecting bid: ', bid); - return false; - } - } - } else if (lotameKeys.length > 0) { - utils.logInfo('OZONE: VALIDATION : arrLotameOverride', arrLotameOverride); - utils.logError('OZONE: lotame override params are incomplete. You must set all ' + ALLOWED_LOTAME_PARAMS.length + ': ' + JSON.stringify(ALLOWED_LOTAME_PARAMS) + ', . Rejecting bid: ', bid); - return false; - } return true; }, @@ -119,8 +162,11 @@ export const spec = { }, buildRequests(validBidRequests, bidderRequest) { + this.loadWhitelabelData(validBidRequests[0]); this.propertyBag.buildRequestsStart = new Date().getTime(); - utils.logInfo(`OZONE: buildRequests time: ${this.propertyBag.buildRequestsStart} ozone v ${OZONEVERSION} validBidRequests`, validBidRequests, 'bidderRequest', bidderRequest); + let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone + let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; + this.logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${OZONEVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); // First check - is there any config to block this request? if (this.blockTheRequest()) { return []; @@ -132,31 +178,31 @@ export const spec = { this.cookieSyncBag.publisherId = utils.deepAccess(validBidRequests[0], 'params.publisherId'); htmlParams = validBidRequests[0].params; } - utils.logInfo('OZONE: cookie sync bag', this.cookieSyncBag); - let singleRequest = config.getConfig('ozone.singleRequest'); + this.logInfo('cookie sync bag', this.cookieSyncBag); + let singleRequest = this.getWhitelabelConfigItem('ozone.singleRequest'); singleRequest = singleRequest !== false; // undefined & true will be true - utils.logInfo('OZONE: config ozone.singleRequest : ', singleRequest); + this.logInfo(`config ${whitelabelBidder}.singleRequest : `, singleRequest); let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params delete ozoneRequest.test; // don't allow test to be set in the config - ONLY use $_GET['pbjs_debug'] if (bidderRequest && bidderRequest.gdprConsent) { - utils.logInfo('OZONE: ADDING GDPR info'); - let apiVersion = utils.deepAccess(bidderRequest.gdprConsent, 'apiVersion', '1'); + this.logInfo('ADDING GDPR info'); + let apiVersion = bidderRequest.gdprConsent.apiVersion || '1'; ozoneRequest.regs = {ext: {gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, apiVersion: apiVersion}}; if (ozoneRequest.regs.ext.gdpr) { ozoneRequest.user = ozoneRequest.user || {}; ozoneRequest.user.ext = {'consent': bidderRequest.gdprConsent.consentString}; } else { - utils.logInfo('OZONE: **** Strange CMP info: bidderRequest.gdprConsent exists BUT bidderRequest.gdprConsent.gdprApplies is false. See bidderRequest logged above. ****'); + this.logInfo('**** Strange CMP info: bidderRequest.gdprConsent exists BUT bidderRequest.gdprConsent.gdprApplies is false. See bidderRequest logged above. ****'); } } else { - utils.logInfo('OZONE: WILL NOT ADD GDPR info; no bidderRequest.gdprConsent object was present.'); + this.logInfo('WILL NOT ADD GDPR info; no bidderRequest.gdprConsent object was present.'); } const getParams = this.getGetParametersAsObject(); - const ozTestMode = getParams.hasOwnProperty('oztestmode') ? getParams.oztestmode : null; // this can be any string, it's used for testing ads + const wlOztestmodeKey = whitelabelPrefix + 'testmode'; + const isTestMode = getParams[wlOztestmodeKey] || null; // this can be any string, it's used for testing ads ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string - let lotameDataSingle = {}; // we will capture lotame data once & send it to the server as ext.ozone.lotameData // build the array of params to attach to `imp` let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; @@ -168,18 +214,18 @@ export const spec = { let arrBannerSizes = []; if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { - utils.logInfo('OZONE: no mediaTypes detected - will use the sizes array in the config root'); + this.logInfo('no mediaTypes detected - will use the sizes array in the config root'); arrBannerSizes = ozoneBidRequest.sizes; } else { - utils.logInfo('OZONE: no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); + this.logInfo('no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); } } else { if (ozoneBidRequest.mediaTypes.hasOwnProperty(BANNER)) { arrBannerSizes = ozoneBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ - utils.logInfo('OZONE: setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); + this.logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); } if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { - utils.logInfo('OZONE: openrtb 2.5 compliant video'); + this.logInfo('openrtb 2.5 compliant video'); // examine all the video attributes in the config, and either put them into obj.video if allowed by IAB2.5 or else in to obj.video.ext if (typeof ozoneBidRequest.mediaTypes[VIDEO] == 'object') { let childConfig = utils.deepAccess(ozoneBidRequest, 'params.video', {}); @@ -188,25 +234,25 @@ export const spec = { } // we need to duplicate some of the video values let wh = getWidthAndHeightFromVideoObject(obj.video); - utils.logInfo('OZONE: setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video, 'wh=', wh); + this.logInfo('setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video, 'wh=', wh); if (wh && typeof wh === 'object') { obj.video.w = wh['w']; obj.video.h = wh['h']; if (playerSizeIsNestedArray(obj.video)) { // this should never happen; it was in the original spec for this change though. - utils.logInfo('OZONE: setting obj.video.format to be an array of objects'); + this.logInfo('setting obj.video.format to be an array of objects'); obj.video.ext.format = [wh]; } else { - utils.logInfo('OZONE: setting obj.video.format to be an object'); + this.logInfo('setting obj.video.format to be an object'); obj.video.ext.format = wh; } } else { - utils.logWarn('OZONE: cannot set w, h & format values for video; the config is not right'); + this.logWarn('cannot set w, h & format values for video; the config is not right'); } } // Native integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { obj.native = ozoneBidRequest.mediaTypes[NATIVE]; - utils.logInfo('OZONE: setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + this.logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); } } if (arrBannerSizes.length > 0) { @@ -223,52 +269,55 @@ export const spec = { // these 3 MUST exist - we check them in the validation method obj.placementId = placementId; // build the imp['ext'] object - obj.ext = {'prebid': {'storedrequest': {'id': placementId}}, 'ozone': {}}; - obj.ext.ozone.adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' - obj.ext.ozone.transactionId = ozoneBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit + obj.ext = {'prebid': {'storedrequest': {'id': placementId}}}; + obj.ext[whitelabelBidder] = {}; + obj.ext[whitelabelBidder].adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' + obj.ext[whitelabelBidder].transactionId = ozoneBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit if (ozoneBidRequest.params.hasOwnProperty('customData')) { - obj.ext.ozone.customData = ozoneBidRequest.params.customData; + obj.ext[whitelabelBidder].customData = ozoneBidRequest.params.customData; } - utils.logInfo('OZONE: obj.ext.ozone is ', obj.ext.ozone); - if (ozTestMode != null) { - utils.logInfo('OZONE: setting ozTestMode to ', ozTestMode); - if (obj.ext.ozone.hasOwnProperty('customData')) { - for (let i = 0; i < obj.ext.ozone.customData.length; i++) { - obj.ext.ozone.customData[i]['targeting']['oztestmode'] = ozTestMode; + this.logInfo(`obj.ext.${whitelabelBidder} is `, obj.ext[whitelabelBidder]); + if (isTestMode != null) { + this.logInfo('setting isTestMode to ', isTestMode); + if (obj.ext[whitelabelBidder].hasOwnProperty('customData')) { + for (let i = 0; i < obj.ext[whitelabelBidder].customData.length; i++) { + obj.ext[whitelabelBidder].customData[i]['targeting'][wlOztestmodeKey] = isTestMode; } } else { - obj.ext.ozone.customData = [{'settings': {}, 'targeting': {'oztestmode': ozTestMode}}]; + obj.ext[whitelabelBidder].customData = [{'settings': {}, 'targeting': {}}]; + obj.ext[whitelabelBidder].customData[0].targeting[wlOztestmodeKey] = isTestMode; } - } else { - utils.logInfo('OZONE: no ozTestMode '); - } - // now deal with lotame, including the optional override parameters - if (Object.keys(lotameDataSingle).length === 0) { // we've not yet found lotameData, see if we can get it from this bid request object - lotameDataSingle = this.tryGetLotameData(ozoneBidRequest); } return obj; }); // in v 2.0.0 we moved these outside of the individual ad slots - let extObj = {'ozone': {'oz_pb_v': OZONEVERSION, 'oz_rw': placementIdOverrideFromGetParam ? 1 : 0, 'oz_lot_rw': this.propertyBag.lotameWasOverridden}}; + let extObj = {}; + extObj[whitelabelBidder] = {}; + extObj[whitelabelBidder][whitelabelPrefix + '_pb_v'] = OZONEVERSION; + extObj[whitelabelBidder][whitelabelPrefix + '_rw'] = placementIdOverrideFromGetParam ? 1 : 0; if (validBidRequests.length > 0) { - let userIds = this.findAllUserIds(validBidRequests[0]); + let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info + // let userIds = this.findAllUserIds(validBidRequests[0]); if (userIds.hasOwnProperty('pubcid')) { - extObj.ozone.pubcid = userIds.pubcid; + extObj[whitelabelBidder].pubcid = userIds.pubcid; } } - extObj.ozone.pv = this.getPageId(); // attach the page ID that will be common to all auciton calls for this page if refresh() is called - extObj.ozone.lotameData = lotameDataSingle; // 2.4.0 moved lotameData out of bid objects into the single ext.ozone area to remove duplication - let ozOmpFloorDollars = config.getConfig('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') - utils.logInfo('OZONE: oz_omp_floor dollar value = ', ozOmpFloorDollars); + extObj[whitelabelBidder].pv = this.getPageId(); // attach the page ID that will be common to all auciton calls for this page if refresh() is called + let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') + this.logInfo(`${whitelabelPrefix}_omp_floor dollar value = `, ozOmpFloorDollars); if (typeof ozOmpFloorDollars === 'number') { - extObj.ozone.oz_omp_floor = ozOmpFloorDollars; + extObj[whitelabelBidder][whitelabelPrefix + '_omp_floor'] = ozOmpFloorDollars; } else if (typeof ozOmpFloorDollars !== 'undefined') { - utils.logError('OZONE: oz_omp_floor is invalid - IF SET then this must be a number, representing dollar value eg. oz_omp_floor: 1.55. You have it set as a ' + (typeof ozOmpFloorDollars)); + this.logError(`${whitelabelPrefix}_omp_floor is invalid - IF SET then this must be a number, representing dollar value eg. ${whitelabelPrefix}_omp_floor: 1.55. You have it set as a ` + (typeof ozOmpFloorDollars)); } - let ozWhitelistAdserverKeys = config.getConfig('ozone.oz_whitelist_adserver_keys'); + let ozWhitelistAdserverKeys = this.getWhitelabelConfigItem('ozone.oz_whitelist_adserver_keys'); let useOzWhitelistAdserverKeys = utils.isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; - extObj.ozone.oz_kvp_rw = useOzWhitelistAdserverKeys ? 1 : 0; + extObj[whitelabelBidder][whitelabelPrefix + '_kvp_rw'] = useOzWhitelistAdserverKeys ? 1 : 0; + if (whitelabelBidder != 'ozone') { + this.logInfo('setting aliases object'); + extObj.prebid = {aliases: {'ozone': whitelabelBidder}}; + } var userExtEids = this.generateEids(validBidRequests); // generate the UserIDs in the correct format for UserId module @@ -277,7 +326,7 @@ export const spec = { 'page': document.location.href, 'id': htmlParams.siteId }; - ozoneRequest.test = (getParams.hasOwnProperty('pbjs_debug') && getParams['pbjs_debug'] == 'true') ? 1 : 0; + ozoneRequest.test = (getParams.hasOwnProperty('pbjs_debug') && getParams['pbjs_debug'] === 'true') ? 1 : 0; // this is for 2.2.1 // coppa compliance @@ -287,7 +336,7 @@ export const spec = { // return the single request object OR the array: if (singleRequest) { - utils.logInfo('OZONE: buildRequests starting to generate response for a single request'); + this.logInfo('buildRequests starting to generate response for a single request'); ozoneRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. ozoneRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? ozoneRequest.imp = tosendtags; @@ -296,36 +345,36 @@ export const spec = { utils.deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); var ret = { method: 'POST', - url: OZONEURI, + url: this.getAuctionUrl(), data: JSON.stringify(ozoneRequest), bidderRequest: bidderRequest }; - utils.logInfo('OZONE: buildRequests ozoneRequest for single = ', ozoneRequest); + this.logInfo('buildRequests request data for single = ', ozoneRequest); this.propertyBag.buildRequestsEnd = new Date().getTime(); - utils.logInfo(`OZONE: buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); + this.logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); return ret; } // not single request - pull apart the tosendtags array & return an array of objects each containing one element in the imp array. let arrRet = tosendtags.map(imp => { - utils.logInfo('OZONE: buildRequests starting to generate non-single response, working on imp : ', imp); + this.logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); let ozoneRequestSingle = Object.assign({}, ozoneRequest); - imp.ext.ozone.pageAuctionId = bidderRequest['auctionId']; // make a note in the ext object of what the original auctionId was, in the bidderRequest object - ozoneRequestSingle.id = imp.ext.ozone.transactionId; // Unique ID of the bid request, provided by the exchange. - ozoneRequestSingle.auctionId = imp.ext.ozone.transactionId; // not sure if this should be here? + imp.ext[whitelabelBidder].pageAuctionId = bidderRequest['auctionId']; // make a note in the ext object of what the original auctionId was, in the bidderRequest object + ozoneRequestSingle.id = imp.ext[whitelabelBidder].transactionId; // Unique ID of the bid request, provided by the exchange. + ozoneRequestSingle.auctionId = imp.ext[whitelabelBidder].transactionId; // not sure if this should be here? ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.ext = extObj; - ozoneRequestSingle.source = {'tid': imp.ext.ozone.transactionId}; + ozoneRequestSingle.source = {'tid': imp.ext[whitelabelBidder].transactionId}; utils.deepSetValue(ozoneRequestSingle, 'user.ext.eids', userExtEids); - utils.logInfo('OZONE: buildRequests ozoneRequestSingle (for non-single) = ', ozoneRequestSingle); + this.logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', - url: OZONEURI, + url: this.getAuctionUrl(), data: JSON.stringify(ozoneRequestSingle), bidderRequest: bidderRequest }; }); this.propertyBag.buildRequestsEnd = new Date().getTime(); - utils.logInfo(`OZONE: buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); + this.logInfo(`buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); return arrRet; }, /** @@ -339,9 +388,12 @@ export const spec = { * @returns {*} */ interpretResponse(serverResponse, request) { + if (request && request.bidderRequest && request.bidderRequest.bids) { this.loadWhitelabelData(request.bidderRequest.bids[0]); } let startTime = new Date().getTime(); - utils.logInfo(`OZONE: interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); - utils.logInfo(`OZONE: serverResponse, request`, serverResponse, request); + let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone + let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; + this.logInfo(`interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); + this.logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); serverResponse = serverResponse.body || {}; // note that serverResponse.id value is the auction_id we might want to use for reporting reasons. if (!serverResponse.hasOwnProperty('seatbid')) { @@ -351,91 +403,91 @@ export const spec = { return []; } let arrAllBids = []; - let enhancedAdserverTargeting = config.getConfig('ozone.enhancedAdserverTargeting'); - utils.logInfo('OZONE: enhancedAdserverTargeting', enhancedAdserverTargeting); + let enhancedAdserverTargeting = this.getWhitelabelConfigItem('ozone.enhancedAdserverTargeting'); + this.logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); if (typeof enhancedAdserverTargeting == 'undefined') { enhancedAdserverTargeting = true; } - utils.logInfo('OZONE: enhancedAdserverTargeting', enhancedAdserverTargeting); + this.logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute. serverResponse.seatbid = this.removeSingleBidderMultipleBids(serverResponse.seatbid); - let ozOmpFloorDollars = config.getConfig('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') + let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') let addOzOmpFloorDollars = typeof ozOmpFloorDollars === 'number'; - let ozWhitelistAdserverKeys = config.getConfig('ozone.oz_whitelist_adserver_keys'); + let ozWhitelistAdserverKeys = this.getWhitelabelConfigItem('ozone.oz_whitelist_adserver_keys'); let useOzWhitelistAdserverKeys = utils.isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; for (let i = 0; i < serverResponse.seatbid.length; i++) { let sb = serverResponse.seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { let thisRequestBid = this.getBidRequestForBidId(sb.bid[j].impid, request.bidderRequest.bids); - utils.logInfo(`OZONE seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); + this.logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); let thisBid = ozoneAddStandardProperties(sb.bid[j], defaultWidth, defaultHeight); let videoContext = null; let isVideo = false; let bidType = utils.deepAccess(thisBid, 'ext.prebid.type'); - utils.logInfo(`OZONE: this bid type is : ${bidType}`, j); + this.logInfo(`this bid type is : ${bidType}`, j); if (bidType === VIDEO) { isVideo = true; videoContext = this.getVideoContextForBidId(thisBid.bidId, request.bidderRequest.bids); // should be instream or outstream (or null if error) if (videoContext === 'outstream') { - utils.logInfo('OZONE: going to attach a renderer to OUTSTREAM video : ', j); + this.logInfo('going to attach a renderer to OUTSTREAM video : ', j); thisBid.renderer = newRenderer(thisBid.bidId); } else { - utils.logInfo('OZONE: bid is not an outstream video, will not attach a renderer: ', j); + this.logInfo('bid is not an outstream video, will not attach a renderer: ', j); } } let adserverTargeting = {}; if (enhancedAdserverTargeting) { let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); // add all the winning & non-winning bids for this bidId: - utils.logInfo('OZONE: Going to iterate allBidsForThisBidId', allBidsForThisBidid); - Object.keys(allBidsForThisBidid).forEach(function (bidderName, index, ar2) { - utils.logInfo(`OZONE: adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); + this.logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); + Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { + this.logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); // let bidderName = bidderNameWH.split('_')[0]; - adserverTargeting['oz_' + bidderName] = bidderName; - adserverTargeting['oz_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); - adserverTargeting['oz_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); - adserverTargeting['oz_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); - adserverTargeting['oz_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); + adserverTargeting[whitelabelPrefix + '_' + bidderName] = bidderName; + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { - adserverTargeting['oz_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); } if (addOzOmpFloorDollars) { - adserverTargeting['oz_' + bidderName + '_omp'] = allBidsForThisBidid[bidderName].price >= ozOmpFloorDollars ? '1' : '0'; + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_omp'] = allBidsForThisBidid[bidderName].price >= ozOmpFloorDollars ? '1' : '0'; } if (isVideo) { - adserverTargeting['oz_' + bidderName + '_vid'] = videoContext; // outstream or instream + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_vid'] = videoContext; // outstream or instream } - let flr = utils.deepAccess(allBidsForThisBidid[bidderName], 'ext.bidder.ozone.floor', null); + let flr = utils.deepAccess(allBidsForThisBidid[bidderName], `ext.bidder.${whitelabelBidder}.floor`, null); if (flr != null) { - adserverTargeting['oz_' + bidderName + '_flr'] = flr; + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_flr'] = flr; } - let rid = utils.deepAccess(allBidsForThisBidid[bidderName], 'ext.bidder.ozone.ruleId', null); + let rid = utils.deepAccess(allBidsForThisBidid[bidderName], `ext.bidder.${whitelabelBidder}.ruleId`, null); if (rid != null) { - adserverTargeting['oz_' + bidderName + '_rid'] = rid; + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_rid'] = rid; } if (bidderName.match(/^ozappnexus/)) { - adserverTargeting['oz_' + bidderName + '_sid'] = String(allBidsForThisBidid[bidderName].cid); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_sid'] = String(allBidsForThisBidid[bidderName].cid); } }); } else { if (useOzWhitelistAdserverKeys) { - utils.logWarn('OZONE: You have set a whitelist of adserver keys but this will be ignored because ozone.enhancedAdserverTargeting is set to false. No per-bid keys will be sent to adserver.'); + this.logWarn(`You have set a whitelist of adserver keys but this will be ignored because ${whitelabelBidder}.enhancedAdserverTargeting is set to false. No per-bid keys will be sent to adserver.`); } else { - utils.logInfo('OZONE: ozone.enhancedAdserverTargeting is set to false, so no per-bid keys will be sent to adserver.'); + this.logInfo(`${whitelabelBidder}.enhancedAdserverTargeting is set to false, so no per-bid keys will be sent to adserver.`); } } // also add in the winning bid, to be sent to dfp let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); - adserverTargeting['oz_auc_id'] = String(request.bidderRequest.auctionId); - adserverTargeting['oz_winner'] = String(winningSeat); + adserverTargeting[whitelabelPrefix + '_auc_id'] = String(request.bidderRequest.auctionId); + adserverTargeting[whitelabelPrefix + '_winner'] = String(winningSeat); if (enhancedAdserverTargeting) { - adserverTargeting['oz_imp_id'] = String(winningBid.impid); - adserverTargeting['oz_pb_v'] = OZONEVERSION; + adserverTargeting[whitelabelPrefix + '_imp_id'] = String(winningBid.impid); + adserverTargeting[whitelabelPrefix + '_pb_v'] = OZONEVERSION; } if (useOzWhitelistAdserverKeys) { // delete any un-whitelisted keys - utils.logInfo('OZONE: Going to filter out adserver targeting keys not in the whitelist: ', ozWhitelistAdserverKeys); + this.logInfo('Going to filter out adserver targeting keys not in the whitelist: ', ozWhitelistAdserverKeys); Object.keys(adserverTargeting).forEach(function(key) { if (ozWhitelistAdserverKeys.indexOf(key) === -1) { delete adserverTargeting[key]; } }); } thisBid.adserverTargeting = adserverTargeting; @@ -443,9 +495,22 @@ export const spec = { } } let endTime = new Date().getTime(); - utils.logInfo(`OZONE: interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); + this.logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); return arrAllBids; }, + /** + * Use this to get all config values + * Now it's getting complicated with whitelabeling, this simplifies the code for getting config values. + * eg. to get ozone.oz_omp_floor you just send '_omp_floor' + * @param ozoneVersion string like 'ozone.oz_omp_floor' + * @return {string|object} + */ + getWhitelabelConfigItem(ozoneVersion) { + if (this.propertyBag.whitelabel.bidder == 'ozone') { return config.getConfig(ozoneVersion); } + let whitelabelledSearch = ozoneVersion.replace('ozone', this.propertyBag.whitelabel.bidder); + whitelabelledSearch = ozoneVersion.replace('oz_', this.propertyBag.whitelabel.keyPrefix + '_'); + return config.getConfig(whitelabelledSearch); + }, /** * If a bidder bids for > 1 size for an adslot, allow only the highest bid * @param seatbid object (serverResponse.seatbid) @@ -475,7 +540,7 @@ export const spec = { }, // see http://prebid.org/dev-docs/bidder-adaptor.html#registering-user-syncs getUserSyncs(optionsType, serverResponse, gdprConsent) { - utils.logInfo('OZONE: getUserSyncs optionsType, serverResponse, gdprConsent, cookieSyncBag', optionsType, serverResponse, gdprConsent, this.cookieSyncBag); + this.logInfo('getUserSyncs optionsType, serverResponse, gdprConsent, cookieSyncBag', optionsType, serverResponse, gdprConsent, this.cookieSyncBag); if (!serverResponse || serverResponse.length === 0) { return []; } @@ -494,15 +559,16 @@ export const spec = { arrQueryString.push('publisherId=' + this.cookieSyncBag.publisherId); arrQueryString.push('siteId=' + this.cookieSyncBag.siteId); arrQueryString.push('cb=' + Date.now()); + arrQueryString.push('bidder=' + this.propertyBag.whitelabel.bidder); var strQueryString = arrQueryString.join('&'); if (strQueryString.length > 0) { strQueryString = '?' + strQueryString; } - utils.logInfo('OZONE: getUserSyncs going to return cookie sync url : ' + OZONECOOKIESYNC + strQueryString); + this.logInfo('getUserSyncs going to return cookie sync url : ' + this.getCookieSyncUrl() + strQueryString); return [{ type: 'iframe', - url: OZONECOOKIESYNC + strQueryString + url: this.getCookieSyncUrl() + strQueryString }]; } }, @@ -535,26 +601,31 @@ export const spec = { }, /** * Look for pubcid & all the other IDs according to http://prebid.org/dev-docs/modules/userId.html + * NOTE that criteortus is deprecated & should be removed asap * @return map */ findAllUserIds(bidRequest) { var ret = {}; - let searchKeysSingle = ['pubcid', 'tdid', 'parrableId', 'idl_env', 'digitrustid', 'criteortus']; + // @todo - what is fabrick called & where to look for it? If it's a simple value then it will automatically be ok + let searchKeysSingle = ['pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'criteoId', 'criteortus', + 'sharedid', 'lotamePanoramaId', 'fabrickId']; if (bidRequest.hasOwnProperty('userId')) { for (let arrayId in searchKeysSingle) { let key = searchKeysSingle[arrayId]; if (bidRequest.userId.hasOwnProperty(key)) { - ret[key] = bidRequest.userId[key]; + if (typeof (bidRequest.userId[key]) == 'string') { + ret[key] = bidRequest.userId[key]; + } else if (typeof (bidRequest.userId[key]) == 'object') { + ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values + } else { + this.logError(`failed to get string key value for userId : ${key}`); + } } } var lipbid = utils.deepAccess(bidRequest.userId, 'lipb.lipbid'); 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'); @@ -564,69 +635,6 @@ export const spec = { } return ret; }, - /** - * get all the lotame override keys/values from the querystring. - * @return object containing zero or more keys/values - */ - getLotameOverrideParams() { - const arrGet = this.getGetParametersAsObject(); - utils.logInfo('OZONE: getLotameOverrideParams - arrGet=', arrGet); - let arrRet = {}; - for (let i in ALLOWED_LOTAME_PARAMS) { - if (arrGet.hasOwnProperty(ALLOWED_LOTAME_PARAMS[i])) { - arrRet[ALLOWED_LOTAME_PARAMS[i]] = arrGet[ALLOWED_LOTAME_PARAMS[i]]; - } - } - return arrRet; - }, - /** - * Boolean function to check that this lotame data is valid (check Audience.id) - */ - isLotameDataValid(lotameObj) { - if (!lotameObj.hasOwnProperty('Profile')) return false; - let prof = lotameObj.Profile; - if (!prof.hasOwnProperty('tpid')) return false; - if (!prof.hasOwnProperty('pid')) return false; - let audiences = utils.deepAccess(prof, 'Audiences.Audience'); - if (typeof audiences != 'object') { - return false; - } - for (var i = 0; i < audiences.length; i++) { - let aud = audiences[i]; - if (!aud.hasOwnProperty('id')) { - return false; - } - } - return true; // All Audiences objects have an 'id' key - }, - /** - * Use the arrOverride keys/vals to update the arrExisting lotame object. - * Ideally we will only be using the oz_lotameid value to update the audiences id, but in the event of bad/missing - * pid & tpid we will also have to use substitute values for those too. - * - * @param objOverride object will contain all the ALLOWED_LOTAME_PARAMS parameters - * @param lotameData object might be {} or contain the lotame data - */ - makeLotameObjectFromOverride(objOverride, lotameData) { - if ((lotameData.hasOwnProperty('Profile') && Object.keys(lotameData.Profile).length < 3) || - (!lotameData.hasOwnProperty('Profile'))) { // bad or empty lotame object (should contain pid, tpid & Audiences object) - build a total replacement - utils.logInfo('OZONE: makeLotameObjectFromOverride will return a full default lotame object'); - return { - 'Profile': { - 'tpid': objOverride['oz_lotametpid'], - 'pid': objOverride['oz_lotamepid'], - 'Audiences': {'Audience': [{'id': objOverride['oz_lotameid'], 'abbr': objOverride['oz_lotameid']}]} - } - }; - } - if (utils.deepAccess(lotameData, 'Profile.Audiences.Audience')) { - utils.logInfo('OZONE: makeLotameObjectFromOverride will return the existing lotame object with updated Audience by oz_lotameid'); - lotameData.Profile.Audiences.Audience = [{'id': objOverride['oz_lotameid'], 'abbr': objOverride['oz_lotameid']}]; - return lotameData; - } - utils.logInfo('OZONE: makeLotameObjectFromOverride Weird error - failed to find Profile.Audiences.Audience in lotame object. Will return the object as-is'); - return lotameData; - }, /** * Convenient method to get the value we need for the placementId - ONLY from the bidRequest - NOT taking into account any GET override ID * @param bidRequest @@ -642,48 +650,30 @@ export const spec = { * @returns null|string */ getPlacementIdOverrideFromGetParam() { + let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; let arr = this.getGetParametersAsObject(); - if (arr.hasOwnProperty('ozstoredrequest')) { - if (this.isValidPlacementId(arr.ozstoredrequest)) { - utils.logInfo('OZONE: using GET ozstoredrequest ' + arr.ozstoredrequest + ' to replace placementId'); - return arr.ozstoredrequest; + if (arr.hasOwnProperty(whitelabelPrefix + 'storedrequest')) { + if (this.isValidPlacementId(arr[whitelabelPrefix + 'storedrequest'])) { + this.logInfo(`using GET ${whitelabelPrefix}storedrequest ` + arr[whitelabelPrefix + 'storedrequest'] + ' to replace placementId'); + return arr[whitelabelPrefix + 'storedrequest']; } else { - utils.logError('OZONE: GET ozstoredrequest FAILED VALIDATION - will not use it'); + this.logError(`GET ${whitelabelPrefix}storedrequest FAILED VALIDATION - will not use it`); } } return null; }, - /** - * Produces external userid object - */ - addExternalUserId(eids, value, source, atype) { - if (utils.isStr(value)) { - eids.push({ - source, - uids: [{ - id: value, - atype - }] - }); - } - }, /** * Generate an object we can append to the auction request, containing user data formatted correctly for different ssps + * http://prebid.org/dev-docs/modules/userId.html * @param validBidRequests * @return {Array} */ generateEids(validBidRequests) { - let eids = []; - this.handleTTDId(eids, validBidRequests); + let eids; const bidRequest = validBidRequests[0]; 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.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); - this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.parrableId.eid`), 'parrable.com', 1); + eids = bidRequest.userIdAsEids; + this.handleTTDId(eids, validBidRequests); } return eids; }, @@ -725,9 +715,9 @@ export const spec = { */ blockTheRequest() { // if there is an ozone.oz_request = false then quit now. - let ozRequest = config.getConfig('ozone.oz_request'); + let ozRequest = this.getWhitelabelConfigItem('ozone.oz_request'); if (typeof ozRequest == 'boolean' && !ozRequest) { - utils.logWarn('OZONE: Will not allow auction : ozone.oz_request is set to false'); + this.logWarn(`Will not allow auction : ${this.propertyBag.whitelabel.keyPrefix}one.${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); return true; } return false; @@ -747,34 +737,6 @@ export const spec = { } return this.propertyBag.pageId; }, - /** - * handle the complexity of there possibly being lotameData override (may be valid/invalid) & there may or may not be lotameData present in the bidRequest - * NOTE THAT this will also set this.propertyBag.lotameWasOverridden=1 if we use lotame override - * @param ozoneBidRequest - * @return object representing the absolute lotameData we need to use. - */ - tryGetLotameData: function(ozoneBidRequest) { - const arrLotameOverride = this.getLotameOverrideParams(); - let ret = {}; - if (Object.keys(arrLotameOverride).length === ALLOWED_LOTAME_PARAMS.length) { - // all override params are present, override lotame object: - if (ozoneBidRequest.params.hasOwnProperty('lotameData')) { - ret = this.makeLotameObjectFromOverride(arrLotameOverride, ozoneBidRequest.params.lotameData); - } else { - ret = this.makeLotameObjectFromOverride(arrLotameOverride, {}); - } - this.propertyBag.lotameWasOverridden = 1; - } else if (ozoneBidRequest.params.hasOwnProperty('lotameData')) { - // no lotame override, use it as-is - if (this.isLotameDataValid(ozoneBidRequest.params.lotameData)) { - ret = ozoneBidRequest.params.lotameData; - } else { - utils.logError('OZONE: INVALID LOTAME DATA FOUND - WILL NOT USE THIS AT ALL ELSE IT MIGHT BREAK THE AUCTION CALL!', ozoneBidRequest.params.lotameData); - ret = {}; - } - } - return ret; - }, unpackVideoConfigIntoIABformat(videoConfig, childConfig) { let ret = {'ext': {}}; ret = this._unpackVideoConfigIntoIABformat(ret, videoConfig); @@ -853,7 +815,7 @@ export const spec = { * @returns seatbid object */ export function injectAdIdsIntoAllBidResponses(seatbid) { - utils.logInfo('OZONE: injectAdIdsIntoAllBidResponses', seatbid); + spec.logInfo('injectAdIdsIntoAllBidResponses', seatbid); for (let i = 0; i < seatbid.length; i++) { let sb = seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { @@ -879,7 +841,7 @@ export function checkDeepArray(Arr) { export function defaultSize(thebidObj) { if (!thebidObj) { - utils.logInfo('OZONE: defaultSize received empty bid obj! going to return fixed default size'); + spec.logInfo('defaultSize received empty bid obj! going to return fixed default size'); return { 'defaultHeight': 250, 'defaultWidth': 300 @@ -932,7 +894,7 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { for (let k = 0; k < theseBids.length; k++) { if (theseBids[k].impid === matchBidId) { if (objBids.hasOwnProperty(thisSeat)) { // > 1 bid for an adunit from a bidder - only use the one with the highest bid - // objBids[`${thisSeat}${theseBids[k].w}x${theseBids[k].h}`] = theseBids[k]; + // objBids[`${thisSeat}${theseBids[k].w}x${theseBids[k].h}`] = theseBids[k]; if (objBids[thisSeat]['price'] < theseBids[k].price) { objBids[thisSeat] = theseBids[k]; } @@ -957,14 +919,14 @@ export function getRoundedBid(price, mediaType) { let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets); let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets); - utils.logInfo('OZONE: getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); + spec.logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); let priceStringsObj = getPriceBucketString( price, theConfigObject, config.getConfig('currency.granularityMultiplier') ); - utils.logInfo('OZONE: priceStringsObj', priceStringsObj); + spec.logInfo('priceStringsObj', priceStringsObj); // by default, without any custom granularity set, you get granularity name : 'medium' let granularityNamePriceStringsKeyMapping = { 'medium': 'med', @@ -975,7 +937,7 @@ export function getRoundedBid(price, mediaType) { }; if (granularityNamePriceStringsKeyMapping.hasOwnProperty(theConfigKey)) { let priceStringsKey = granularityNamePriceStringsKeyMapping[theConfigKey]; - utils.logInfo('OZONE: getRoundedBid: looking for priceStringsKey:', priceStringsKey); + spec.logInfo('getRoundedBid: looking for priceStringsKey:', priceStringsKey); return priceStringsObj[priceStringsKey]; } return priceStringsObj['auto']; @@ -1044,15 +1006,15 @@ export function getWidthAndHeightFromVideoObject(objVideo) { return null; } if (playerSize[0] && typeof playerSize[0] === 'object') { - utils.logInfo('OZONE: getWidthAndHeightFromVideoObject found nested array inside playerSize.', playerSize[0]); + spec.logInfo('getWidthAndHeightFromVideoObject found nested array inside playerSize.', playerSize[0]); playerSize = playerSize[0]; if (typeof playerSize[0] !== 'number' && typeof playerSize[0] !== 'string') { - utils.logInfo('OZONE: getWidthAndHeightFromVideoObject found non-number/string type inside the INNER array in playerSize. This is totally wrong - cannot continue.', playerSize[0]); + spec.logInfo('getWidthAndHeightFromVideoObject found non-number/string type inside the INNER array in playerSize. This is totally wrong - cannot continue.', playerSize[0]); return null; } } if (playerSize.length !== 2) { - utils.logInfo('OZONE: getWidthAndHeightFromVideoObject found playerSize with length of ' + playerSize.length + '. This is totally wrong - cannot continue.'); + spec.logInfo('getWidthAndHeightFromVideoObject found playerSize with length of ' + playerSize.length + '. This is totally wrong - cannot continue.'); return null; } return ({'w': playerSize[0], 'h': playerSize[1]}); @@ -1079,17 +1041,17 @@ export function playerSizeIsNestedArray(objVideo) { * @returns {*} */ function getPlayerSizeFromObject(objVideo) { - utils.logInfo('OZONE: getPlayerSizeFromObject received object', objVideo); + spec.logInfo('getPlayerSizeFromObject received object', objVideo); let playerSize = utils.deepAccess(objVideo, 'playerSize'); if (!playerSize) { playerSize = utils.deepAccess(objVideo, 'ext.playerSize'); } if (!playerSize) { - utils.logError('OZONE: getPlayerSizeFromObject FAILED: no playerSize in video object or ext', objVideo); + spec.logError('getPlayerSizeFromObject FAILED: no playerSize in video object or ext', objVideo); return null; } if (typeof playerSize !== 'object') { - utils.logError('OZONE: getPlayerSizeFromObject FAILED: playerSize is not an object/array', objVideo); + spec.logError('getPlayerSizeFromObject FAILED: playerSize is not an object/array', objVideo); return null; } return playerSize; @@ -1099,21 +1061,23 @@ function getPlayerSizeFromObject(objVideo) { The renderer function will not assume that the renderer script is loaded - it will push() the ultimate render function call */ function newRenderer(adUnitCode, rendererOptions = {}) { + let isLoaded = window.ozoneVideo; + spec.logInfo(`newRenderer going to set loaded to ${isLoaded ? 'true' : 'false'}`); const renderer = Renderer.install({ - url: OZONE_RENDERER_URL, + url: spec.getRendererUrl(), config: rendererOptions, - loaded: false, + loaded: isLoaded, adUnitCode }); try { renderer.setRender(outstreamRender); } catch (err) { - utils.logWarn('OZONE Prebid Error calling setRender on renderer', err); + spec.logError('Prebid Error when calling setRender on renderer', JSON.parse(JSON.stringify(renderer)), err); } return renderer; } function outstreamRender(bid) { - utils.logInfo('OZONE: outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid =', bid); + spec.logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid =', JSON.parse(JSON.stringify(bid))); // push to render queue because ozoneVideo may not be loaded yet bid.renderer.push(() => { window.ozoneVideo.outstreamRender(bid); @@ -1121,4 +1085,4 @@ function outstreamRender(bid) { } registerBidder(spec); -utils.logInfo('OZONE: ozoneBidAdapter was loaded'); +utils.logInfo(`*BidAdapter ${OZONEVERSION} was loaded`); diff --git a/modules/ozoneBidAdapter.md b/modules/ozoneBidAdapter.md index bc8cb6a6102..ca18c962219 100644 --- a/modules/ozoneBidAdapter.md +++ b/modules/ozoneBidAdapter.md @@ -37,7 +37,6 @@ adUnits = [{ siteId: '4204204201', /* An ID used to identify a site within a publisher account - required */ placementId: '0420420421', /* an ID used to identify the piece of inventory - required - for appnexus test use 13144370. */ customData: [{"settings": {}, "targeting": {"key": "value", "key2": ["value1", "value2"]}}],/* optional array with 'targeting' placeholder for passing publisher specific key-values for targeting. */ - lotameData: {"Profile": {"tpid":"value","pid":"value","Audiences": {"Audience":[{"id":"value"},{"id":"value2"}]}}}, /* optional JSON placeholder for passing Lotame DMP data */ } }] }]; @@ -52,7 +51,7 @@ adUnits = [{ code: 'id-of-your-video-div', mediaTypes: { video: { - playerSize: [640, 480], + playerSize: [640, 360], mimes: ['video/mp4'], context: 'outstream', } @@ -64,7 +63,6 @@ adUnits = [{ siteId: '4204204201', /* An ID used to identify a site within a publisher account - required */ customData: [{"settings": {}, "targeting": { "key": "value", "key2": ["value1", "value2"]}}] placementId: '0440440442', /* an ID used to identify the piece of inventory - required - for unruly test use 0440440442. */ - lotameData: {"Profile": {"tpid":"value","pid":"value","Audiences": {"Audience":[{"id":"value"},{"id":"value2"}]}}}, /* optional JSON placeholder for passing Lotame DMP data */ video: { skippable: true, /* optional */ playback_method: ['auto_play_sound_off'], /* optional */ diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index c1022608b4a..10b8ce31d28 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -6,12 +6,13 @@ import {getGranularityKeyName, getGranularityObject} from '../../../modules/ozon import * as utils from '../../../src/utils.js'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const BIDDER_CODE = 'ozone'; + /* NOTE - use firefox console to deep copy the objects to use here */ -var originalPropertyBag = {'lotameWasOverridden': 0, 'pageId': null}; +var originalPropertyBag = {'pageId': null}; var validBidRequests = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -21,7 +22,7 @@ var validBidRequests = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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' } @@ -36,7 +37,7 @@ var validBidRequestsMulti = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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' }, @@ -49,11 +50,14 @@ var validBidRequestsMulti = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c0', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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' } ]; +// use 'pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'criteoId', 'criteortus' +// NOTE THAT criteortus is no longer referenced anywhere - should be removed asap +// see http://prebid.org/dev-docs/modules/userId.html var validBidRequestsWithUserIdData = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -63,10 +67,83 @@ var validBidRequestsWithUserIdData = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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': { 'uid': 'ID5-someId' }, 'criteortus': {'ozone': {'userid': 'critId123'}}, 'idl_env': 'liverampId', 'lipb': {'lipbid': 'lipbidId123'}, 'parrableId': {eid: 'parrableid123'}} + userId: { + 'pubcid': '12345678', + 'tdid': '1111tdid', + 'id5id': 'ID5-someId', + 'criteortus': {'ozone': {'userid': 'critId123'}}, + 'criteoId': '1111criteoId', + 'idl_env': 'liverampId', + 'lipb': {'lipbid': 'lipbidId123'}, + 'parrableId': {'eid': '01.5678.parrableid'} + }, + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '12345678', + 'atype': 1 + } + ] + }, + { + 'source': 'adserver.org', + 'uids': [{ + 'id': '1111tdid', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }, + { + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'ID5-someId', + 'atype': 1, + }] + }, + { + 'source': 'criteortus', + 'uids': [{ + 'id': {'ozone': {'userid': 'critId123'}}, + 'atype': 1, + }] + }, + { + 'source': 'criteoId', + 'uids': [{ + 'id': '1111criteoId', + 'atype': 1, + }] + }, + { + 'source': 'idl_env', + 'uids': [{ + 'id': 'liverampId', + 'atype': 1, + }] + }, + { + 'source': 'lipb', + 'uids': [{ + 'id': {'lipbid': 'lipbidId123'}, + 'atype': 1, + }] + }, + { + 'source': 'parrableId', + 'uids': [{ + 'id': {'eid': '01.5678.parrableid'}, + 'atype': 1, + }] + } + ] + } ]; var validBidRequestsMinimal = [ @@ -91,7 +168,7 @@ var validBidRequestsNoSizes = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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 } } ] }, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; @@ -105,7 +182,7 @@ var validBidRequestsWithBannerMediaType = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - 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 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], 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 } } ] }, mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } @@ -119,7 +196,7 @@ var validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - 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, video: {skippable: true, playback_method: ['auto_play_sound_off'], targetDiv: 'some-different-div-id-to-my-adunitcode'} } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, video: {skippable: true, playback_method: ['auto_play_sound_off'], targetDiv: 'some-different-div-id-to-my-adunitcode'} } ] }, mediaTypes: {video: {mimes: ['video/mp4'], 'context': 'outstream', 'sizes': [640, 480], playerSize: [640, 480]}, native: {info: 'dummy data'}}, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } @@ -161,43 +238,21 @@ var validBidRequests1OutstreamVideo2020 = [ } } ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } + 'userId': { + 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' + }, + 'userIdAsEids': [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', + 'atype': 1 + } + ] } - } - }, - 'userId': { - 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' + ] }, - 'userIdAsEids': [ - { - 'source': 'pubcid.org', - 'uids': [ - { - 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', - 'atype': 1 - } - ] - } - ], 'mediaTypes': { 'video': { 'playerSize': [ @@ -272,32 +327,10 @@ var validBidderRequest1OutstreamVideo2020 = { 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'userId': { - 'id5id': { uid: 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA' }, + 'id5id': 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA', 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' }, 'userIdAsEids': [ @@ -370,7 +403,7 @@ var validBidderRequest = { bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - 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: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' }], @@ -399,7 +432,7 @@ var bidderRequestWithFullGdpr = { bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - 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: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' }], @@ -491,17 +524,6 @@ var bidderRequestWithPartialGdpr = { 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', @@ -982,29 +1004,7 @@ var multiRequest1 = [ 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1069,29 +1069,7 @@ var multiRequest1 = [ 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1165,29 +1143,7 @@ var multiBidderRequest1 = { 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1252,29 +1208,7 @@ var multiBidderRequest1 = { 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1588,8 +1522,7 @@ describe('ozone Adapter', function () { placementId: '1310000099', publisherId: '9876abcd12-3', siteId: '1234567890', - 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'}]}}}, + customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}] }, siteId: 1234567890 } @@ -1883,26 +1816,11 @@ describe('ozone Adapter', function () { it('should not validate customParams - this is a renamed key', function () { expect(spec.isBidRequestValid(xBadCustomParams)).to.equal(false); }); - - var xBadLotame = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'lotameData': 'this should be an object', - siteId: '1234567890' - } - }; - it('should not validate lotameData being sent', function () { - expect(spec.isBidRequestValid(xBadLotame)).to.equal(false); - }); - var xBadVideoContext2 = { bidder: BIDDER_CODE, params: { 'placementId': '1234567890', 'publisherId': '9876abcd12-3', - 'lotameData': {}, siteId: '1234567890' }, mediaTypes: { @@ -1937,35 +1855,6 @@ describe('ozone Adapter', function () { instreamVid.mediaTypes.video.context = 'instream'; expect(spec.isBidRequestValid(instreamVid)).to.equal(true); }); - // validate lotame override parameters - it('should validate lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': 'tpid123'}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(true); - }); - it('should validate missing lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123'}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(false); - }); - it('should validate invalid lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123 "this ain\\t right!" eee'}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(false); - }); - it('should validate no lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(true); - }); }); describe('buildRequests', function () { @@ -1989,19 +1878,27 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request.data).to.be.a('string'); var data = JSON.parse(request.data); - expect(data.ext.ozone.lotameData).to.be.an('object'); + expect(data.imp[0].ext.ozone.customData).to.be.an('array'); + expect(request).not.to.have.key('lotameData'); + expect(request).not.to.have.key('customData'); + }); + + it('adds all parameters inside the ext object only - lightning', function () { + let localBidReq = JSON.parse(JSON.stringify(validBidRequests)); + const request = spec.buildRequests(localBidReq, validBidderRequest.bidderRequest); + expect(request.data).to.be.a('string'); + var data = JSON.parse(request.data); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(request).not.to.have.key('lotameData'); expect(request).not.to.have.key('customData'); }); it('ignores ozoneData in & after version 2.1.1', function () { - let validBidRequestsWithOzoneData = validBidRequests; + let validBidRequestsWithOzoneData = JSON.parse(JSON.stringify(validBidRequests)); validBidRequestsWithOzoneData[0].params.ozoneData = {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const request = spec.buildRequests(validBidRequestsWithOzoneData, validBidderRequest.bidderRequest); expect(request.data).to.be.a('string'); var data = JSON.parse(request.data); - expect(data.ext.ozone.lotameData).to.be.an('object'); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.ozoneData).to.be.undefined; expect(request).not.to.have.key('lotameData'); @@ -2018,7 +1915,7 @@ describe('ozone Adapter', function () { expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); - it('handles no ozone, lotame or custom data', function () { + it('handles no ozone or custom data', function () { const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); @@ -2080,6 +1977,7 @@ describe('ozone Adapter', function () { expect(payload.regs.ext.gdpr).to.equal(1); expect(payload.user.ext.consent).to.equal(consentString); }); + it('should set regs.ext.gdpr flag to 0 when gdprApplies is false', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; let bidderRequest = validBidderRequest.bidderRequest; @@ -2118,13 +2016,14 @@ describe('ozone Adapter', function () { bidRequests[0]['userId'] = { 'criteortus': '1111', 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': {'uid': '2222'}, + 'id5id': '2222', 'idl_env': '3333', 'lipb': {'lipbid': '4444'}, - 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, + 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', 'pubcid': '5555', 'tdid': '6666' }; + bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); let firstBid = payload.imp[0].ext.ozone; @@ -2138,62 +2037,121 @@ describe('ozone Adapter', function () { bidRequests[0]['userId'] = { 'criteortus': '1111', 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': {'uid': '2222'}, + 'id5id': '2222', 'idl_env': '3333', 'lipb': {'lipbid': '4444'}, - 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, + 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', // 'pubcid': '5555', // remove pubcid from here to emulate the OLD module & cause the failover code to kick in 'tdid': '6666' }; + bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; const request = spec.buildRequests(bidRequests, validBidderRequest.bidderRequest); const payload = JSON.parse(request.data); expect(payload.ext.ozone.pubcid).to.equal(bidRequests[0]['crumbs']['pubcid']); delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests }); - it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019)', function() { + it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019) Updated Aug 2020', function() { const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest.bidderRequest); + /* + 'pubcid': '12345678', + 'tdid': '1111tdid', + 'id5id': 'ID5-someId', + 'criteortus': {'ozone': {'userid': 'critId123'}}, + 'criteoId': '1111criteoId', + 'idl_env': 'liverampId', + 'lipb': {'lipbid': 'lipbidId123'}, + 'parrableId': {'eid': '01.5678.parrableid'} + */ + const payload = JSON.parse(request.data); expect(payload.user).to.exist; expect(payload.user.ext).to.exist; expect(payload.user.ext.eids).to.exist; - expect(payload.user.ext.eids[0]['source']).to.equal('pubcid'); + expect(payload.user.ext.eids[0]['source']).to.equal('pubcid.org'); expect(payload.user.ext.eids[0]['uids'][0]['id']).to.equal('12345678'); - expect(payload.user.ext.eids[1]['source']).to.equal('pubcommon'); - expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('12345678'); + expect(payload.user.ext.eids[1]['source']).to.equal('adserver.org'); + expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('1111tdid'); expect(payload.user.ext.eids[2]['source']).to.equal('id5-sync.com'); expect(payload.user.ext.eids[2]['uids'][0]['id']).to.equal('ID5-someId'); - expect(payload.user.ext.eids[3]['source']).to.equal('criteortus'); - expect(payload.user.ext.eids[3]['uids'][0]['id']).to.equal('critId123'); - expect(payload.user.ext.eids[4]['source']).to.equal('liveramp.com'); - expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('liverampId'); - expect(payload.user.ext.eids[5]['source']).to.equal('liveintent.com'); - expect(payload.user.ext.eids[5]['uids'][0]['id']).to.equal('lipbidId123'); - expect(payload.user.ext.eids[6]['source']).to.equal('parrable.com'); - expect(payload.user.ext.eids[6]['uids'][0]['id']).to.equal('parrableid123'); + expect(payload.user.ext.eids[3]['source']).to.equal('criteortus'); // this is deprecated + expect(payload.user.ext.eids[3]['uids'][0]['id']['ozone']['userid']).to.equal('critId123'); + expect(payload.user.ext.eids[4]['source']).to.equal('criteoId'); + expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('1111criteoId'); + expect(payload.user.ext.eids[5]['source']).to.equal('idl_env'); + expect(payload.user.ext.eids[5]['uids'][0]['id']).to.equal('liverampId'); + expect(payload.user.ext.eids[6]['source']).to.equal('lipb'); + expect(payload.user.ext.eids[6]['uids'][0]['id']['lipbid']).to.equal('lipbidId123'); + expect(payload.user.ext.eids[7]['source']).to.equal('parrableId'); + expect(payload.user.ext.eids[7]['uids'][0]['id']['eid']).to.equal('01.5678.parrableid'); + }); + + it('replaces the auction url for a config override', function () { + spec.propertyBag.whitelabel = null; + let fakeOrigin = 'http://sometestendpoint'; + config.setConfig({'ozone': {'endpointOverride': {'origin': fakeOrigin}}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + expect(request.url).to.equal(fakeOrigin + '/openrtb2/auction'); + expect(request.method).to.equal('POST'); + config.setConfig({'ozone': {'kvpPrefix': null, 'endpointOverride': null}}); + spec.propertyBag.whitelabel = null; }); + it('replaces the renderer url for a config override', function () { + spec.propertyBag.whitelabel = null; + let fakeUrl = 'http://renderer.com'; + config.setConfig({'ozone': {'endpointOverride': {'rendererUrl': fakeUrl}}}); + const request = spec.buildRequests(validBidRequests1OutstreamVideo2020, validBidderRequest1OutstreamVideo2020.bidderRequest); + const result = spec.interpretResponse(getCleanValidVideoResponse(), validBidderRequest1OutstreamVideo2020); + const bid = result[0]; + expect(bid.renderer).to.be.an.instanceOf(Renderer); + expect(bid.renderer.url).to.equal(fakeUrl); + config.setConfig({'ozone': {'kvpPrefix': null, 'endpointOverride': null}}); + spec.propertyBag.whitelabel = null; + }); + + it('replaces the kvp prefix ', function () { + spec.propertyBag.whitelabel = null; + config.setConfig({'ozone': {'kvpPrefix': 'test'}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.ozone).to.haveOwnProperty('test_rw'); + config.setConfig({'ozone': {'kvpPrefix': null}}); + spec.propertyBag.whitelabel = null; + }); + + it('handles an alias ', function () { + spec.propertyBag.whitelabel = null; + config.setConfig({'lmc': {'kvpPrefix': 'test'}}); + let br = JSON.parse(JSON.stringify(validBidRequests)); + br[0]['bidder'] = 'lmc'; + const request = spec.buildRequests(br, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.lmc).to.haveOwnProperty('test_rw'); + config.setConfig({'lmc': {'kvpPrefix': null}}); // I cant remove the key so set the value to null + spec.propertyBag.whitelabel = null; + }); + var specMock = utils.deepClone(spec); it('should use oztestmode GET value if set', function() { // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: - spec.getGetParametersAsObject = function() { + specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const request = specMock.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const data = JSON.parse(request.data); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); }); it('should use oztestmode GET value if set, even if there is no customdata in config', function() { // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: - spec.getGetParametersAsObject = function() { + specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; - const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); + const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); const data = JSON.parse(request.data); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); }); - var specMock = utils.deepClone(spec); it('should use a valid ozstoredrequest GET value if set to override the placementId values, and set oz_rw if we find it', function() { // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: specMock.getGetParametersAsObject = function() { @@ -2215,54 +2173,6 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1310000099'); }); - it('should pick up the value of valid lotame override parameters when there is a lotame object', function () { - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eee'}; - }; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); - expect(payload.ext.ozone.oz_lot_rw).to.equal(1); - }); - it('should pick up the value of valid lotame override parameters when there is an empty lotame object', function () { - let nolotameBidReq = JSON.parse(JSON.stringify(validBidRequests)); - nolotameBidReq[0].params.lotameData = {}; - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eeetpid'}; - }; - const request = spec.buildRequests(nolotameBidReq, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); - expect(payload.ext.ozone.lotameData.Profile.tpid).to.equal('123eeetpid'); - expect(payload.ext.ozone.lotameData.Profile.pid).to.equal('pid123'); - expect(payload.ext.ozone.oz_lot_rw).to.equal(1); - }); - it('should pick up the value of valid lotame override parameters when there is NO "lotame" key at all', function () { - let nolotameBidReq = JSON.parse(JSON.stringify(validBidRequests)); - delete (nolotameBidReq[0].params['lotameData']); - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eeetpid'}; - }; - const request = spec.buildRequests(nolotameBidReq, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); - expect(payload.ext.ozone.lotameData.Profile.tpid).to.equal('123eeetpid'); - expect(payload.ext.ozone.lotameData.Profile.pid).to.equal('pid123'); - expect(payload.ext.ozone.oz_lot_rw).to.equal(1); - spec.propertyBag = originalPropertyBag; // tidy up - }); - // NOTE - only one negative test case; - // you can't send invalid lotame params to buildRequests because 'validate' will have rejected them - it('should not use lotame override parameters if they dont exist', function () { - expect(spec.propertyBag.lotameWasOverridden).to.equal(0); - spec.getGetParametersAsObject = function() { - return {}; // no lotame override params - }; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.oz_lot_rw).to.equal(0); - }); - it('should pick up the config value of coppa & set it in the request', function () { config.setConfig({'coppa': true}); const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); @@ -2609,6 +2519,14 @@ describe('ozone Adapter', function () { const result = playerSizeIsNestedArray(obj); expect(result).to.be.null; }); + it('should add oz_appnexus_dealid into ads request if dealid exists in the auction response', function () { + const request = spec.buildRequests(validBidRequestsMulti, validBidderRequest.bidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2Bids)); + validres.body.seatbid[0].bid[0].dealid = '1234'; + const result = spec.interpretResponse(validres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_dealid')).to.equal('1234'); + expect(utils.deepAccess(result[1].adserverTargeting, 'oz_appnexus_dealid', '')).to.equal(''); + }); }); describe('default size', function () { @@ -2660,44 +2578,6 @@ describe('ozone Adapter', function () { config.resetConfig(); }); }); - describe('makeLotameObjectFromOverride', function() { - it('should update an object with valid lotame data', function () { - let objLotameOverride = {'oz_lotametpid': '1234', 'oz_lotameid': '12345', 'oz_lotamepid': '123456'}; - let result = spec.makeLotameObjectFromOverride( - objLotameOverride, - {'Profile': {'pid': 'originalpid', 'tpid': 'originaltpid', 'Audiences': {'Audience': [{'id': 'aud1'}]}}} - ); - expect(result.Profile.Audiences.Audience).to.be.an('array'); - expect(result.Profile.Audiences.Audience[0]).to.be.an('object'); - expect(result.Profile.Audiences.Audience[0]).to.deep.include({'id': '12345', 'abbr': '12345'}); - }); - it('should return the original object if it seems weird', function () { - let objLotameOverride = {'oz_lotametpid': '1234', 'oz_lotameid': '12345', 'oz_lotamepid': '123456'}; - let objLotameOriginal = {'Profile': {'pid': 'originalpid', 'tpid': 'originaltpid', 'somethingstrange': [{'id': 'aud1'}]}}; - let result = spec.makeLotameObjectFromOverride( - objLotameOverride, - objLotameOriginal - ); - expect(result).to.equal(objLotameOriginal); - }); - }); - describe('lotameDataIsValid', function() { - it('should allow a valid minimum lotame object', function() { - let obj = {'Profile': {'pid': '', 'tpid': '', 'Audiences': {'Audience': []}}}; - let result = spec.isLotameDataValid(obj); - expect(result).to.be.true; - }); - it('should allow a valid lotame object', function() { - let obj = {'Profile': {'pid': '12345', 'tpid': '45678', 'Audiences': {'Audience': [{'id': '1234', 'abbr': '567'}, {'id': '9999', 'abbr': '1111'}]}}}; - let result = spec.isLotameDataValid(obj); - expect(result).to.be.true; - }); - it('should disallow a lotame object without an Audience.id', function() { - let obj = {'Profile': {'tpid': '', 'pid': '', 'Audiences': {'Audience': [{'abbr': 'marktest'}]}}}; - let result = spec.isLotameDataValid(obj); - expect(result).to.be.false; - }); - }); describe('getPageId', function() { it('should return the same Page ID for multiple calls', function () { let result = spec.getPageId(); @@ -2720,24 +2600,6 @@ describe('ozone Adapter', function () { expect(result).to.equal('outstream'); }); }); - describe('getLotameOverrideParams', function() { - it('should get 3 valid lotame params that exist in GET params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': 'tpid123'}; - }; - let result = spec.getLotameOverrideParams(); - expect(Object.keys(result).length).to.equal(3); - }); - it('should get only 1 valid lotame param that exists in GET params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'xoz_lotamepid': 'pid123', 'xoz_lotametpid': 'tpid123'}; - }; - let result = spec.getLotameOverrideParams(); - expect(Object.keys(result).length).to.equal(1); - }); - }); describe('unpackVideoConfigIntoIABformat', function() { it('should correctly unpack a usual video config', function () { let mediaTypes = { From 06bcf9fb9e1208bd7409f43fb5450ff5fbcff0ca Mon Sep 17 00:00:00 2001 From: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> Date: Wed, 24 Feb 2021 06:46:12 -0500 Subject: [PATCH 15/96] Triplelift Bid Adapter: add PubCommon ID support (#6352) --- modules/tripleliftBidAdapter.js | 27 +++++++++++-------- .../spec/modules/tripleliftBidAdapter_spec.js | 21 ++++++++++++--- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 029cdd68331..b681b0980ea 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -129,9 +129,10 @@ function _buildPostBody(bidRequests) { }); let eids = [ - ...getUnifiedIdEids(bidRequests), - ...getIdentityLinkEids(bidRequests), - ...getCriteoEids(bidRequests) + ...getUnifiedIdEids([bidRequests[0]]), + ...getIdentityLinkEids([bidRequests[0]]), + ...getCriteoEids([bidRequests[0]]), + ...getPubCommonEids([bidRequests[0]]) ]; if (eids.length > 0) { @@ -239,20 +240,24 @@ function _getExt(schain, fpd) { return ext; } -function getUnifiedIdEids(bidRequests) { - return getEids(bidRequests, 'tdid', 'adserver.org', 'TDID'); +function getUnifiedIdEids(bidRequest) { + return getEids(bidRequest, 'tdid', 'adserver.org', 'TDID'); } -function getIdentityLinkEids(bidRequests) { - return getEids(bidRequests, 'idl_env', 'liveramp.com', 'idl'); +function getIdentityLinkEids(bidRequest) { + return getEids(bidRequest, 'idl_env', 'liveramp.com', 'idl'); } -function getCriteoEids(bidRequests) { - return getEids(bidRequests, 'criteoId', 'criteo.com', 'criteoId'); +function getCriteoEids(bidRequest) { + return getEids(bidRequest, 'criteoId', 'criteo.com', 'criteoId'); } -function getEids(bidRequests, type, source, rtiPartner) { - return bidRequests +function getPubCommonEids(bidRequest) { + return getEids(bidRequest, 'pubcid', 'pubcid.org', 'pubcid'); +} + +function getEids(bidRequest, type, source, rtiPartner) { + return bidRequest .map(getUserId(type)) // bids -> userIds of a certain type .filter((x) => !!x) // filter out null userIds .map(formatEid(source, rtiPartner)); // userIds -> eid objects diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index c5d00f4db2b..eb410c2525d 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -506,15 +506,16 @@ describe('triplelift adapter', function () { }); }); - it('should add user ids from multiple bid requests', function () { + it('should consolidate user ids from multiple bid requests', function () { const tdidId = '6bca7f6b-a98a-46c0-be05-6020f7604598'; const idlEnvId = 'XY6104gr0njcH9UDIR7ysFFJcm2XNpqeJTYslleJ_cMlsFOfZI'; const criteoId = '53e30ea700424f7bbdd793b02abc5d7'; + const pubcid = '3261d8ad-435d-481d-abd1-9f1a9ec99f0e'; const bidRequestsMultiple = [ - { ...bidRequests[0], userId: { tdid: tdidId } }, - { ...bidRequests[0], userId: { idl_env: idlEnvId } }, - { ...bidRequests[0], userId: { criteoId: criteoId } } + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } } ]; const request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); @@ -549,10 +550,22 @@ describe('triplelift adapter', function () { ext: { rtiPartner: 'criteoId' } } ] + }, + { + source: 'pubcid.org', + uids: [ + { + id: '3261d8ad-435d-481d-abd1-9f1a9ec99f0e', + ext: { rtiPartner: 'pubcid' } + } + ] } ] } }); + + expect(payload.user.ext.eids).to.be.an('array'); + expect(payload.user.ext.eids).to.have.lengthOf(4); }); it('should return a query string for TL call', function () { From 18d07ca9afedc511267528f6bf48a4f52ba1e7f4 Mon Sep 17 00:00:00 2001 From: Mike Chowla Date: Wed, 24 Feb 2021 13:49:05 -0800 Subject: [PATCH 16/96] Prebid 4.28.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dc0a46844ca..27b5dff8888 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.28.0-pre", + "version": "4.28.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 4279d19fd36116c34543bdf1ce40589f7c068af9 Mon Sep 17 00:00:00 2001 From: Mike Chowla Date: Wed, 24 Feb 2021 14:10:51 -0800 Subject: [PATCH 17/96] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 27b5dff8888..e992a108c5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.28.0", + "version": "4.29.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a8e1d371b055db993c8cb215b8c52de4b1900a01 Mon Sep 17 00:00:00 2001 From: KazuakiM Date: Thu, 25 Feb 2021 16:34:42 +0900 Subject: [PATCH 18/96] GMOSSP Bid Adapter : add refererInfo from bidderRequest (#6319) --- modules/gmosspBidAdapter.js | 32 ++++++++++++++++++++-- test/spec/modules/gmosspBidAdapter_spec.js | 24 ++++++++++++---- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index d9dc8f7641a..5eac4069b21 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -29,7 +29,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const bidRequests = []; - const url = bidderRequest.refererInfo.referer; + const urlInfo = getUrlInfo(bidderRequest.refererInfo); const cur = getCurrencyType(); const dnt = utils.getDNT() ? '1' : '0'; @@ -46,7 +46,8 @@ export const spec = { queryString = utils.tryAppendQueryString(queryString, 'bid', bid); queryString = utils.tryAppendQueryString(queryString, 'ver', ver); queryString = utils.tryAppendQueryString(queryString, 'sid', sid); - queryString = utils.tryAppendQueryString(queryString, 'url', url); + queryString = utils.tryAppendQueryString(queryString, 'url', urlInfo.url); + queryString = utils.tryAppendQueryString(queryString, 'ref', urlInfo.ref); queryString = utils.tryAppendQueryString(queryString, 'cur', cur); queryString = utils.tryAppendQueryString(queryString, 'dnt', dnt); @@ -131,4 +132,31 @@ function getCurrencyType() { return 'JPY'; } +function getUrlInfo(refererInfo) { + return { + url: getUrl(refererInfo), + ref: getReferrer(), + }; +} + +function getUrl(refererInfo) { + if (refererInfo && refererInfo.referer) { + return refererInfo.referer; + } + + try { + return window.top.location.href; + } catch (e) { + return window.location.href; + } +} + +function getReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return document.referrer; + } +} + registerBidder(spec); diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js index 5de3db623c5..52bb8460caa 100644 --- a/test/spec/modules/gmosspBidAdapter_spec.js +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -54,18 +54,30 @@ describe('GmosspAdapter', function () { } ]; - const bidderRequest = { - refererInfo: { - referer: 'https://hoge.com' - } - }; - it('sends bid request to ENDPOINT via GET', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://hoge.com' + } + }; + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('GET'); expect(requests[0].data).to.equal('tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&url=https%3A%2F%2Fhoge.com&cur=JPY&dnt=0&'); }); + + it('should use fallback if refererInfo.referer in bid request is empty', function () { + const bidderRequest = { + refererInfo: { + referer: '' + } + }; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + const result = 'tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&url=' + encodeURIComponent(window.top.location.href) + '&cur=JPY&dnt=0&'; + expect(requests[0].data).to.equal(result); + }); }); describe('interpretResponse', function () { From f2ca498edc055a080aab3b632e8e157e9a942b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Fern=C3=A1ndez?= Date: Thu, 25 Feb 2021 09:28:44 +0100 Subject: [PATCH 19/96] Axonix Bid Adapter: add new bid adapter (#6341) * Add Axonix bid adapter * Fixed tests --- modules/axonixBidAdapter.js | 178 ++++++++++ modules/axonixBidAdapter.md | 140 ++++++++ test/spec/modules/axonixBidAdapter_spec.js | 372 +++++++++++++++++++++ 3 files changed, 690 insertions(+) create mode 100644 modules/axonixBidAdapter.js create mode 100644 modules/axonixBidAdapter.md create mode 100644 test/spec/modules/axonixBidAdapter_spec.js diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js new file mode 100644 index 00000000000..161ea8c6715 --- /dev/null +++ b/modules/axonixBidAdapter.js @@ -0,0 +1,178 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; + +const BIDDER_CODE = 'axonix'; +const BIDDER_VERSION = '1.0.0'; + +const CURRENCY = 'USD'; +const DEFAULT_REGION = 'us-east-1'; + +function getBidFloor(bidRequest) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: CURRENCY, + mediaType: '*', + size: '*' + }); + } + + return floorInfo.floor || 0; +} + +function getPageUrl(bidRequest, bidderRequest) { + let pageUrl = config.getConfig('pageUrl'); + + if (bidRequest.params.referrer) { + pageUrl = bidRequest.params.referrer; + } else if (!pageUrl) { + pageUrl = bidderRequest.refererInfo.referer; + } + + return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; +} + +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function getURL(params, path) { + let { supplyId, region, endpoint } = params; + let url; + + if (endpoint) { + url = endpoint; + } else if (region) { + url = `https://openrtb-${region}.axonix.com/supply/${path}/${supplyId}`; + } else { + url = `https://openrtb-${DEFAULT_REGION}.axonix.com/supply/${path}/${supplyId}` + } + + return url; +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function(bid) { + // video bid request validation + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (!bid.mediaTypes[VIDEO].hasOwnProperty('mimes') || + !utils.isArray(bid.mediaTypes[VIDEO].mimes) || + bid.mediaTypes[VIDEO].mimes.length === 0) { + utils.logError('mimes are mandatory for video bid request. Ad Unit: ', JSON.stringify(bid)); + + return false; + } + } + + return !!(bid.params && bid.params.supplyId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + // device.connectiontype + let connection = navigator.connection || navigator.webkitConnection; + let connectiontype = 'unknown'; + + if (connection && connection.effectiveType) { + connectiontype = connection.effectiveType; + } + + const requests = validBidRequests.map(validBidRequest => { + // app/site + let app; + let site; + + if (typeof config.getConfig('app') === 'object') { + app = config.getConfig('app'); + } else { + site = { + page: getPageUrl(validBidRequest, bidderRequest) + } + } + + const data = { + app, + site, + validBidRequest, + connectiontype, + devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2, + bidfloor: getBidFloor(validBidRequest), + dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, + language: navigator.language, + prebidVersion: '$prebid.version$', + screenHeight: screen.height, + screenWidth: screen.width, + tmax: config.getConfig('bidderTimeout'), + ua: navigator.userAgent, + }; + + return { + method: 'POST', + url: getURL(validBidRequest.params, 'prebid'), + options: { + withCredentials: false, + contentType: 'application/json' + }, + data + }; + }); + + return requests; + }, + + interpretResponse: function(serverResponse) { + if (!utils.isArray(serverResponse)) { + return []; + } + + const responses = []; + + for (const response of serverResponse) { + if (response.requestId) { + responses.push(Object.assign(response, { + ttl: config.getConfig('_bidderTimeout') + })); + } + } + + return responses; + }, + + onTimeout: function(timeoutData) { + const params = utils.deepAccess(timeoutData, '0.params.0'); + + if (!utils.isEmpty(params)) { + ajax(getURL(params, 'prebid/timeout'), null, timeoutData[0], { + method: 'POST', + options: { + withCredentials: false, + contentType: 'application/json' + } + }); + } + }, + + onBidWon: function(bids) { + for (const bid of bids) { + const { nurl } = bid || {}; + + if (bid.nurl) { + utils.replaceAuctionPrice(nurl, bid.cpm) + utils.triggerPixel(nurl); + }; + } + } +} + +registerBidder(spec); diff --git a/modules/axonixBidAdapter.md b/modules/axonixBidAdapter.md new file mode 100644 index 00000000000..1ff59f17828 --- /dev/null +++ b/modules/axonixBidAdapter.md @@ -0,0 +1,140 @@ +# Overview + +``` +Module Name : Axonix Bidder Adapter +Module Type : Bidder Adapter +Maintainer : support-prebid@axonix.com +``` + +# Description + +Module that connects to Axonix's exchange for bids. + +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :---------------------------------------------- | :------------------------------------- | +| `supplyId` | required | Supply UUID | `"2c426f78-bb18-4a16-abf4-62c6cd0ee8de"` | +| `region` | optional | Cloud region | `"us-east-1"` | +| `endpoint` | optional | Supply custom endpoint | `"https://open-rtb.axonix.com/custom"` | +| `instl` | optional | Set to 1 if using interstitial (default: 0) | `1` | + +# Test Parameters + +## Banner + +```javascript +var bannerAdUnit = { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[120, 600], [300, 250], [320, 50], [468, 60], [728, 90]] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}; +``` + +## Video + +```javascript +var videoAdUnit = { + code: 'test-video', + mediaTypes: { + video: { + protocols: [1, 2, 3, 4, 5, 6, 7, 8] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}; +``` + +## Native + +```javascript +var nativeAdUnit = { + code: 'test-native', + mediaTypes: { + native: { + + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}; +``` + +## Multiformat + +```javascript +var adUnits = [ +{ + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[120, 600], [300, 250], [320, 50], [468, 60], [728, 90]] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}, +{ + code: 'test-video', + mediaTypes: { + video: { + protocols: [1, 2, 3, 4, 5, 6, 7, 8] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}, +{ + code: 'test-native', + mediaTypes: { + native: { + + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +} +]; +``` diff --git a/test/spec/modules/axonixBidAdapter_spec.js b/test/spec/modules/axonixBidAdapter_spec.js new file mode 100644 index 00000000000..632e080f83d --- /dev/null +++ b/test/spec/modules/axonixBidAdapter_spec.js @@ -0,0 +1,372 @@ +import { expect } from 'chai'; +import { spec } from 'modules/axonixBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +describe('AxonixBidAdapter', function () { + const adapter = newBidder(spec); + + const SUPPLY_ID_1 = '91fd110a-5685-11eb-8db6-a7e0eeefbbc7'; + const SUPPLY_ID_2 = '22de2092-568b-11eb-bae3-cfa975dc72aa'; + const REGION_1 = 'us-east-1'; + const REGION_2 = 'eu-west-1'; + + const BANNER_REQUEST = { + adUnitCode: 'ad_code', + bidId: 'abcd1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_1, + region: REGION_1 + }, + requestId: 'q4owht8ofqi3ulwsd', + transactionId: 'fvpq3oireansdwo' + }; + + const VIDEO_REQUEST = { + adUnitCode: 'ad_code', + bidId: 'abcd1234', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'], + playerSize: [400, 300], + renderer: { + url: 'https://url.com', + backupOnly: true, + render: () => true + }, + } + }, + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_1, + region: REGION_1 + }, + requestId: 'q4owht8ofqi3ulwsd', + transactionId: 'fvpq3oireansdwo' + }; + + const BIDDER_REQUEST = { + bidderCode: 'axonix', + auctionId: '18fd8b8b0bd757', + bidderRequestId: '418b37f85e772c', + timeout: 3000, + gdprConsent: { + consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==', + gdprApplies: true + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + } + }; + + const BANNER_RESPONSE = { + requestId: 'f08b3a8dcff747eabada295dcf94eee0', + cpm: 6, + currency: 'USD', + width: 300, + height: 250, + ad: '', + creativeId: 'abc', + netRevenue: false, + meta: { + networkId: 'nid', + advertiserDomains: [ + 'https://the.url' + ], + secondaryCatIds: [ + 'IAB1' + ], + mediaType: 'banner' + }, + nurl: 'https://win.url' + }; + + const VIDEO_RESPONSE = { + requestId: 'f08b3a8dcff747eabada295dcf94eee0', + cpm: 6, + currency: 'USD', + width: 300, + height: 250, + ad: '', + creativeId: 'abc', + netRevenue: false, + meta: { + networkId: 'nid', + advertiserDomains: [ + 'https://the.url' + ], + secondaryCatIds: [ + 'IAB1' + ], + mediaType: 'video' + }, + nurl: 'https://win.url' + }; + + 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 validBids = [ + { + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_1, + region: REGION_1 + }, + }, + { + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_2, + region: REGION_2 + }, + future_parameter: { + future: 'ididid' + } + }, + ]; + + let invalidBids = [ + { + bidder: 'axonix', + params: {}, + }, + { + bidder: 'axonix', + }, + ]; + + it('should accept valid bids', function () { + for (let bid of validBids) { + expect(spec.isBidRequestValid(bid)).to.equal(true); + } + }); + + it('should reject invalid bids', function () { + for (let bid of invalidBids) { + expect(spec.isBidRequestValid(bid)).to.equal(false); + } + }); + }); + + describe('buildRequests: can handle banner ad requests', function () { + it('creates ServerRequests with the correct data', function () { + const [request] = spec.buildRequests([BANNER_REQUEST], BIDDER_REQUEST); + + expect(request).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(request).to.have.property('method', 'POST'); + expect(request).to.have.property('data'); + + const { data } = request; + expect(data.app).to.be.undefined; + + expect(data).to.have.property('site'); + expect(data.site).to.have.property('page', 'https://www.prebid.org'); + + expect(data).to.have.property('validBidRequest', BANNER_REQUEST); + expect(data).to.have.property('connectiontype').to.exist; + expect(data).to.have.property('devicetype', 2); + expect(data).to.have.property('bidfloor', 0); + expect(data).to.have.property('dnt', 0); + expect(data).to.have.property('language').to.be.a('string'); + expect(data).to.have.property('prebidVersion').to.be.a('string'); + expect(data).to.have.property('screenHeight').to.be.a('number'); + expect(data).to.have.property('screenWidth').to.be.a('number'); + expect(data).to.have.property('tmax').to.be.a('number'); + expect(data).to.have.property('ua').to.be.a('string'); + }); + + it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { + const bannerRequests = [utils.deepClone(BANNER_REQUEST), utils.deepClone(BANNER_REQUEST)]; + bannerRequests[0].params.endpoint = 'https://the.url'; + bannerRequests[1].params.endpoint = 'https://the.other.url'; + + const requests = spec.buildRequests(bannerRequests, BIDDER_REQUEST); + + requests.forEach((request, index) => { + expect(request).to.have.property('url', bannerRequests[index].params.endpoint); + }); + }); + + it('creates ServerRequests pointing to default endpoint if missing', function () { + const bannerRequests = [utils.deepClone(BANNER_REQUEST), utils.deepClone(BANNER_REQUEST)]; + bannerRequests[1].params.supplyId = SUPPLY_ID_2; + bannerRequests[1].params.region = REGION_2; + + const requests = spec.buildRequests(bannerRequests, BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(requests[1]).to.have.property('url', `https://openrtb-${REGION_2}.axonix.com/supply/prebid/${SUPPLY_ID_2}`); + }); + + it('creates ServerRequests pointing to default region if missing', function () { + const bannerRequest = utils.deepClone(BANNER_REQUEST); + delete bannerRequest.params.region; + + const requests = spec.buildRequests([bannerRequest], BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + }); + }); + + describe('buildRequests: can handle video ad requests', function () { + it('creates ServerRequests with the correct data', function () { + const [request] = spec.buildRequests([VIDEO_REQUEST], BIDDER_REQUEST); + + expect(request).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(request).to.have.property('method', 'POST'); + expect(request).to.have.property('data'); + + const { data } = request; + expect(data.app).to.be.undefined; + + expect(data).to.have.property('site'); + expect(data.site).to.have.property('page', 'https://www.prebid.org'); + + expect(data).to.have.property('validBidRequest', VIDEO_REQUEST); + expect(data).to.have.property('connectiontype').to.exist; + expect(data).to.have.property('devicetype', 2); + expect(data).to.have.property('bidfloor', 0); + expect(data).to.have.property('dnt', 0); + expect(data).to.have.property('language').to.be.a('string'); + expect(data).to.have.property('prebidVersion').to.be.a('string'); + expect(data).to.have.property('screenHeight').to.be.a('number'); + expect(data).to.have.property('screenWidth').to.be.a('number'); + expect(data).to.have.property('tmax').to.be.a('number'); + expect(data).to.have.property('ua').to.be.a('string'); + }); + + it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { + const videoRequests = [utils.deepClone(VIDEO_REQUEST), utils.deepClone(VIDEO_REQUEST)]; + videoRequests[0].params.endpoint = 'https://the.url'; + videoRequests[1].params.endpoint = 'https://the.other.url'; + + const requests = spec.buildRequests(videoRequests, BIDDER_REQUEST); + + requests.forEach((request, index) => { + expect(request).to.have.property('url', videoRequests[index].params.endpoint); + }); + }); + + it('creates ServerRequests pointing to default endpoint if missing', function () { + const videoRequests = [utils.deepClone(VIDEO_REQUEST), utils.deepClone(VIDEO_REQUEST)]; + videoRequests[1].params.supplyId = SUPPLY_ID_2; + videoRequests[1].params.region = REGION_2; + + const requests = spec.buildRequests(videoRequests, BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(requests[1]).to.have.property('url', `https://openrtb-${REGION_2}.axonix.com/supply/prebid/${SUPPLY_ID_2}`); + }); + + it('creates ServerRequests pointing to default region if missing', function () { + const videoRequest = utils.deepClone(VIDEO_REQUEST); + delete videoRequest.params.region; + + const requests = spec.buildRequests([videoRequest], BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + }); + }); + + describe.skip('buildRequests: can handle native ad requests', function () { + it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { + // loop: + // set supply id + // set region/endpoint in ssp config + // call buildRequests, validate request (url, method, supply id) + expect.fail('Not implemented'); + }); + + it('creates ServerRequests pointing to default endpoint if missing', function () { + // no endpoint in config means default value openrtb.axonix.com + expect.fail('Not implemented'); + }); + + it('creates ServerRequests pointing to default region if missing', function () { + // no region in config means default value us-east-1 + expect.fail('Not implemented'); + }); + }); + + describe('interpretResponse', function () { + it('considers corner cases', function() { + expect(spec.interpretResponse(null)).to.be.an('array').that.is.empty; + expect(spec.interpretResponse()).to.be.an('array').that.is.empty; + }); + + it('ignores unparseable responses', function() { + expect(spec.interpretResponse('invalid')).to.be.an('array').that.is.empty; + expect(spec.interpretResponse(['invalid'])).to.be.an('array').that.is.empty; + expect(spec.interpretResponse([{ invalid: 'object' }])).to.be.an('array').that.is.empty; + }); + + it('parses banner responses', function () { + const response = spec.interpretResponse([BANNER_RESPONSE]); + + expect(response).to.be.an('array').that.is.not.empty; + expect(response[0]).to.equal(BANNER_RESPONSE); + }); + + it('parses 1 video responses', function () { + const response = spec.interpretResponse([VIDEO_RESPONSE]); + + expect(response).to.be.an('array').that.is.not.empty; + expect(response[0]).to.equal(VIDEO_RESPONSE); + }); + + it.skip('parses 1 native responses', function () { + // passing 1 valid native in a response generates an array with 1 correct prebid response + // examine mediaType:native, native element + // check nativeBidIsValid from {@link file://./../../../src/native.js} + expect.fail('Not implemented'); + }); + }); + + describe('onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('called once', function () { + spec.onBidWon(spec.interpretResponse([BANNER_RESPONSE])); + expect(utils.triggerPixel.calledOnce).to.equal(true); + }); + + it('called false', function () { + spec.onBidWon([{ cpm: '2.21' }]); + expect(utils.triggerPixel.called).to.equal(false); + }); + + it('when there is no notification expected server side, none is called', function () { + var response = spec.onBidWon([]); + expect(utils.triggerPixel.called).to.equal(false); + expect(response).to.be.an('undefined') + }); + }); + + describe('onTimeout', function () { + it('banner response', () => { + spec.onTimeout(spec.interpretResponse([BANNER_RESPONSE])); + }); + + it('video response', () => { + spec.onTimeout(spec.interpretResponse([VIDEO_RESPONSE])); + }); + }); +}); From 63d8b6b87c42cc2b9222fb037e83f90cf44dfbd5 Mon Sep 17 00:00:00 2001 From: ysfbsf Date: Thu, 25 Feb 2021 14:22:09 +0100 Subject: [PATCH 20/96] Documentation: Prebid Server and Postbid integration example ( ad server-less ) (#6348) --- .../postbid/postbid_prebidServer_example.html | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 integrationExamples/postbid/postbid_prebidServer_example.html diff --git a/integrationExamples/postbid/postbid_prebidServer_example.html b/integrationExamples/postbid/postbid_prebidServer_example.html new file mode 100644 index 00000000000..3af7b010872 --- /dev/null +++ b/integrationExamples/postbid/postbid_prebidServer_example.html @@ -0,0 +1,86 @@ + + + + + + + + + + + From bb3acb968372f0ce3b8f69ecaea3d22cdff085ef Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 25 Feb 2021 16:32:27 +0100 Subject: [PATCH 21/96] Native: add custom data assets capability (#6220) Use `mediaTypes.native.ext: {}` special object to add/allow custom data. assets. --- src/native.js | 78 +++++++++++++++++++++++++++------------- test/spec/native_spec.js | 74 ++++++++++++++++++++++++++++++++------ 2 files changed, 117 insertions(+), 35 deletions(-) diff --git a/src/native.js b/src/native.js index 2d22e16c8a5..b43e582327b 100644 --- a/src/native.js +++ b/src/native.js @@ -165,30 +165,38 @@ export function getNativeTargeting(bid, bidReq) { `nativeParams.sendTargetingKeys` ) !== false; - Object.keys(bid['native']).forEach(asset => { - if (asset !== 'adTemplate') { - const key = CONSTANTS.NATIVE_KEYS[asset]; - let value = getAssetValue(bid['native'][asset]); - - const sendPlaceholder = deepAccess( - bidReq, - `mediaTypes.native.${asset}.sendId` - ); - - if (sendPlaceholder) { - const placeholder = `${key}:${bid.adId}`; - value = placeholder; - } - - const assetSendTargetingKeys = deepAccess( - bidReq, - `nativeParams.${asset}.sendTargetingKeys`); - - const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys; - - if (key && value && sendTargeting) { - keyValues[key] = value; - } + const nativeKeys = getNativeKeys(bidReq); + + const flatBidNativeKeys = { ...bid.native, ...bid.native.ext }; + delete flatBidNativeKeys.ext; + + Object.keys(flatBidNativeKeys).forEach(asset => { + const key = nativeKeys[asset]; + let value = getAssetValue(bid.native[asset]) || getAssetValue(deepAccess(bid, `native.ext.${asset}`)); + + if (asset === 'adTemplate' || !key || !value) { + return; + } + + let sendPlaceholder = deepAccess(bidReq, `nativeParams.${asset}.sendId`); + if (typeof sendPlaceholder !== 'boolean') { + sendPlaceholder = deepAccess(bidReq, `nativeParams.ext.${asset}.sendId`); + } + + if (sendPlaceholder) { + const placeholder = `${key}:${bid.adId}`; + value = placeholder; + } + + let assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.${asset}.sendTargetingKeys`) + if (typeof assetSendTargetingKeys !== 'boolean') { + assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.ext.${asset}.sendTargetingKeys`); + } + + const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys; + + if (sendTargeting) { + keyValues[key] = value; } }); @@ -234,6 +242,13 @@ export function getAllAssetsMessage(data, adObject) { message.adTemplate = getAssetValue(adObject.native[key]); } else if (key === 'rendererUrl' && adObject.native[key]) { message.rendererUrl = getAssetValue(adObject.native[key]); + } else if (key === 'ext') { + Object.keys(adObject.native[key]).forEach(extKey => { + if (adObject.native[key][extKey]) { + const value = getAssetValue(adObject.native[key][extKey]); + message.assets.push({ key: extKey, value }); + } + }) } else if (adObject.native[key] && CONSTANTS.NATIVE_KEYS.hasOwnProperty(key)) { const value = getAssetValue(adObject.native[key]); @@ -255,3 +270,18 @@ function getAssetValue(value) { return value; } + +function getNativeKeys(bidReq) { + const extraNativeKeys = {} + + if (deepAccess(bidReq, 'nativeParams.ext')) { + Object.keys(bidReq.nativeParams.ext).forEach(extKey => { + extraNativeKeys[extKey] = `hb_native_${extKey}`; + }) + } + + return { + ...CONSTANTS.NATIVE_KEYS, + ...extraNativeKeys + } +} diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index ef9d407dd0c..7154a0fde37 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -23,7 +23,11 @@ const bid = { clickUrl: 'https://www.link.example', clickTrackers: ['https://tracker.example'], impressionTrackers: ['https://impression.example'], - javascriptTrackers: '' + javascriptTrackers: '', + ext: { + foo: 'foo-value', + baz: 'baz-value' + } } }; @@ -36,7 +40,11 @@ const bidWithUndefinedFields = { clickUrl: 'https://www.link.example', clickTrackers: ['https://tracker.example'], impressionTrackers: ['https://impression.example'], - javascriptTrackers: '' + javascriptTrackers: '', + ext: { + foo: 'foo-value', + baz: undefined + } } }; @@ -59,14 +67,21 @@ describe('native.js', function () { expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal(bid.native.body); expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal(bid.native.clickUrl); + expect(targeting.hb_native_foo).to.equal(bid.native.foo); }); it('sends placeholders for configured assets', function () { const bidRequest = { - mediaTypes: { - native: { - body: { sendId: true }, - clickUrl: { sendId: true }, + nativeParams: { + body: { sendId: true }, + clickUrl: { sendId: true }, + ext: { + foo: { + sendId: false + }, + baz: { + sendId: true + } } } }; @@ -75,15 +90,33 @@ describe('native.js', function () { expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal('hb_native_body:123'); expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal('hb_native_linkurl:123'); + expect(targeting.hb_native_foo).to.equal(bid.native.ext.foo); + expect(targeting.hb_native_baz).to.equal('hb_native_baz:123'); }); it('should only include native targeting keys with values', function () { - const targeting = getNativeTargeting(bidWithUndefinedFields); + const bidRequest = { + nativeParams: { + body: { sendId: true }, + clickUrl: { sendId: true }, + ext: { + foo: { + required: false + }, + baz: { + required: false + } + } + } + }; + + const targeting = getNativeTargeting(bidWithUndefinedFields, bidRequest); expect(Object.keys(targeting)).to.deep.equal([ CONSTANTS.NATIVE_KEYS.title, CONSTANTS.NATIVE_KEYS.sponsoredBy, - CONSTANTS.NATIVE_KEYS.clickUrl + CONSTANTS.NATIVE_KEYS.clickUrl, + 'hb_native_foo' ]); }); @@ -138,6 +171,12 @@ describe('native.js', function () { sponsoredBy: { required: false, sendTargetingKeys: false + }, + ext: { + foo: { + required: false, + sendTargetingKeys: true + } } } @@ -148,7 +187,8 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.title, CONSTANTS.NATIVE_KEYS.body, CONSTANTS.NATIVE_KEYS.image, - CONSTANTS.NATIVE_KEYS.clickUrl + CONSTANTS.NATIVE_KEYS.clickUrl, + 'hb_native_foo' ]); }); @@ -265,7 +305,7 @@ describe('native.js', function () { const message = getAllAssetsMessage(messageRequest, bid); - expect(message.assets.length).to.equal(7); + expect(message.assets.length).to.equal(9); expect(message.assets).to.deep.include({ key: 'body', value: bid.native.body @@ -294,6 +334,14 @@ describe('native.js', function () { key: 'sponsoredBy', value: bid.native.sponsoredBy }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz + }); }); it('creates native all asset message with only defined fields', function() { @@ -305,7 +353,7 @@ describe('native.js', function () { const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); - expect(message.assets.length).to.equal(3); + expect(message.assets.length).to.equal(4); expect(message.assets).to.deep.include({ key: 'clickUrl', value: bid.native.clickUrl @@ -318,6 +366,10 @@ describe('native.js', function () { key: 'sponsoredBy', value: bid.native.sponsoredBy }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo + }); }); }); From 06b4742fc41a7bc7a7c324ee76a6d870ddc23cf5 Mon Sep 17 00:00:00 2001 From: iprom-adserver <79305981+iprom-adserver@users.noreply.github.com> Date: Thu, 25 Feb 2021 19:07:31 +0100 Subject: [PATCH 22/96] iPROM adapter upload - adapter (#6334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gašper Žagar --- modules/ipromBidAdapter.js | 72 ++++++++ modules/ipromBidAdapter.md | 34 ++++ test/spec/modules/ipromBidAdapter_spec.js | 193 ++++++++++++++++++++++ 3 files changed, 299 insertions(+) create mode 100644 modules/ipromBidAdapter.js create mode 100644 modules/ipromBidAdapter.md create mode 100644 test/spec/modules/ipromBidAdapter_spec.js diff --git a/modules/ipromBidAdapter.js b/modules/ipromBidAdapter.js new file mode 100644 index 00000000000..e328cd1ec5d --- /dev/null +++ b/modules/ipromBidAdapter.js @@ -0,0 +1,72 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'iprom'; +const ENDPOINT_URL = 'https://core.iprom.net/programmatic'; +const VERSION = 'v1.0.0'; +const DEFAULT_CURRENCY = 'EUR'; +const DEFAULT_NETREVENUE = true; +const DEFAULT_TTL = 360; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function ({ bidder, params = {} } = {}) { + // id parameter checks + if (!params.id) { + utils.logError(`${bidder}: Parameter 'id' missing`); + return false; + } else if (typeof params.id !== 'string') { + utils.logError(`${bidder}: Parameter 'id' needs to be a string`); + return false; + } + // dimension parameter checks + if (!params.dimension) { + utils.logError(`${bidder}: Required parameter 'dimension' missing`); + return false; + } else if (typeof params.dimension !== 'string') { + utils.logError(`${bidder}: Parameter 'dimension' needs to be a string`); + return false; + } + + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const payload = { + bids: validBidRequests, + referer: bidderRequest.refererInfo, + version: VERSION + }; + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString + }; + }, + + interpretResponse: function (serverResponse, request) { + let bids = serverResponse.body; + + const bidResponses = []; + + bids.forEach(bid => { + bidResponses.push({ + ad: bid.ad, + requestId: bid.requestId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + creativeId: bid.creativeId, + currency: bid.currency || DEFAULT_CURRENCY, + netRevenue: bid.netRevenue || DEFAULT_NETREVENUE, + ttl: bid.ttl || DEFAULT_TTL, + }); + }); + + return bidResponses; + }, +} + +registerBidder(spec); diff --git a/modules/ipromBidAdapter.md b/modules/ipromBidAdapter.md new file mode 100644 index 00000000000..f7124e7c89c --- /dev/null +++ b/modules/ipromBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Iprom PreBid Adapter +Module Type: Bidder Adapter +Maintainer: support@iprom.si +``` + +# Description + +Module that connects to Iprom's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "iprom", + params: { + id: '1234', + dimension: '300x250' + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/ipromBidAdapter_spec.js b/test/spec/modules/ipromBidAdapter_spec.js new file mode 100644 index 00000000000..535cc332b21 --- /dev/null +++ b/test/spec/modules/ipromBidAdapter_spec.js @@ -0,0 +1,193 @@ +import {expect} from 'chai'; +import {spec} from 'modules/ipromBidAdapter.js'; + +describe('iPROM Adapter', function () { + let bidRequests; + let bidderRequest; + + beforeEach(function () { + bidRequests = [ + { + bidder: 'iprom', + params: { + id: '1234', + dimension: '300x250', + }, + adUnitCode: '/19966331/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidId: '29a72b151f7bd3', + auctionId: 'e36abb27-g3b1-1ad6-8a4c-701c8919d3hh', + bidderRequestId: '2z76da40m1b3cb8', + transactionId: 'j51lhf58-1ad6-g3b1-3j6s-912c9493g0gu' + } + ]; + + bidderRequest = { + timeout: 3000, + refererInfo: { + referer: 'https://adserver.si/index.html', + reachedTop: true, + numIframes: 1, + stack: [ + 'https://adserver.si/index.html', + 'https://adserver.si/iframe1.html', + ] + } + } + }); + + describe('validating bids', function () { + it('should accept valid bid', function () { + let validBid = { + bidder: 'iprom', + params: { + id: '1234', + dimension: '300x250', + }, + }; + + const isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + + it('should reject bid if missing dimension and id', function () { + let invalidBid = { + bidder: 'iprom', + params: {} + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject bid if missing dimension', function () { + let invalidBid = { + bidder: 'iprom', + params: { + id: '1234', + } + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject bid if dimension is not a string', function () { + let invalidBid = { + bidder: 'iprom', + params: { + id: '1234', + dimension: 404, + } + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject bid if missing id', function () { + let invalidBid = { + bidder: 'iprom', + params: { + dimension: '300x250', + } + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject bid if id is not a string', function () { + let invalidBid = { + bidder: 'iprom', + params: { + id: 1234, + dimension: '300x250', + } + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + + describe('building requests', function () { + it('should go to correct endpoint', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.method).to.exist; + expect(request.method).to.equal('POST'); + expect(request.url).to.exist; + expect(request.url).to.equal('https://core.iprom.net/programmatic'); + }); + + it('should add referer info', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.referer).to.exist; + expect(requestparse.referer.referer).to.equal('https://adserver.si/index.html'); + }); + + it('should add adapter version', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.version).to.exist; + }); + + it('should contain id and dimension', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.bids[0].params.id).to.equal('1234'); + expect(requestparse.bids[0].params.dimension).to.equal('300x250'); + }); + }); + + describe('handling responses', function () { + it('should return complete bid response', function () { + const serverResponse = { + body: [{ + requestId: '29a72b151f7bd3', + cpm: 0.5, + width: '300', + height: '250', + creativeId: 1234, + ad: 'Iprom Header bidding example' + } + ]}; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].requestId).to.equal('29a72b151f7bd3'); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].width).to.equal('300'); + expect(bids[0].height).to.equal('250'); + expect(bids[0].ad).to.have.length.above(1); + }); + + it('should return empty bid response', function () { + const emptyServerResponse = { + body: [] + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(emptyServerResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + }); +}); From 2a5ee8c5ade63e1427044fb52bd42c72ece8acd8 Mon Sep 17 00:00:00 2001 From: jackhsiehucf <77815341+jackhsiehucf@users.noreply.github.com> Date: Fri, 26 Feb 2021 21:31:17 +0800 Subject: [PATCH 23/96] Ucfunnel Bid Adapter: update currency parameter in ad response (#6357) --- modules/ucfunnelBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index d54ad7fb0ee..4003c6ed5fd 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -69,7 +69,7 @@ export const spec = { cpm: ad.cpm || 0, creativeId: ad.crid || ad.ad_id || bidRequest.params.adid, dealId: ad.deal || null, - currency: 'USD', + currency: ad.currency || 'USD', netRevenue: true, ttl: 1800, meta: {} From 3b31cf498dfe7bfc7873e743a8fc45ba6b9e2757 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Mon, 1 Mar 2021 15:08:44 +0300 Subject: [PATCH 24/96] TheMediaGrid Bid Adapter: fix trouble with alias using (#6363) --- modules/gridBidAdapter.js | 1 - test/spec/modules/gridBidAdapter_spec.js | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index b296fe39ae4..8ab2e2b8a95 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -329,7 +329,6 @@ function _addBidResponse(serverBid, bidRequest, bidResponses) { if (bid) { const bidResponse = { requestId: bid.bidId, // bid.bidderRequestId, - bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index b4e87119111..df119bb689d 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -491,7 +491,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -549,7 +548,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -563,7 +561,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -577,7 +574,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -637,7 +633,6 @@ describe('TheMediaGrid Adapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, @@ -654,7 +649,6 @@ describe('TheMediaGrid Adapter', function () { 'dealId': undefined, 'width': undefined, 'height': undefined, - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, @@ -786,7 +780,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -800,7 +793,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -814,7 +806,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -828,7 +819,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 4
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, From c0d1b149dcdc13e57ba107624471a857f9733308 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Mon, 1 Mar 2021 10:35:41 -0800 Subject: [PATCH 25/96] Rubicon Analytics Adapter: pass along advertiserDomains (#6356) * Pass along advertiser domains! * only send up to 10 adomains --- modules/rubiconAnalyticsAdapter.js | 9 ++- .../modules/rubiconAnalyticsAdapter_spec.js | 63 +++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index f1ba86de30f..08ae8bf2dd8 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -139,7 +139,8 @@ function sendMessage(auctionId, bidWonId) { 'mediaType', 'floorValue', 'floorRuleValue', - 'floorRule' + 'floorRule', + 'adomains' ]) : undefined ]); } @@ -364,7 +365,11 @@ 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 + 'floorRule', () => utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined, + 'adomains', () => { + let adomains = utils.deepAccess(bid, 'meta.advertiserDomains'); + return Array.isArray(adomains) && adomains.length > 0 ? adomains.slice(0, 10) : undefined + } ]); } diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 505a2885404..136f5ef240a 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -852,6 +852,69 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].adUnits[1].bids[0].bidResponse.dimensions).to.equal(undefined); }); + it('should pass along adomians correctly', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // 1 adomains + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + bidResponse1.meta = { + advertiserDomains: ['magnite.com'] + } + + // two adomains + let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse2.meta = { + advertiserDomains: ['prebid.org', 'magnite.com'] + } + + // make sure we only pass max 10 adomains + bidResponse2.meta.advertiserDomains = [...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains] + + events.emit(BID_RESPONSE, bidResponse1); + events.emit(BID_RESPONSE, bidResponse2); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['magnite.com']); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com']); + }); + + it('should NOT pass along adomians correctly when edge cases', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // empty => nothing + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + bidResponse1.meta = { + advertiserDomains: [] + } + + // not array => nothing + let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse2.meta = { + advertiserDomains: 'prebid.org' + } + + events.emit(BID_RESPONSE, bidResponse1); + events.emit(BID_RESPONSE, bidResponse2); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.be.undefined; + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; + }); + function performFloorAuction(provider) { let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); auctionInit.bidderRequests[0].bids[0].floorData = { From 4f4b49eb046977deffdf41823a9a8d6d2086a1ea Mon Sep 17 00:00:00 2001 From: msm0504 <51493331+msm0504@users.noreply.github.com> Date: Mon, 1 Mar 2021 23:07:06 -0500 Subject: [PATCH 26/96] support setting coopSync in s2sConfig (#6330) --- modules/prebidServerBidAdapter/index.js | 4 +++ .../modules/prebidServerBidAdapter_spec.js | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 2d8acb34225..d64dad31b17 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -203,6 +203,10 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig) { payload.us_privacy = uspConsent; } + if (typeof s2sConfig.coopSync === 'boolean') { + payload.coopSync = s2sConfig.coopSync; + } + const jsonPayload = JSON.stringify(payload); ajax(s2sConfig.syncEndpoint, (response) => { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 8d88a3e0042..e5c6fe0324a 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2529,5 +2529,36 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.deep.equal(['appnexus', 'rubicon']); }); + + it('should add cooperative sync flag to cookie_sync request if property is present', function () { + let s2sConfig = utils.deepClone(CONFIG); + s2sConfig.coopSync = false; + s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + let bidRequest = utils.deepClone(BID_REQUESTS); + + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.coopSync).to.equal(false); + }); + + it('should not add cooperative sync flag to cookie_sync request if property is not present', function () { + let s2sConfig = utils.deepClone(CONFIG); + s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + let bidRequest = utils.deepClone(BID_REQUESTS); + + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.coopSync).to.be.undefined; + }); }); }); From c746982bdb55d9026b84b683f567fc6e3b01eae3 Mon Sep 17 00:00:00 2001 From: PWyrembak Date: Tue, 2 Mar 2021 12:27:21 +0300 Subject: [PATCH 27/96] TrustX Bid Adapter: Fix alias error (#6373) --- modules/trustxBidAdapter.js | 1 - test/spec/modules/trustxBidAdapter_spec.js | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index f412fa21a05..a86ac1f2874 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -207,7 +207,6 @@ function _addBidResponse(serverBid, bidsMap, priceType, bidResponses, RendererCo const bid = slot.bids.shift(); const bidResponse = { requestId: bid.bidId, // bid.bidderRequestId, - bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 9e0aad9b36f..e9daaa83b5d 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -262,7 +262,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -320,7 +319,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -334,7 +332,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -348,7 +345,6 @@ describe('TrustXAdapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -476,7 +472,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -490,7 +485,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -504,7 +498,6 @@ describe('TrustXAdapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -518,7 +511,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 4
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -580,7 +572,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -594,7 +585,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 2
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -655,7 +645,6 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': true, @@ -735,7 +724,6 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': true, @@ -753,7 +741,6 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 250, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': true, @@ -771,7 +758,6 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 250, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': true, From 4d905765e61170d25ed96a8a0ea72805d74e094e Mon Sep 17 00:00:00 2001 From: Justas Pupelis Date: Tue, 2 Mar 2021 16:14:51 +0200 Subject: [PATCH 28/96] Adform Bid Adapter: add global targeting to the request query as parameter (#6376) Co-authored-by: Justas --- modules/adformBidAdapter.js | 30 +++++++++++++++++++++ test/spec/modules/adformBidAdapter_spec.js | 31 ++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js index 05e45a428d3..dc30e152861 100644 --- a/modules/adformBidAdapter.js +++ b/modules/adformBidAdapter.js @@ -5,6 +5,7 @@ import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import * as utils from '../src/utils.js'; +import includes from 'core-js-pure/features/array/includes.js'; const OUTSTREAM_RENDERER_URL = 'https://s2.adform.net/banners/scripts/video/outstream/render.js'; @@ -24,13 +25,32 @@ export const spec = { var request = []; var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ], [ 'eids', eids ] ]; + const targetingParams = { mkv: [], mkw: [], msw: [] }; + var bids = JSON.parse(JSON.stringify(validBidRequests)); var bidder = (bids[0] && bids[0].bidder) || BIDDER_CODE; + + // set common targeting options as query params + if (bids.length > 1) { + for (let key in targetingParams) { + if (targetingParams.hasOwnProperty(key)) { + const collection = bids.map(bid => ((bid.params[key] && bid.params[key].split(',')) || [])); + targetingParams[key] = collection.reduce(intersects); + if (targetingParams[key].length) { + bids.forEach((bid, index) => { + bid.params[key] = collection[index].filter(item => !includes(targetingParams[key], item)); + }); + } + } + } + } + for (i = 0, l = bids.length; i < l; i++) { bid = bids[i]; if ((bid.params.priceType === 'net') || (bid.params.pt === 'net')) { netRevenue = 'net'; } + for (j = 0, k = globalParams.length; j < k; j++) { _key = globalParams[j][0]; _value = bid[_key] || bid.params[_key]; @@ -65,6 +85,12 @@ export const spec = { request.push('us_privacy=' + bidderRequest.uspConsent); } + for (let key in targetingParams) { + if (targetingParams.hasOwnProperty(key)) { + globalParams.push([ key, targetingParams[key].join(',') ]); + } + } + for (i = 1, l = globalParams.length; i < l; i++) { _key = globalParams[i][0]; _value = globalParams[i][1]; @@ -114,6 +140,10 @@ export const spec = { return result; }, {}); } + + function intersects(col1, col2) { + return col1.filter(item => includes(col2, item)); + } }, interpretResponse: function (serverResponse, bidRequest) { const VALID_RESPONSES = { diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index 23db7a8dc97..18b2565d4f8 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -157,6 +157,37 @@ describe('Adform adapter', function () { assert.equal(eids, 'some_id_value'); }); + it('should add parameter to global parameters if it exists in all bids', function () { + const _bids = []; + _bids.push(bids[0]); + _bids.push(bids[1]); + _bids.push(bids[2]); + _bids[0].params.mkv = 'key:value,key1:value1'; + _bids[1].params.mkv = 'key:value,key1:value1,keyR:removed'; + _bids[2].params.mkv = 'key:value,key1:value1,keyR:removed,key8:value1'; + _bids[0].params.mkw = 'targeting'; + _bids[1].params.mkw = 'targeting'; + _bids[2].params.mkw = 'targeting,targeting2'; + _bids[0].params.msw = 'search:word,search:word2'; + _bids[1].params.msw = 'search:word'; + _bids[2].params.msw = 'search:word,search:word5'; + let bidList = _bids; + let request = spec.buildRequests(bidList); + let parsedUrl = parseUrl(request.url); + assert.equal(parsedUrl.query.mkv, encodeURIComponent('key:value,key1:value1')); + assert.equal(parsedUrl.query.mkw, 'targeting'); + assert.equal(parsedUrl.query.msw, encodeURIComponent('search:word')); + assert.ok(!parsedUrl.items[0].mkv); + assert.ok(!parsedUrl.items[0].mkw); + assert.equal(parsedUrl.items[0].msw, 'search:word2'); + assert.equal(parsedUrl.items[1].mkv, 'keyR:removed'); + assert.ok(!parsedUrl.items[1].mkw); + assert.ok(!parsedUrl.items[1].msw); + assert.equal(parsedUrl.items[2].mkv, 'keyR:removed,key8:value1'); + assert.equal(parsedUrl.items[2].mkw, 'targeting2'); + assert.equal(parsedUrl.items[2].msw, 'search:word5'); + }); + describe('user privacy', function () { it('should send GDPR Consent data to adform if gdprApplies', function () { let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: true, consentString: 'concentDataString'}}); From ee05c41e0610e8dab9a4db1d7853c9237b4b34af Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Tue, 2 Mar 2021 19:43:39 +0100 Subject: [PATCH 29/96] Prebid 4.29.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e992a108c5c..9de23df383d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.29.0-pre", + "version": "4.29.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 57d14e17daa860ab2dd6647ba1f448a5a4e25be3 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Tue, 2 Mar 2021 19:58:09 +0100 Subject: [PATCH 30/96] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9de23df383d..dfd8a60b06d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.29.0", + "version": "4.30.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 6debd2bf94ae5435497a24904f302a51265bfabd Mon Sep 17 00:00:00 2001 From: Salomon Rada Date: Tue, 2 Mar 2021 21:04:10 +0200 Subject: [PATCH 31/96] Gamoshi Bid Adapter: update adaptor aliases (#6355) --- modules/gamoshiBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index f8d2b70981c..de839219897 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -43,7 +43,7 @@ export const helper = { export const spec = { code: 'gamoshi', - aliases: ['gambid', 'cleanmedia', '9MediaOnline', 'MobfoxX'], + aliases: ['gambid', '9MediaOnline'], supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { From e5bd7f255d2122fcd4de09310c80ed60bdcd131d Mon Sep 17 00:00:00 2001 From: vladi-mmg Date: Tue, 2 Mar 2021 23:50:09 +0200 Subject: [PATCH 32/96] Marsmedia Bid Adapter: add support for viewability, floor price module, COPPA & CCPA (#6337) * Change publisherId to zoneId Add gdpr Add supply chain Add video media type * Remove comments * Fix unit test coverage * fix request id bug add vastXml to video response * Remove bid response default sizes * Change endpoint url * Add unit test for vastXml * Change end point * Remove trailing-space * Add onBidWon function * New adapter - videofy * Marsmedia & Videofy - Add onTimeout onSetTargeting * Create sendbeacon function * - add viewability * remove unnecessary utils.getWindowTop() --- modules/marsmediaBidAdapter.js | 171 ++++++++- test/spec/modules/marsmediaBidAdapter_spec.js | 349 ++++++++++-------- 2 files changed, 354 insertions(+), 166 deletions(-) diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js index 1ce2558b8de..bb1763ebb2e 100644 --- a/modules/marsmediaBidAdapter.js +++ b/modules/marsmediaBidAdapter.js @@ -3,6 +3,7 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; function MarsmediaAdapter() { this.code = 'marsmedia'; @@ -16,7 +17,7 @@ function MarsmediaAdapter() { let SUPPORTED_VIDEO_API = [1, 2, 5]; let slotsToBids = {}; let that = this; - let version = '2.3'; + let version = '2.4'; this.isBidRequestValid = function (bid) { return !!(bid.params && bid.params.zoneId); @@ -53,6 +54,7 @@ function MarsmediaAdapter() { if (!(impObj.banner || impObj.video)) { continue; } + impObj.bidfloor = _getFloor(BRs[i]); impObj.ext = frameExt(BRs[i]); impList.push(impObj); } @@ -153,9 +155,31 @@ function MarsmediaAdapter() { } function frameExt(bid) { - return { - bidder: { - zoneId: bid.params['zoneId'] + if ((bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes)) { + let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => utils.isArray(size)); + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) + ? _getViewability(element, utils.getWindowTop(), minSize) + : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + return { + bidder: { + zoneId: bid.params['zoneId'] + }, + viewability: viewabilityAmountRounded + } + } else { + return { + bidder: { + zoneId: bid.params['zoneId'] + }, + viewability: 'na' } } } @@ -180,12 +204,15 @@ function MarsmediaAdapter() { } }; if (BRs[0].schain) { - bid.source = { - 'ext': { - 'schain': BRs[0].schain - } - } + utils.deepSetValue(bid, 'source.ext.schain', BRs[0].schain); + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(bid, 'regs.ext.us_privacy', bidderRequest.uspConsent) } + if (config.getConfig('coppa') === true) { + utils.deepSetValue(bid, 'regs.coppa', config.getConfig('coppa') & 1) + } + return bid; } @@ -241,12 +268,6 @@ function MarsmediaAdapter() { sendbeacon(bid, 20) }; - function sendbeacon(bid, type) { - const bidString = JSON.stringify(bid); - const encodedBuf = window.btoa(bidString); - utils.triggerPixel('https://ping-hqx-1.go2speed.media/notification/rtb/beacon/?bt=' + type + '&bid=3mhdom&hb_j=' + encodedBuf, null); - } - this.interpretResponse = function (serverResponse) { let responses = serverResponse.body || []; let bids = []; @@ -295,6 +316,126 @@ function MarsmediaAdapter() { return bids; }; + + function sendbeacon(bid, type) { + const bidString = JSON.stringify(bid); + const encodedBuf = window.btoa(bidString); + utils.triggerPixel('https://ping-hqx-1.go2speed.media/notification/rtb/beacon/?bt=' + type + '&bid=3mhdom&hb_j=' + encodedBuf, null); + } + + /** + * Gets bidfloor + * @param {Object} bid + * @returns {Number} floor + */ + function _getFloor (bid) { + const curMediaType = bid.mediaTypes.video ? 'video' : 'banner'; + let floor = 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: '*' + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = floorInfo.floor; + } + } + + return floor; + } + + function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); + } + + function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; + } + + function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; + } + } + + function _getViewability(element, topWin, { w, h } = {}) { + return topWin.document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; + } + + function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + const elementInViewBoundingBox = _getIntersectionOfRects([ { + left: 0, + top: 0, + right: topWin.innerWidth, + bottom: topWin.innerHeight + }, elementBoundingBox ]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + return 0; + } + + function _getBoundingBox(element, { w, h } = {}) { + let { width, height, left, top, right, bottom } = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return { width, height, left, top, right, bottom }; + } + + function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, + right: rects[0].right, + top: rects[0].top, + bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; + } } export const spec = new MarsmediaAdapter(); diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js index b4c2fe68f34..cf074b0f3d6 100644 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -1,11 +1,41 @@ -import {spec} from '../../../modules/marsmediaBidAdapter.js'; -import * as utils from '../../../src/utils.js'; -import * as sinon from 'sinon'; +import { spec } from 'modules/marsmediaBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; -var r1adapter = spec; +var marsAdapter = spec; describe('marsmedia adapter tests', function () { + let element, win; + let sandbox; + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; this.defaultBidderRequest = { 'refererInfo': { 'referer': 'Reference Page', @@ -15,28 +45,40 @@ describe('marsmedia adapter tests', function () { ] } }; + + this.defaultBidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'Unit-Code', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('Unit-Code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); }); describe('Verify 1.0 POST Banner Bid Request', function () { it('buildRequests works', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); expect(bidRequest.method).to.equal('POST'); @@ -52,11 +94,11 @@ describe('marsmedia adapter tests', function () { expect(openrtbRequest.imp[0].ext.bidder.zoneId).to.equal(9999); }); - it('interpretResponse works', function() { + /* it('interpretResponse works', function() { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-0', + 'impid': 'Unit-Code', 'w': 300, 'h': 250, 'adm': '
My Compelling Ad
', @@ -67,7 +109,7 @@ describe('marsmedia adapter tests', function () { ] }; - var bannerBids = r1adapter.interpretResponse(bidList); + var bannerBids = marsAdapter.interpretResponse(bidList); expect(bannerBids.length).to.equal(1); const bid = bannerBids[0]; @@ -78,7 +120,7 @@ describe('marsmedia adapter tests', function () { expect(bid.netRevenue).to.equal(true); expect(bid.cpm).to.equal(1.0); expect(bid.ttl).to.equal(350); - }); + }); */ }); describe('Verify POST Video Bid Request', function() { @@ -95,7 +137,7 @@ describe('marsmedia adapter tests', function () { 'context': 'instream' } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'sizes': [ [300, 250] ], @@ -107,7 +149,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); expect(bidRequest.method).to.equal('POST'); @@ -132,7 +174,7 @@ describe('marsmedia adapter tests', function () { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-1', + 'impid': 'Unit-Code', 'price': 1, 'adm': 'https://example.com/', 'adomain': [ @@ -147,7 +189,7 @@ describe('marsmedia adapter tests', function () { ] }; - var videoBids = r1adapter.interpretResponse(bidList); + var videoBids = marsAdapter.interpretResponse(bidList); expect(videoBids.length).to.equal(1); const bid = videoBids[0]; @@ -166,7 +208,7 @@ describe('marsmedia adapter tests', function () { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-1', + 'impid': 'Unit-Code', 'price': 1, 'adm': '', 'adomain': [ @@ -181,7 +223,7 @@ describe('marsmedia adapter tests', function () { ] }; - var videoBids = r1adapter.interpretResponse(bidList); + var videoBids = marsAdapter.interpretResponse(bidList); expect(videoBids.length).to.equal(1); const bid = videoBids[0]; @@ -199,26 +241,6 @@ describe('marsmedia adapter tests', function () { describe('misc buildRequests', function() { it('should send GDPR Consent data to Marsmedia tag', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-3', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - var consentString = 'testConsentString'; var gdprBidderRequest = this.defaultBidderRequest; gdprBidderRequest.gdprConsent = { @@ -226,13 +248,50 @@ describe('marsmedia adapter tests', function () { 'consentString': consentString }; - var bidRequest = r1adapter.buildRequests(bidRequestList, gdprBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, gdprBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.user.ext.consent).to.equal(consentString); expect(openrtbRequest.regs.ext.gdpr).to.equal(true); }); + it('should have CCPA Consent if defined', function () { + const ccpaBidderRequest = this.defaultBidderRequest; + ccpaBidderRequest.uspConsent = '1YYN'; + const bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, ccpaBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.regs.ext.us_privacy).to.equal('1YYN'); + }); + + it('should submit coppa if set in config', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.regs.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should process floors module if available', function() { + const floorBidderRequest = this.defaultBidRequestList; + const floorInfo = { + currency: 'USD', + floor: 1.20 + }; + floorBidderRequest[0].getFloor = () => floorInfo; + const request = marsAdapter.buildRequests(floorBidderRequest, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.imp[0].bidfloor).to.equal(1.20); + }); + + it('should have 0 bidfloor value', function() { + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.imp[0].bidfloor).to.equal(0); + }); + it('prefer 2.0 sizes', function () { var bidRequestList = [ { @@ -245,7 +304,7 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300, 600]] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'sizes': [[300, 250]], 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', @@ -255,7 +314,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); @@ -274,7 +333,7 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300]] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -283,7 +342,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.method).to.be.undefined; }); @@ -297,7 +356,7 @@ describe('marsmedia adapter tests', function () { 'mediaTypes': { 'banner': {} }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -306,7 +365,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.method).to.be.undefined; }); @@ -320,7 +379,7 @@ describe('marsmedia adapter tests', function () { 'mediaTypes': { 'banner': {'sizes': [['400', '500'], ['4n0', '5g0']]} }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -329,35 +388,15 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].banner.format.length).to.equal(1); }); it('dnt is correctly set to 1', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 600]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - var dntStub = sinon.stub(utils, 'getDNT').returns(1); - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); dntStub.restore(); @@ -378,7 +417,7 @@ describe('marsmedia adapter tests', function () { 'playerSize': ['600', '300'] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -387,7 +426,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.equal(600); @@ -407,7 +446,7 @@ describe('marsmedia adapter tests', function () { 'playerSize': ['badWidth', 'badHeight'] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -416,7 +455,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.be.undefined; @@ -435,7 +474,7 @@ describe('marsmedia adapter tests', function () { 'context': 'instream' } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -444,7 +483,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.be.undefined; @@ -453,52 +492,74 @@ describe('marsmedia adapter tests', function () { it('should return empty site data when refererInfo is missing', function() { delete this.defaultBidderRequest.refererInfo; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.site.domain).to.equal(''); expect(openrtbRequest.site.page).to.equal(''); expect(openrtbRequest.site.ref).to.equal(''); }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(75); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(0); + }); + }); }); it('should return empty site.domain and site.page when refererInfo.stack is empty', function() { this.defaultBidderRequest.refererInfo.stack = []; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.site.domain).to.equal(''); @@ -508,24 +569,7 @@ describe('marsmedia adapter tests', function () { it('should secure correctly', function() { this.defaultBidderRequest.refererInfo.stack[0] = ['https://securesite.dvl']; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].secure).to.equal(1); @@ -551,9 +595,12 @@ describe('marsmedia adapter tests', function () { 'params': { 'zoneId': 9999 }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -563,7 +610,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.source.ext.schain).to.deep.equal(schain); @@ -571,7 +618,7 @@ describe('marsmedia adapter tests', function () { describe('misc interpretResponse', function () { it('No bid response', function() { - var noBidResponse = r1adapter.interpretResponse({ + var noBidResponse = marsAdapter.interpretResponse({ 'body': '' }); expect(noBidResponse.length).to.equal(0); @@ -589,22 +636,22 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300, 250]] } }, - 'adUnitCode': 'bannerDiv' + 'adUnitCode': 'Unit-Code' }; it('should return true when required params found', function () { - expect(r1adapter.isBidRequestValid(bid)).to.equal(true); + expect(marsAdapter.isBidRequestValid(bid)).to.equal(true); }); it('should return false when placementId missing', function () { delete bid.params.zoneId; - expect(r1adapter.isBidRequestValid(bid)).to.equal(false); + expect(marsAdapter.isBidRequestValid(bid)).to.equal(false); }); }); describe('getUserSyncs', function () { it('returns an empty string', function () { - expect(r1adapter.getUserSyncs()).to.deep.equal([]); + expect(marsAdapter.getUserSyncs()).to.deep.equal([]); }); }); From a821c84468649f0a0bf17811f553bda6c5fa6f31 Mon Sep 17 00:00:00 2001 From: JulieLorin Date: Thu, 4 Mar 2021 13:51:19 +0100 Subject: [PATCH 33/96] fix bidderRequest matching for finding a renderer (#6359) --- src/auction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auction.js b/src/auction.js index ec9ff7d8209..217a50be3d6 100644 --- a/src/auction.js +++ b/src/auction.js @@ -519,7 +519,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bidObject); // a publisher-defined renderer can be used to render bids - const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode); + const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode && bid.bidId == bidObject.requestId); const adUnitRenderer = bidReq && bidReq.renderer; // a publisher can also define a renderer for a mediaType From 1b0d7b80342689c1d233797c03bb9cd178ecbd02 Mon Sep 17 00:00:00 2001 From: Laura Morillo-Velarde Date: Thu, 4 Mar 2021 15:40:20 +0100 Subject: [PATCH 34/96] Seedtag Bid Adapter: add support for inArticle placement (#6369) * Fix: check mandatory video params * Simplifying mediaType video existence check * SQDTAR-42: onWonBid event (#2) * Adding onBidWon handler. * Adding nurl to bid. * Adding nurl field to bid. * Adding inArticle placement type to seedtag adapter. (#1) Co-authored-by: Carlos Barreiro Mata --- modules/seedtagBidAdapter.js | 27 ++++++++++--- test/spec/modules/seedtagBidAdapter_spec.js | 45 ++++++++++++++++++--- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index e1832e50020..24cc6489ef0 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -6,7 +6,13 @@ const BIDDER_CODE = 'seedtag'; const SEEDTAG_ALIAS = 'st'; const SEEDTAG_SSP_ENDPOINT = 'https://s.seedtag.com/c/hb/bid'; const SEEDTAG_SSP_ONTIMEOUT_ENDPOINT = 'https://s.seedtag.com/se/hb/timeout'; - +const ALLOWED_PLACEMENTS = { + inImage: true, + inScreen: true, + inArticle: true, + banner: true, + video: true +} const mediaTypesMap = { [BANNER]: 'display', [VIDEO]: 'video' @@ -27,10 +33,7 @@ function hasMandatoryParams(params) { !!params.publisherId && !!params.adUnitId && !!params.placement && - (params.placement === 'inImage' || - params.placement === 'inScreen' || - params.placement === 'banner' || - params.placement === 'video') + !!ALLOWED_PLACEMENTS[params.placement] ); } @@ -88,8 +91,10 @@ function buildBid(seedtagBid) { currency: seedtagBid.currency, netRevenue: true, mediaType: mediaType, - ttl: seedtagBid.ttl + ttl: seedtagBid.ttl, + nurl: seedtagBid.nurl }; + if (mediaType === VIDEO) { bid.vastXml = seedtagBid.content; } else { @@ -200,6 +205,16 @@ export const spec = { onTimeout(data) { getTimeoutUrl(data); utils.triggerPixel(SEEDTAG_SSP_ONTIMEOUT_ENDPOINT); + }, + + /** + * Function to call when the adapter wins the auction + * @param {bid} Bid information received from the server + */ + onBidWon: function (bid) { + if (bid && bid.nurl) { + utils.triggerPixel(bid.nurl); + } } } registerBidder(spec); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 8957bde6bd9..b43d2fa8a5f 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai' import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js' +import * as utils from 'src/utils.js' const PUBLISHER_ID = '0000-0000-01' const ADUNIT_ID = '000000' @@ -42,7 +43,7 @@ describe('Seedtag Adapter', function() { } ) } - const placements = ['banner', 'video', 'inImage', 'inScreen'] + const placements = ['banner', 'video', 'inImage', 'inScreen', 'inArticle'] placements.forEach(placement => { it('should be ' + placement, function() { const isBidRequestValid = spec.isBidRequestValid( @@ -53,7 +54,7 @@ describe('Seedtag Adapter', function() { }) }) }) - describe('when video slot has all mandatory params.', function() { + describe('when video slot has all mandatory params', function() { it('should return true, when video mediatype object are correct.', function() { const slotConfig = getSlotConfigs( { @@ -116,7 +117,7 @@ describe('Seedtag Adapter', function() { expect(isBidRequestValid).to.equal(false) }) }) - describe('when video mediaType object is not correct.', function() { + describe('when video mediaType object is not correct', function() { function createVideoSlotconfig(mediaType) { return getSlotConfigs(mediaType, { publisherId: PUBLISHER_ID, @@ -301,7 +302,7 @@ describe('Seedtag Adapter', function() { expect(typeof bids).to.equal('object') expect(bids.length).to.equal(0) }) - it('should return a void array, when the server response have not got bids.', function() { + it('should return a void array, when the server response have no bids.', function() { const request = { data: JSON.stringify({}) } const serverResponse = { body: { bids: [] } } const bids = spec.interpretResponse(serverResponse, request) @@ -323,7 +324,8 @@ describe('Seedtag Adapter', function() { width: 728, height: 90, mediaType: 'display', - ttl: 360 + ttl: 360, + nurl: 'testurl.com/nurl' } ], cookieSync: { url: '' } @@ -338,6 +340,7 @@ describe('Seedtag Adapter', function() { expect(bids[0].currency).to.equal('USD') expect(bids[0].netRevenue).to.equal(true) expect(bids[0].ad).to.equal('content') + expect(bids[0].nurl).to.equal('testurl.com/nurl') }) }) describe('the bid is a video', function() { @@ -354,7 +357,8 @@ describe('Seedtag Adapter', function() { width: 728, height: 90, mediaType: 'video', - ttl: 360 + ttl: 360, + nurl: undefined } ], cookieSync: { url: '' } @@ -416,4 +420,33 @@ describe('Seedtag Adapter', function() { ) }) }) + + describe('onBidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel') + }) + + afterEach(function() { + utils.triggerPixel.restore() + }) + + describe('without nurl', function() { + const bid = {} + + it('does not create pixel ', function() { + spec.onBidWon(bid) + expect(utils.triggerPixel.called).to.equal(false); + }) + }) + + describe('with nurl', function () { + const nurl = 'http://seedtag_domain/won' + const bid = { nurl } + + it('creates nurl pixel if bid nurl', function() { + spec.onBidWon({ nurl }) + expect(utils.triggerPixel.calledWith(nurl)).to.equal(true); + }) + }) + }) }) From 8432c42c8d2da2acbfdf1e3d678b206a4236c15d Mon Sep 17 00:00:00 2001 From: guiann Date: Thu, 4 Mar 2021 19:41:36 +0100 Subject: [PATCH 35/96] improve robustness on OnEvent fields (#6384) --- modules/adyoulikeBidAdapter.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 308d474d2c4..e5777f1e627 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -276,10 +276,15 @@ function getNativeAssets(response, nativeConfig) { native.clickUrl = adJson.TrackingPrefix + '/ar?event_kind=CLICK&attempt=' + adJson.Attempt + '&campaign=' + adJson.Campaign + '&url=' + encodeURIComponent(adJson.Content.Landing.Url); - native.clickTrackers = getTrackers(adJson.OnEvents['CLICK']); - native.impressionTrackers = getTrackers(adJson.OnEvents['IMPRESSION']); + if (adJson.OnEvents) { + native.clickTrackers = getTrackers(adJson.OnEvents['CLICK']); + native.impressionTrackers = getTrackers(adJson.OnEvents['IMPRESSION']); + native.javascriptTrackers = getTrackers(adJson.OnEvents['IMPRESSION'], true); + } else { + native.impressionTrackers = []; + } + native.impressionTrackers.push(impressionUrl); - native.javascriptTrackers = getTrackers(adJson.OnEvents['IMPRESSION'], true); } Object.keys(nativeConfig).map(function(key, index) { From 11017aea3070b55f88d256885cf54386a3673b24 Mon Sep 17 00:00:00 2001 From: Adam Browning <19834421+adam-browning@users.noreply.github.com> Date: Thu, 4 Mar 2021 20:55:31 +0200 Subject: [PATCH 36/96] oneVideo Bid Adapter: remove adapter adId because of conflict with pbjs core (#6382) * updated object,str,num validations using pbjs utils * validation if statements for content object * validation if statements for content object * updated contetn object validations using utils * refractoring clean if statement * fixing typos * added todos * added category string & data object validations * fixed esling ENDPOINT issue * updated content obj unit tests * fixed cat & data validation * fixed producer as object * revert .includes() to .indexof() for IE * reduced content obj params accoriding to ad-server support * fixed typeOf typo * fixed episode to Number * gitignore * restore gitignore * removed unsupported params from md file * reverted package & pagkage-lock * fixed incorrect episdoe from str to num * removed bid.adid setting from L97 * removed bid.adId setting * updated adId test * updated version to 3.0.6 --- modules/oneVideoBidAdapter.js | 3 +-- test/spec/modules/oneVideoBidAdapter_spec.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js index 2f9275f969d..3bf14eb11cb 100644 --- a/modules/oneVideoBidAdapter.js +++ b/modules/oneVideoBidAdapter.js @@ -4,7 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'oneVideo'; export const spec = { code: 'oneVideo', - VERSION: '3.0.5', + VERSION: '3.0.6', ENDPOINT: 'https://ads.adaptv.advertising.com/rtb/openrtb?ext_id=', E2ETESTENDPOINT: 'https://ads-wc.v.ssp.yahoo.com/rtb/openrtb?ext_id=', SYNC_ENDPOINT1: 'https://pixel.advertising.com/ups/57304/sync?gdpr=&gdpr_consent=&_origin=0&redir=true', @@ -94,7 +94,6 @@ export const spec = { requestId: bidRequest.bidId, bidderCode: spec.code, cpm: bid.price, - adId: bid.adid, creativeId: bid.crid, width: size.width, height: size.height, diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index bc37ea15255..9ee1045a6e8 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -221,7 +221,7 @@ describe('OneVideoBidAdapter', function () { const placement = bidRequest.params.video.placement; const rewarded = bidRequest.params.video.rewarded; const inventoryid = bidRequest.params.video.inventoryid; - const VERSION = '3.0.5'; + const VERSION = '3.0.6'; expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); @@ -567,7 +567,6 @@ describe('OneVideoBidAdapter', function () { requestId: bidRequest.bidId, bidderCode: spec.code, cpm: serverResponse.seatbid[0].bid[0].price, - adId: serverResponse.seatbid[0].bid[0].adid, creativeId: serverResponse.seatbid[0].bid[0].crid, vastXml: serverResponse.seatbid[0].bid[0].adm, width: 640, From 86bb938c7100de8f6a9b9fedf38d014a57e6e585 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 4 Mar 2021 19:00:40 -0400 Subject: [PATCH 37/96] PBS Bid Adapter: add dchain (demand chain object) to prebid server adapter (#6383) * Update prebidServerBidAdapter_spec.js * Update index.js --- modules/prebidServerBidAdapter/index.js | 1 + test/spec/modules/prebidServerBidAdapter_spec.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index d64dad31b17..7cfef6a0784 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -930,6 +930,7 @@ const OPEN_RTB_PROTOCOL = { if (bid.burl) { bidObject.burl = bid.burl; } bidObject.currency = (response.cur) ? response.cur : DEFAULT_S2S_CURRENCY; bidObject.meta = bidObject.meta || {}; + if (bid.ext && bid.ext.dchain) { bidObject.meta.dchain = utils.deepClone(bid.ext.dchain); } if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } // the OpenRTB location for "TTL" as understood by Prebid.js is "exp" (expiration). diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index e5c6fe0324a..9e20629fe45 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -270,6 +270,7 @@ const RESPONSE_OPENRTB = { 'win': 'http://wurl.org?id=333' } }, + 'dchain': { 'ver': '1.0', 'complete': 0, 'nodes': [ { 'asi': 'magnite.com', 'bsid': '123456789', } ] }, 'bidder': { 'appnexus': { 'brand_id': 1, @@ -1936,6 +1937,9 @@ describe('S2S Adapter', function () { expect(response).to.have.property('meta'); expect(response.meta).to.have.property('advertiserDomains'); expect(response.meta.advertiserDomains[0]).to.equal('appnexus.com'); + expect(response.meta).to.have.property('dchain'); + expect(response.meta.dchain.ver).to.equal('1.0'); + expect(response.meta.dchain.nodes[0].asi).to.equal('magnite.com'); expect(response).to.not.have.property('vastUrl'); expect(response).to.not.have.property('videoCacheKey'); expect(response).to.have.property('ttl', 60); From ad649b777eb929239bc63866b1704a7701b3019d Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Fri, 5 Mar 2021 03:25:05 -0800 Subject: [PATCH 38/96] Gulp Build: fix to populate modules list when gulp bundle is executed (#6331) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * need to update modules list when gulp bundle is called * added a comment --- gulpfile.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 51536992bd0..d7b91db4ade 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -157,7 +157,7 @@ function makeWebpackPkg() { const analyticsSources = helpers.getAnalyticsSources(); const moduleSources = helpers.getModulePaths(externalModules); - const modulesString = (externalModules.length > 0) ? externalModules.join(', ') : 'All available modules in current version.'; + const modulesString = getModulesListToAddInBanner(externalModules); return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) @@ -167,6 +167,10 @@ function makeWebpackPkg() { .pipe(gulp.dest('build/dist')); } +function getModulesListToAddInBanner(modules){ + return (modules.length > 0) ? modules.join(', ') : 'All available modules in current version.'; +} + function gulpBundle(dev) { return bundle(dev).pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist'))); } @@ -216,6 +220,8 @@ function bundle(dev, moduleArr) { return gulp.src( entries ) + // Need to uodate the "Modules: ..." section in comment with the current modules list + .pipe(replace(/(Modules: )(.*?)(\*\/)/, ('$1' + getModulesListToAddInBanner(helpers.getArgModules()) + ' $3'))) .pipe(gulpif(dev, sourcemaps.init({ loadMaps: true }))) .pipe(concat(outputFileName)) .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', { From 3ae5ea07b2a62a911efe46df16a3a2e5e76e5dd1 Mon Sep 17 00:00:00 2001 From: Skylinar <53079123+Skylinar@users.noreply.github.com> Date: Fri, 5 Mar 2021 22:31:23 +0100 Subject: [PATCH 39/96] Smartx Bid Adapter: updated out-stream render to support smartPlay 5.2 (#6370) * Add smartclipBidAdapter * smartxBidAdapter.js - removed unused variables, removed debug, added window before the outstream related functions * - made outstream player configurable * remove wrong named files * camelcase * fix * Out-Stream render update to SmartPlay 5.2 * ESlint fix * ESlint fix * ESlint fix * adjust tests, fixes * ESlint Co-authored-by: smartclip AdTechnology Co-authored-by: Gino Cirlini --- modules/smartxBidAdapter.js | 64 +++++++--------------- modules/smartxBidAdapter.md | 12 ++-- test/spec/modules/smartxBidAdapter_spec.js | 4 +- 3 files changed, 29 insertions(+), 51 deletions(-) diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 804b25d1afc..57c54cb5090 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -64,7 +64,7 @@ export const spec = { return false; } if (!utils.getBidIdParameter('outstream_function', bid.params)) { - utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header. You can override the default SmartX outstream rendering by defining your own Outstream function using field outstream_function.'); + utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header.'); return true; } } @@ -311,7 +311,7 @@ export const spec = { return utils.logMessage('SmartX outstream video loaded event'); }, ended: function ended() { - utils.logMessage('SmartX outstream renderer video event'); + return utils.logMessage('SmartX outstream renderer video event'); } }); } catch (err) { @@ -328,50 +328,25 @@ export const spec = { } function createOutstreamScript(bid) { - // for SmartPlay 4.12 - function scPrebidClose(ele, completeCollapsed) { - if (completeCollapsed) { - document.getElementById(ele.id).style.display = 'none'; - } - } - const confMinAdWidth = utils.getBidIdParameter('minAdWidth', bid.renderer.config.outstream_options) || 290; const confMaxAdWidth = utils.getBidIdParameter('maxAdWidth', bid.renderer.config.outstream_options) || 900; - const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options) || 'false'; - const confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options) || 'true'; - const confHeaderText = utils.getBidIdParameter('headerText', bid.renderer.config.outstream_options) || ''; - const confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options) || 0; - const confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options) || 1600; + const confStartOpen = utils.getBidIdParameter('startOpen', bid.renderer.config.outstream_options); + const confEndingScreen = utils.getBidIdParameter('endingScreen', bid.renderer.config.outstream_options); + const confTitle = utils.getBidIdParameter('title', bid.renderer.config.outstream_options); + const confSkipOffset = utils.getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); + const confDesiredBitrate = utils.getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); const elementId = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; - // for SmartPlay 4.12 - let initCollapsed = true; - let completeCollapsed = true; - if (confStartOpen === 'true') { - initCollapsed = false; - } - if (confEndingScreen === 'true') { - completeCollapsed = false; - } - utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); - let smartPlayObj = { + var smartPlayObj = { minAdWidth: confMinAdWidth, maxAdWidth: confMaxAdWidth, - headerText: confHeaderText, + title: confTitle, skipOffset: confSkipOffset, - behaviourMatrix: { - init: { - 'collapsed': initCollapsed - }, - complete: { - 'collapsed': completeCollapsed - } - }, - environmentVars: { - desiredBitrate: confDesiredBitrate, - }, + startOpen: confStartOpen, + endingScreen: confEndingScreen, + desiredBitrate: confDesiredBitrate, onStartCallback: function (m, n) { try { window.sc_smartIntxtStart(n); @@ -384,23 +359,24 @@ function createOutstreamScript(bid) { }, onEndCallback: function (m, n) { try { - scPrebidClose(n, completeCollapsed); // for SmartPlay 4.12 window.sc_smartIntxtEnd(n); } catch (f) {} }, }; + smartPlayObj.adResponse = bid.vastContent; - const script = window.document.createElement('script'); + + const divID = '#' + elementId; + var script = document.createElement('script'); + script.src = 'https://dco.smartclip.net/?plc=7777778'; script.type = 'text/javascript'; script.async = 'true'; - script.src = 'https://dco.smartclip.net/?plc=7777777'; script.onload = script.onreadystatechange = function () { - var rs = this.readyState; - if (rs && rs != 'complete' && rs != 'loaded') return; try { - window.SmartPlay(elementId, smartPlayObj); + // eslint-disable-next-line + let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); } catch (e) { - utils.logError('error caught : ' + e); + utils.logError('[SmartPlay][renderer] Error caught: ' + e); } }; return script; diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index 082a36f3dde..b25ce68bb6e 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -36,10 +36,10 @@ This adapter requires setup and approval from the smartclip team. slot: 'video1', minAdWidth: 290, maxAdWidth: 900, - headerText: '', + title: '', skipOffset: 0, - startOpen: 'true', - endingScreen: 'true', + startOpen: true, + endingScreen: true, desiredBitrate: 1600, }, } @@ -71,10 +71,10 @@ This adapter requires setup and approval from the smartclip team. slot: 'video1', minAdWidth: 290, maxAdWidth: 900, - headerText: '', + title: '', skipOffset: 0, - startOpen: 'true', - endingScreen: 'true', + startOpen: true, + endingScreen: true, desiredBitrate: 1600, }, user: { diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 82c6642bd74..abc06b48ce7 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -421,6 +421,8 @@ describe('The smartx adapter', function () { it('should return an array of bid responses', function () { var responses = spec.interpretResponse(serverResponse, bidderRequestObj); expect(responses).to.be.an('array').with.length(2); + expect(bidderRequestObj).to.be.an('Object'); + expect(bidderRequestObj.bidRequest.bids).to.be.an('array').with.length(2); expect(responses[0].requestId).to.equal(123); expect(responses[0].currency).to.equal('USD'); expect(responses[0].cpm).to.equal(12); @@ -505,7 +507,7 @@ describe('The smartx adapter', function () { responses[0].renderer.render(responses[0]); expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); - expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777777'); + expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777778'); window.document.getElementById.restore(); }); From d474a4647a937e21475fa45ccd832058fc4136e0 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Fri, 5 Mar 2021 17:51:35 -0500 Subject: [PATCH 40/96] Fpd 2.0 Update (#6293) * Update to consolidate applying FPD to both banner and video requests. FPD will be merged using global defined FPD, ad unit FPD, and rubicon bidder param FPD. Validation logic with warning logs added * Refectored last push to: 1) Correct keywords bug 2) Revise error which looked for FPD in (user/context).ext.data as opposed to (user/context).data 3) General code cleanup * Consolidated other FPD data logic into new function * 1. Update to move pbadslot and adserver data into imp[] as opposed to parent. 2. Update to convert keywords passed through RP params to string if array found * Removed unnecessary conditional * Changed conditional to check for undefined type * FPD 2.0 Update 1) The setConfig and setBidderConfig functions support a transition period where they map the original 'fpd' config: - fpd.context.ATTR --> ortb2.site.ATTR - fpd.context.data.ATTR --> ortb2.site.ext.data - fpd.user.ATTR --> ortb2.user.ATTR - fpd.user.data.ATTR --> ortb2.user.ext.data 2) gptPreAuction: a) move adunit.fpd to adunit.ortb2 b) adUnit.ortb2Imp.ext.data.adserver.{name, adSlot} c) pbAdSlot moves to AdUnit.ortb2Imp.ext.data.pbAdSlot 3) pbsBidAdapter a) merge the new ortb2 and AdUnit.ortb2Imp.ext objects into the OpenRTB JSON. b) therefore imp[].ext.context.data.pbadslot is now changed to imp[].ext.data.pbadslot (no context) c) read adUnit.ortb2Imp.ext.data.adserver from the new location. Output location is moved to imp[].ext.data.adserver (no context) * FPD 2.0 Update Update to adrelevantis adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to amx adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to avocet adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to criteo adapter to look at config.ortb2 instead of config.fpd * Update to correct imp fpd structure * Update to s2s adapter to coincide with imp fpd alteration * Update to consolidate several lines of duplicate code into one location * Slight modification for ortb2Imp to use ortb2Imp.ext as opposed to ortb2Imp.ext.data * FPD 2.0 Update Update to grid adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to inmar adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to luponmedia adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to smaato adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to triplelift adapter to look at config.ortb2 instead of config.fpd * Update to gptPreAuction to move over to imp level ortb2 * FPD 2.0 Update Update to triplelift adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update * FPD 2.0 Update Update to jwplayerRtd adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to admixer adapter to look at config.ortb2 instead of config.fpd * FPD 2.0 Update Update to rubicon adapter to look at config.ortb2 instead of config.fpd * Update to fix keyword bug * Added backwards compatibility functions for FPD both global/bidder and adunit level data * Update to utilize new backward functionality for fpd 2.0 * Removed extra new line * Update to include new backward functionality for FPD 2.0 data * Update to utilize new backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Update to utilize backward functionality to pass FPD 2.0 data * Fixed typo in fpd config object location * Uodate to utilize backward functionality to pass FPD 2.0 data * Update to change all ortb2Imp.ext.data.adserver.adSlot references to ortb2Imp.ext.data.adserver.adslot - all lowercase. Corresponding adapter and unit tests to adhere to these changes * Fixed typo * Fixed typo * FPD 2.0 update to rubicon adapter to pass iab values * Updates: 1) Change function name 2) addAdUnits always pass array 3) Remove unecessary comment 4) Bug fix for ortb2.user.data to be filtered on legacy fpd conversion --- modules/admixerBidAdapter.js | 8 +- modules/adrelevantisBidAdapter.js | 6 +- modules/amxBidAdapter.js | 2 +- modules/avocetBidAdapter.js | 2 +- modules/criteoBidAdapter.js | 11 +- modules/gptPreAuction.js | 32 +-- modules/gridBidAdapter.js | 4 +- modules/inmarBidAdapter.js | 2 +- modules/jwplayerRtdProvider.js | 4 +- modules/luponmediaBidAdapter.js | 7 +- modules/prebidServerBidAdapter/index.js | 60 +++--- modules/rubiconBidAdapter.js | 97 +++++---- modules/smaatoBidAdapter.js | 6 +- modules/tripleliftBidAdapter.js | 11 +- src/adapterManager.js | 2 +- src/config.js | 135 ++++++++++++- src/prebid.js | 6 +- test/spec/adUnits_spec.js | 11 + test/spec/config_spec.js | 15 ++ .../modules/adrelevantisBidAdapter_spec.js | 10 +- test/spec/modules/amxBidAdapter_spec.js | 20 +- test/spec/modules/criteoBidAdapter_spec.js | 24 ++- test/spec/modules/gptPreAuction_spec.js | 54 ++--- test/spec/modules/gridBidAdapter_spec.js | 10 + test/spec/modules/jwplayerRtdProvider_spec.js | 48 ++--- .../modules/prebidServerBidAdapter_spec.js | 110 ++++++---- test/spec/modules/rubiconBidAdapter_spec.js | 190 +++++++++++------- test/spec/modules/smaatoBidAdapter_spec.js | 6 +- .../spec/modules/tripleliftBidAdapter_spec.js | 20 +- 29 files changed, 583 insertions(+), 330 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index d2a6f9a639e..6cf738a0086 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -21,7 +21,7 @@ export const spec = { buildRequests: function (validRequest, bidderRequest) { const payload = { imps: [], - fpd: config.getConfig('fpd') + fpd: config.getLegacyFpd(config.getConfig('ortb2')) }; let endpointUrl; if (bidderRequest) { @@ -42,7 +42,11 @@ export const spec = { } } validRequest.forEach((bid) => { - payload.imps.push(bid); + let imp = {}; + Object.keys(bid).forEach(key => { + (key === 'ortb2Imp') ? imp.fpd = config.getLegacyImpFpd(bid[key]) : imp[key] = bid[key]; + }); + payload.imps.push(imp); }); const payloadString = JSON.stringify(payload); return { diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 5da941c65ca..c6298cffde9 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -120,11 +120,11 @@ export const spec = { payload.referrer_detection = refererinfo; } - let fpdcfg = config.getConfig('fpd') + let fpdcfg = config.getLegacyFpd(config.getConfig('ortb2')); if (fpdcfg && fpdcfg.context) { let fdata = { - keywords: fpdcfg.context.keywords, - category: fpdcfg.context.data.category + keywords: fpdcfg.context.keywords || '', + category: utils.deepAccess(fpdcfg, 'context.data.category') || '' } payload.fpd = fdata; } diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index a1fa202c154..497c2142b9b 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -259,7 +259,7 @@ export const spec = { d: '', m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, - fpd: config.getConfig('fpd'), + fpd: config.getLegacyFpd(config.getConfig('ortb2')), eids: values(bidRequests.reduce((all, bid) => { // we only want unique ones in here if (bid == null || bid.userIdAsEids == null) { diff --git a/modules/avocetBidAdapter.js b/modules/avocetBidAdapter.js index 7a9e5062c0f..1283bb865d4 100644 --- a/modules/avocetBidAdapter.js +++ b/modules/avocetBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { const publisherDomain = config.getConfig('publisherDomain'); // First-party data from config - const fpd = config.getConfig('fpd'); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')); // GDPR status and TCF consent string let tcfConsentString; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index e3a6b9eaa12..41cbb0670c8 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -28,7 +28,7 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [ BANNER, VIDEO, NATIVE ], - /** + /** f * @param {object} bid * @return {boolean} */ @@ -56,10 +56,11 @@ export const spec = { buildRequests: (bidRequests, bidderRequest) => { let url; let data; + let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; Object.assign(bidderRequest, { - publisherExt: config.getConfig('fpd.context'), - userExt: config.getConfig('fpd.user'), + publisherExt: fpd.context, + userExt: fpd.user, ceh: config.getConfig('criteo.ceh') }); @@ -280,8 +281,8 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.zoneId) { slot.zoneid = bidRequest.params.zoneId; } - if (bidRequest.fpd && bidRequest.fpd.context) { - slot.ext = bidRequest.fpd.context; + if (utils.deepAccess(bidRequest, 'ortb2Imp.ext')) { + slot.ext = bidRequest.ortb2Imp.ext; } if (bidRequest.params.ext) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 48b72671d6a..ee2b5406453 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -26,46 +26,48 @@ export const appendGptSlots = adUnits => { if (matchingAdUnitCode) { const adUnit = adUnitMap[matchingAdUnitCode]; - adUnit.fpd = adUnit.fpd || {}; - adUnit.fpd.context = adUnit.fpd.context || {}; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; - const context = adUnit.fpd.context; - context.adServer = context.adServer || {}; - context.adServer.name = 'gam'; - context.adServer.adSlot = slot.getAdUnitPath(); + const context = adUnit.ortb2Imp.ext.data; + context.adserver = context.adserver || {}; + context.adserver.name = 'gam'; + context.adserver.adslot = slot.getAdUnitPath(); } }); }; export const appendPbAdSlot = adUnit => { - adUnit.fpd = adUnit.fpd || {}; - adUnit.fpd.context = adUnit.fpd.context || {}; - const context = adUnit.fpd.context; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; + const context = adUnit.ortb2Imp.ext.data; const { customPbAdSlot } = _currentConfig; if (customPbAdSlot) { - context.pbAdSlot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adServer.adSlot')); + context.pbadslot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adserver.adslot')); return; } // use context.pbAdSlot if set - if (context.pbAdSlot) { + if (context.pbadslot) { return; } // use data attribute 'data-adslotid' if set try { const adUnitCodeDiv = document.getElementById(adUnit.code); if (adUnitCodeDiv.dataset.adslotid) { - context.pbAdSlot = adUnitCodeDiv.dataset.adslotid; + context.pbadslot = adUnitCodeDiv.dataset.adslotid; return; } } catch (e) {} // banner adUnit, use GPT adunit if defined - if (context.adServer) { - context.pbAdSlot = context.adServer.adSlot; + if (utils.deepAccess(context, 'adserver.adslot')) { + context.pbadslot = context.adserver.adslot; return; } - context.pbAdSlot = adUnit.code; + context.pbadslot = adUnit.code; }; export const makeBidRequestsHook = (fn, adUnits, ...args) => { diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 8ab2e2b8a95..964b34dcfa2 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -180,8 +180,8 @@ export const spec = { } const configKeywords = utils.transformBidderParamKeywords({ - 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, - 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + 'user': utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('ortb2.site'), 'keywords') || null }); if (configKeywords.length) { diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js index 7583985b23c..e1edd935587 100755 --- a/modules/inmarBidAdapter.js +++ b/modules/inmarBidAdapter.js @@ -40,7 +40,7 @@ export const spec = { uspConsent: bidderRequest.uspConsent, currencyCode: config.getConfig('currency.adServerCurrency'), coppa: config.getConfig('coppa'), - firstPartyData: config.getConfig('fpd'), + firstPartyData: config.getLegacyFpd(config.getConfig('ortb2')), prebidVersion: '$prebid.version$' }; diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index a29c4ce5fa7..8332e720ae7 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -144,7 +144,7 @@ function enrichBidRequest(bidReqConfig, onDone) { * @param {function} onDone */ export function enrichAdUnits(adUnits) { - const fpdFallback = config.getConfig('fpd.context.data.jwTargeting'); + const fpdFallback = config.getConfig('ortb2.site.ext.data.jwTargeting'); adUnits.forEach(adUnit => { const jwTargeting = extractPublisherParams(adUnit, fpdFallback); if (!jwTargeting || !Object.keys(jwTargeting).length) { @@ -170,7 +170,7 @@ function supportsInstreamVideo(mediaTypes) { export function extractPublisherParams(adUnit, fallback) { let adUnitTargeting; try { - adUnitTargeting = adUnit.fpd.context.data.jwTargeting; + adUnitTargeting = adUnit.ortb2Imp.ext.data.jwTargeting; } catch (e) {} if (!adUnitTargeting && !supportsInstreamVideo(adUnit.mediaTypes)) { diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 4f7fd2ae1a0..29b54f77fbb 100644 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -279,8 +279,9 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } - const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); - const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + const siteData = Object.assign({}, bidRequest.params.inventory, fpd.context); + const userData = Object.assign({}, bidRequest.params.visitor, fpd.user); if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { const bidderData = { @@ -301,7 +302,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); } - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + const pbAdSlot = utils.deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 7cfef6a0784..20cf93caae5 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -336,18 +336,18 @@ function addBidderFirstPartyDataToRequest(request) { const bidderConfig = config.getBidderConfig(); const fpdConfigs = Object.keys(bidderConfig).reduce((acc, bidder) => { const currBidderConfig = bidderConfig[bidder]; - if (currBidderConfig.fpd) { - const fpd = {}; - if (currBidderConfig.fpd.context) { - fpd.site = currBidderConfig.fpd.context; + if (currBidderConfig.ortb2) { + const ortb2 = {}; + if (currBidderConfig.ortb2.site) { + ortb2.site = currBidderConfig.ortb2.site; } - if (currBidderConfig.fpd.user) { - fpd.user = currBidderConfig.fpd.user; + if (currBidderConfig.ortb2.user) { + ortb2.user = currBidderConfig.ortb2.user; } acc.push({ bidders: [ bidder ], - config: { fpd } + config: { ortb2 } }); } return acc; @@ -618,23 +618,27 @@ const OPEN_RTB_PROTOCOL = { const imp = { id: adUnit.code, ext, secure: s2sConfig.secure }; - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - const pbAdSlot = utils.deepAccess(adUnit, 'fpd.context.pbAdSlot'); - if (typeof pbAdSlot === 'string' && pbAdSlot) { - utils.deepSetValue(imp, 'ext.context.data.pbadslot', pbAdSlot); - } - - /** - * Copy GAM AdUnit and Name to imp - */ - ['name', 'adSlot'].forEach(name => { - /** @type {(string|undefined)} */ - const value = utils.deepAccess(adUnit, `fpd.context.adserver.${name}`); - if (typeof value === 'string' && value) { - utils.deepSetValue(imp, `ext.context.data.adserver.${name.toLowerCase()}`, value); + const ortb2 = {...utils.deepAccess(adUnit, 'ortb2Imp.ext.data')}; + Object.keys(ortb2).forEach(prop => { + /** + * Prebid AdSlot + * @type {(string|undefined)} + */ + if (prop === 'pbadslot') { + if (typeof ortb2[prop] === 'string' && ortb2[prop]) utils.deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); + } else if (prop === 'adserver') { + /** + * Copy GAM AdUnit and Name to imp + */ + ['name', 'adslot'].forEach(name => { + /** @type {(string|undefined)} */ + const value = utils.deepAccess(ortb2, `adserver.${name}`); + if (typeof value === 'string' && value) { + utils.deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); + } + }); + } else { + utils.deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); } }); @@ -763,12 +767,12 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'regs.coppa', 1); } - const commonFpd = getConfig('fpd') || {}; - if (commonFpd.context) { - utils.deepSetValue(request, 'site.ext.data', commonFpd.context); + const commonFpd = getConfig('ortb2') || {}; + if (commonFpd.site) { + utils.deepSetValue(request, 'site', commonFpd.site); } if (commonFpd.user) { - utils.deepSetValue(request, 'user.ext.data', commonFpd.user); + utils.deepSetValue(request, 'user', commonFpd.user); } addBidderFirstPartyDataToRequest(request); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 7297c82440f..9905498edee 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -871,21 +871,26 @@ function addVideoParameters(data, bidRequest) { } function applyFPD(bidRequest, mediaType, data) { - const bidFpd = { - user: {...bidRequest.params.visitor}, - context: {...bidRequest.params.inventory} + const BID_FPD = { + user: {ext: {data: {...bidRequest.params.visitor}}}, + site: {ext: {data: {...bidRequest.params.inventory}}} }; - if (bidRequest.params.keywords) bidFpd.context.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; + if (bidRequest.params.keywords) BID_FPD.site.keywords = (utils.isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; - let fpd = utils.mergeDeep({}, config.getConfig('fpd') || {}, bidRequest.fpd || {}, bidFpd); - - const map = {user: {banner: 'tg_v.', code: 'user'}, context: {banner: 'tg_i.', code: 'site'}, adserver: 'dfp_ad_unit_code'}; - let obj = {}; - let impData = {}; - let keywords = []; + let fpd = utils.mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); + let impData = utils.deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; + const MAP = {user: 'tg_v.', site: 'tg_i.', adserver: 'tg_i.dfp_ad_unit_code', pbadslot: 'tg_i.pbadslot', keywords: 'kw'}; const validate = function(prop, key) { - if (typeof prop === 'object' && !Array.isArray(prop)) { + if (key === 'data' && Array.isArray(prop)) { + return prop.filter(name => name.segment && utils.deepAccess(name, 'ext.taxonomyname').match(/iab/i)).map(value => { + let segments = value.segment.filter(obj => obj.id).reduce((result, obj) => { + result.push(obj.id); + return result; + }, []); + if (segments.length > 0) return segments.toString(); + }).toString(); + } else if (typeof prop === 'object' && !Array.isArray(prop)) { utils.logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); } else if (typeof prop !== 'undefined') { return (Array.isArray(prop)) ? prop.filter(value => { @@ -895,53 +900,43 @@ function applyFPD(bidRequest, mediaType, data) { }).toString() : prop.toString(); } }; - - Object.keys(fpd).filter(value => fpd[value] && map[value] && typeof fpd[value] === 'object').forEach((type) => { - obj[map[type].code] = Object.keys(fpd[type]).filter(value => typeof fpd[type][value] !== 'undefined').reduce((result, key) => { - if (key === 'keywords') { - if (!Array.isArray(fpd[type][key]) && mediaType === BANNER) fpd[type][key] = [fpd[type][key]] - - result[key] = fpd[type][key]; - - if (mediaType === BANNER) keywords = keywords.concat(fpd[type][key]); - } else if (key === 'data') { - utils.mergeDeep(result, {ext: {data: fpd[type][key]}}); - } else if (key === 'adServer' || key === 'pbAdSlot') { - (key === 'adServer') ? ['name', 'adSlot'].forEach(name => { - const value = validate(fpd[type][key][name]); - if (value) utils.deepSetValue(impData, `adserver.${name.toLowerCase()}`, value.replace(/^\/+/, '')) - }) : impData[key.toLowerCase()] = fpd[type][key].replace(/^\/+/, '') - } else { - utils.mergeDeep(result, {ext: {data: {[key]: fpd[type][key]}}}); - } - - return result; - }, {}); - - if (mediaType === BANNER) { - let duplicate = (typeof obj[map[type].code].ext === 'object' && obj[map[type].code].ext.data) || {}; - - Object.keys(duplicate).forEach((key) => { - const val = (key === 'adserver') ? duplicate.adserver.adslot : validate(duplicate[key], key); - - if (val) data[(map[key]) ? `${map[type][BANNER]}${map[key]}` : `${map[type][BANNER]}${key}`] = val; - }); - } - }); + const addBannerData = function(obj, name, key) { + let val = validate(obj, key); + let loc = (MAP[key]) ? `${MAP[key]}` : (key === 'data') ? `${MAP[name]}iab` : `${MAP[name]}${key}`; + data[loc] = (data[loc]) ? data[loc].concat(',', val) : val; + } Object.keys(impData).forEach((key) => { - if (mediaType === BANNER) { - (map[key]) ? data[`tg_i.${map[key]}`] = impData[key].adslot : data[`tg_i.${key.toLowerCase()}`] = impData[key]; - } else { - utils.mergeDeep(data.imp[0], {ext: {context: {data: {[key]: impData[key]}}}}); + if (key === 'adserver') { + ['name', 'adslot'].forEach(prop => { + if (impData[key][prop]) impData[key][prop] = impData[key][prop].replace(/^\/+/, ''); + }); + } else if (key === 'pbadslot') { + impData[key] = impData[key].replace(/^\/+/, ''); } }); if (mediaType === BANNER) { - let kw = validate(keywords, 'keywords'); - if (kw) data.kw = kw; + ['site', 'user'].forEach(name => { + Object.keys(fpd[name]).forEach((key) => { + if (key !== 'ext') { + addBannerData(fpd[name][key], name, key); + } else if (fpd[name][key].data) { + Object.keys(fpd[name].ext.data).forEach((key) => { + addBannerData(fpd[name].ext.data[key], name, key); + }); + } + }); + }); + Object.keys(impData).forEach((key) => { + (key === 'adserver') ? addBannerData(impData[key].adslot, name, key) : addBannerData(impData[key], 'site', key); + }); } else { - utils.mergeDeep(data, obj); + if (Object.keys(impData).length) { + utils.mergeDeep(data.imp[0].ext, {data: impData}); + } + + utils.mergeDeep(data, fpd); } } diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 93915689cee..fdb4d2df984 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -98,8 +98,10 @@ const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => { } }; - Object.assign(request.user, config.getConfig('fpd.user')); - Object.assign(request.site, config.getConfig('fpd.context')); + let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + + Object.assign(request.user, fpd.user); + Object.assign(request.site, fpd.context); if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index b681b0980ea..e8d248eea03 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -122,8 +122,8 @@ function _buildPostBody(bidRequests) { } else if (bidRequest.mediaTypes.banner) { imp.banner = { format: _sizes(bidRequest.sizes) }; }; - if (!utils.isEmpty(bidRequest.fpd)) { - imp.fpd = _getAdUnitFpd(bidRequest.fpd); + if (!utils.isEmpty(bidRequest.ortb2Imp)) { + imp.fpd = _getAdUnitFpd(bidRequest.ortb2Imp); } return imp; }); @@ -190,9 +190,10 @@ function _getGlobalFpd() { const fpd = {}; const context = {} const user = {}; + const ortbData = config.getLegacyFpd(config.getConfig('ortb2')) || {}; - const fpdContext = Object.assign({}, config.getConfig('fpd.context')); - const fpdUser = Object.assign({}, config.getConfig('fpd.user')); + const fpdContext = Object.assign({}, ortbData.context); + const fpdUser = Object.assign({}, ortbData.user); _addEntries(context, fpdContext); _addEntries(user, fpdUser); @@ -210,7 +211,7 @@ function _getAdUnitFpd(adUnitFpd) { const fpd = {}; const context = {}; - _addEntries(context, adUnitFpd.context); + _addEntries(context, adUnitFpd.ext); if (!utils.isEmpty(context)) { fpd.context = context; diff --git a/src/adapterManager.js b/src/adapterManager.js index cb84607e130..f7f5d821932 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -68,7 +68,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) } bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'fpd', + 'ortb2Imp', 'mediaType', 'renderer', 'storedAuctionResponse' diff --git a/src/config.js b/src/config.js index daaf739bbbd..51184a8014d 100644 --- a/src/config.js +++ b/src/config.js @@ -324,6 +324,119 @@ export function newConfig() { return bidderConfig; } + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + Object.keys(obj).forEach((type) => { + let prop = (type === 'site') ? 'context' : type; + duplicate[prop] = (prop === 'context' || prop === 'user') ? Object.keys(obj[type]).filter(key => key !== 'data').reduce((result, key) => { + if (key === 'ext') { + utils.mergeDeep(result, obj[type][key]); + } else { + utils.mergeDeep(result, {[key]: obj[type][key]}); + } + + return result; + }, {}) : obj[type]; + }); + + return duplicate; + } + + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyImpFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + if (utils.deepAccess(obj, 'ext.data')) { + Object.keys(obj.ext.data).forEach((key) => { + if (key === 'pbadslot') { + utils.mergeDeep(duplicate, {context: {pbAdSlot: obj.ext.data[key]}}); + } else if (key === 'adserver') { + utils.mergeDeep(duplicate, {context: {adServer: obj.ext.data[key]}}); + } else { + utils.mergeDeep(duplicate, {context: {data: {[key]: obj.ext.data[key]}}}); + } + }); + } + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in config + */ + function convertFpd(opt) { + let duplicate = {}; + + Object.keys(opt).forEach((type) => { + let prop = (type === 'context') ? 'site' : type; + duplicate[prop] = (prop === 'site' || prop === 'user') ? Object.keys(opt[type]).reduce((result, key) => { + if (key === 'data') { + utils.mergeDeep(result, {ext: {data: opt[type][key]}}); + } else { + utils.mergeDeep(result, {[key]: opt[type][key]}); + } + + return result; + }, {}) : opt[type]; + }); + + return duplicate; + } + + /** + * Copy Impression FPD over to OpenRTB standard format in config + * Only accepts bid level context.data values with pbAdSlot and adServer exceptions + */ + function convertImpFpd(opt) { + let duplicate = {}; + + Object.keys(opt).filter(prop => prop === 'context').forEach((type) => { + Object.keys(opt[type]).forEach((key) => { + if (key === 'data') { + utils.mergeDeep(duplicate, {ext: {data: opt[type][key]}}); + } else { + if (typeof opt[type][key] === 'object' && !Array.isArray(opt[type][key])) { + Object.keys(opt[type][key]).forEach(data => { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: {[data.toLowerCase()]: opt[type][key][data]}}}}); + }); + } else { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: opt[type][key]}}}); + } + } + }); + }); + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in each adunit + */ + function convertAdUnitFpd(arr) { + let convert = []; + + arr.forEach((adunit) => { + if (adunit.fpd) { + (adunit['ortb2Imp']) ? utils.mergeDeep(adunit['ortb2Imp'], convertImpFpd(adunit.fpd)) : adunit['ortb2Imp'] = convertImpFpd(adunit.fpd); + convert.push((({ fpd, ...duplicate }) => duplicate)(adunit)); + } else { + convert.push(adunit); + } + }); + + return convert; + } + /* * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function @@ -338,13 +451,14 @@ export function newConfig() { let topicalConfig = {}; topics.forEach(topic => { - let option = options[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(options[topic]) : options[topic]; - if (utils.isPlainObject(defaults[topic]) && utils.isPlainObject(option)) { - option = Object.assign({}, defaults[topic], option); + if (utils.isPlainObject(defaults[prop]) && utils.isPlainObject(option)) { + option = Object.assign({}, defaults[prop], option); } - topicalConfig[topic] = config[topic] = option; + topicalConfig[prop] = config[prop] = option; }); callSubscribers(topicalConfig); @@ -437,11 +551,13 @@ export function newConfig() { bidderConfig[bidder] = {}; } Object.keys(config.config).forEach(topic => { - let option = config.config[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(config.config[topic]) : config.config[topic]; + if (utils.isPlainObject(option)) { - bidderConfig[bidder][topic] = Object.assign({}, bidderConfig[bidder][topic] || {}, option); + bidderConfig[bidder][prop] = Object.assign({}, bidderConfig[bidder][prop] || {}, option); } else { - bidderConfig[bidder][topic] = option; + bidderConfig[bidder][prop] = option; } }); }); @@ -499,7 +615,10 @@ export function newConfig() { runWithBidder, callbackWithBidder, setBidderConfig, - getBidderConfig + getBidderConfig, + convertAdUnitFpd, + getLegacyFpd, + getLegacyImpFpd }; } diff --git a/src/prebid.js b/src/prebid.js index 3452107effd..6565c1610d8 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -595,11 +595,7 @@ $$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - if (utils.isArray(adUnitArr)) { - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, adUnitArr); - } else if (typeof adUnitArr === 'object') { - $$PREBID_GLOBAL$$.adUnits.push(adUnitArr); - } + $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, config.convertAdUnitFpd(utils.isArray(adUnitArr) ? adUnitArr : [adUnitArr])); // emit event events.emit(ADD_AD_UNITS); }; diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 7dd48a13208..baa5b4ac8c4 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -23,6 +23,16 @@ describe('Publisher API _ AdUnits', function () { } ] }, { + fpd: { + context: { + pbAdSlot: 'adSlotTest', + data: { + inventory: [4], + keywords: 'foo,bar', + visitor: [1, 2, 3], + } + } + }, code: '/1996833/slot-2', sizes: [[468, 60]], bids: [ @@ -85,6 +95,7 @@ describe('Publisher API _ AdUnits', function () { it('the second adUnits value should be same with the adUnits that is added by $$PREBID_GLOBAL$$.addAdUnits();', function () { assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.deepEqual(adUnit2['ortb2Imp'], {'ext': {'data': {'pbadslot': 'adSlotTest', 'inventory': [4], 'keywords': 'foo,bar', 'visitor': [1, 2, 3]}}}, 'adUnit2 ortb2Imp'); assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 81ce966efb2..0b8dd6978cf 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -6,6 +6,8 @@ const utils = require('src/utils'); let getConfig; let setConfig; +let getBidderConfig; +let setBidderConfig; let setDefaults; describe('config API', function () { @@ -15,6 +17,8 @@ describe('config API', function () { const config = newConfig(); getConfig = config.getConfig; setConfig = config.setConfig; + getBidderConfig = config.getBidderConfig; + setBidderConfig = config.setBidderConfig; setDefaults = config.setDefaults; logErrorSpy = sinon.spy(utils, 'logError'); logWarnSpy = sinon.spy(utils, 'logWarn'); @@ -57,6 +61,17 @@ describe('config API', function () { expect(getConfig('foo')).to.eql({baz: 'qux'}); }); + it('moves fpd config into ortb2 properties', function () { + setConfig({fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}); + expect(getConfig('ortb2')).to.eql({site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}); + expect(getConfig('fpd')).to.eql(undefined); + }); + + it('moves fpd bidderconfig into ortb2 properties', function () { + setBidderConfig({bidders: ['bidderA'], config: {fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}}); + expect(getBidderConfig()).to.eql({'bidderA': {ortb2: {site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}}}); + }); + it('sets debugging', function () { setConfig({ debug: true }); expect(getConfig('debug')).to.be.true; diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index 11a6a14a353..596f3bade5d 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -224,12 +224,14 @@ describe('AdrelevantisAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0]); sinon .stub(config, 'getConfig') - .withArgs('fpd') + .withArgs('ortb2') .returns({ - context: { + site: { keywords: 'US Open', - data: { - category: 'sports/tennis' + ext: { + data: { + category: 'sports/tennis' + } } } }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 766045b0f3e..0658fe9f33c 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -17,10 +17,26 @@ const embeddedTrackingPixel = `https://1x1.a-mo.net/hbx/g_impression?A=sample&B= const sampleNurl = 'https://example.exchange/nurl'; const sampleFPD = { + site: { + keywords: 'sample keywords', + ext: { + data: { + pageType: 'article' + } + } + }, + user: { + gender: 'O', + yob: 1982, + } +}; + +const legacySampleFPD = { context: { keywords: 'sample keywords', data: { pageType: 'article' + } }, user: { @@ -31,7 +47,7 @@ const sampleFPD = { const stubConfig = (withStub) => { const stub = sinon.stub(config, 'getConfig').callsFake( - (arg) => arg === 'fpd' ? sampleFPD : null + (arg) => arg === 'ortb2' ? sampleFPD : null ) withStub(); @@ -253,7 +269,7 @@ describe('AmxBidAdapter', () => { it('will forward first-party data', () => { stubConfig(() => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.fpd).to.deep.equal(sampleFPD) + expect(data.fpd).to.deep.equal(legacySampleFPD) }); }); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index e91d3b10abb..cad1e3f8114 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -820,14 +820,18 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request with first party data', function () { const contextData = { keywords: ['power tools'], - data: { - pageType: 'article' + ext: { + data: { + pageType: 'article' + } } }; const userData = { gender: 'M', - data: { - registered: true + ext: { + data: { + registered: true + } } }; const bidRequests = [ @@ -842,8 +846,8 @@ describe('The Criteo bidding adapter', function () { bidfloor: 0.75 } }, - fpd: { - context: { + ortb2Imp: { + ext: { data: { someContextAttribute: 'abc' } @@ -854,8 +858,8 @@ describe('The Criteo bidding adapter', function () { sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context: contextData, + ortb2: { + site: contextData, user: userData } }; @@ -863,8 +867,8 @@ describe('The Criteo bidding adapter', function () { }); const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.publisher.ext).to.deep.equal(contextData); - expect(request.data.user.ext).to.deep.equal(userData); + expect(request.data.publisher.ext).to.deep.equal({keywords: ['power tools'], data: {pageType: 'article'}}); + expect(request.data.user.ext).to.deep.equal({gender: 'M', data: {registered: true}}); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 16b84467af2..c4a81c21d5c 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -30,34 +30,34 @@ describe('GPT pre-auction module', () => { '
test2
'; it('should be unchanged if already defined on adUnit', () => { - const adUnit = { fpd: { context: { pbAdSlot: '12345' } } }; + const adUnit = { ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('12345'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('12345'); }); it('should use adUnit.code if matching id exists', () => { - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('bar1'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('bar1'); }); it('should use the gptSlot.adUnitPath if the adUnit.code matches a div id but does not have a data-adslotid', () => { - const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, fpd: { context: { adServer: { name: 'gam', adSlot: '/baz' } } } }; + const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: { adserver: { name: 'gam', adslot: '/baz' } } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('/baz'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('/baz'); }); it('should use the video adUnit.code (which *should* match the configured "adSlotName", but is not being tested) if there is no matching div with "data-adslotid" defined', () => { - const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, fpd: { context: {} } }; + const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: {} } } }; adUnit.code = 'foo5'; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo5'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo5'); }); it('should use the adUnit.code if all other sources failed', () => { - const adUnit = { code: 'foo4', fpd: { context: {} } }; + const adUnit = { code: 'foo4', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo4'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo4'); }); it('should use the customPbAdSlot function if one is given', () => { @@ -67,32 +67,32 @@ describe('GPT pre-auction module', () => { } }); - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('customPbAdSlotName'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('customPbAdSlotName'); }); }); describe('appendGptSlots', () => { it('should not add adServer object to context if no slots defined', () => { - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should not add adServer object to context if no slot matches', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should add adServer object to context if matching slot is found', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'slotCode2', fpd: { context: {} } }; + const adUnit = { code: 'slotCode2', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode2' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode2' }); }); it('should use the customGptSlotMatching function if one is given', () => { @@ -104,10 +104,10 @@ describe('GPT pre-auction module', () => { }); window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'SlOtCoDe1', fpd: { context: {} } }; + const adUnit = { code: 'SlOtCoDe1', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode1' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode1' }); }); }); @@ -164,23 +164,23 @@ describe('GPT pre-auction module', () => { it('should append PB Ad Slot and GPT Slot info to first-party data in each ad unit', () => { const testAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890' } } + ortb2Imp: { ext: { data: { pbadslot: '67890' } } } }, { code: 'slotCode3', }]; const expectedAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890', adServer: { name: 'gam', adSlot: 'slotCode1' } } } + ortb2Imp: { ext: { data: { pbadslot: '67890', adserver: { name: 'gam', adslot: 'slotCode1' } } } } }, { code: 'slotCode3', - fpd: { context: { pbAdSlot: 'slotCode3', adServer: { name: 'gam', adSlot: 'slotCode3' } } } + ortb2Imp: { ext: { data: { pbadslot: 'slotCode3', adserver: { name: 'gam', adslot: 'slotCode3' } } } } }]; window.googletag.pubads().setSlots(testSlots); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index df119bb689d..2e8601bddf6 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -400,6 +400,16 @@ describe('TheMediaGrid Adapter', function () { expect(payload.site.content).to.deep.equal(jsContent); }); + it('should contain the keyword values if it present in ortb2.(site/user)', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user' ? {'keywords': 'foo'} : (arg === 'ortb2.site' ? {'keywords': 'bar'} : null)); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.ext.keywords).to.deep.equal([{'key': 'user', 'value': ['foo']}, {'key': 'context', 'value': ['bar']}]); + getConfigStub.restore(); + }); + it('shold be right tmax when timeout in config is less then timeout in bidderRequest', function() { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'bidderTimeout' ? 2000 : null); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index f7beb6ba486..458e45e8ae7 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -229,8 +229,8 @@ describe('jwplayerRtdProvider', function() { const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: mediaIdWithSegment, @@ -298,8 +298,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -345,8 +345,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -392,8 +392,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -435,19 +435,19 @@ describe('jwplayerRtdProvider', function() { }); it('should include banner ad units that specify jwTargeting', function() { - const adUnit = { mediaTypes: { banner: {} }, fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { mediaTypes: { banner: {} }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); it('should include outstream ad units that specify jwTargeting', function() { - const adUnit = { mediaTypes: { video: { context: 'outstream' } }, fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { mediaTypes: { video: { context: 'outstream' } }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); it('should fallback to config when empty jwTargeting is defined in ad unit', function () { - const adUnit = { fpd: { context: { data: { jwTargeting: {} } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.deep.equal(config); }); @@ -457,7 +457,7 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: 'bad_id', mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); @@ -468,7 +468,7 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: expectedPlayerID, mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); @@ -577,8 +577,8 @@ describe('jwplayerRtdProvider', function() { bidReqConfig = { adUnits: [ { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[0] @@ -591,8 +591,8 @@ describe('jwplayerRtdProvider', function() { ] }, { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[1] @@ -658,8 +658,8 @@ describe('jwplayerRtdProvider', function() { it('sets targeting data in proper structure', function () { const bid = {}; const adUnitWithMediaId = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -690,8 +690,8 @@ describe('jwplayerRtdProvider', function() { const adUnitCode = 'test_ad_unit'; const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -701,7 +701,7 @@ describe('jwplayerRtdProvider', function() { }, bids: [ bid ] }; - const expectedContentId = 'jw_' + adUnit.fpd.context.data.jwTargeting.mediaID; + const expectedContentId = 'jw_' + adUnit.ortb2Imp.ext.data.jwTargeting.mediaID; const expectedTargeting = { content: { id: expectedContentId @@ -732,8 +732,8 @@ describe('jwplayerRtdProvider', function() { const adUnitEmptyfpd = { code: 'test_ad_unit_empty_fpd', - fpd: { - context: { + ortb2Imp: { + ext: { id: 'sthg' } }, diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 9e20629fe45..babee7e10d7 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1607,7 +1607,29 @@ describe('S2S Adapter', function () { const expected = allowedBidders.map(bidder => ({ bidders: [ bidder ], - config: { fpd: { site: context, user } } + config: { + ortb2: { + site: { + content: { userrating: 4 }, + ext: { + data: { + pageType: 'article', + category: 'tools' + } + } + }, + user: { + yob: '1984', + geo: { country: 'ca' }, + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } })); config.setConfig({ fpd: { context: commonContext, user: commonUser } }); @@ -1615,12 +1637,12 @@ describe('S2S Adapter', function () { adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); - expect(parsedRequestBody.site.ext.data).to.deep.equal(commonContext); - expect(parsedRequestBody.user.ext.data).to.deep.equal(commonUser); + expect(parsedRequestBody.site).to.deep.equal(commonContext); + expect(parsedRequestBody.user).to.deep.equal(commonUser); }); describe('pbAdSlot config', function () { - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context\" is undefined', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1630,30 +1652,32 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1662,16 +1686,18 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { + it('should send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a non-empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } }; @@ -1680,13 +1706,13 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.pbadslot'); - expect(parsedRequestBody.imp[0].ext.context.data.pbadslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.pbadslot'); + expect(parsedRequestBody.imp[0].ext.data.pbadslot).to.equal('/a/b/c'); }); }); describe('GAM ad unit config', function () { - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context\" is undefined', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1696,31 +1722,33 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is undefined', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is empty string', function () { + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is empty string', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adServer: { - adSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1730,18 +1758,20 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should send both \"adslot\" and \"name\" from \"imp.ext.context.data.adserver\" if \"fpd.context.adserver.adSlot\" and \"fpd.context.adserver.name\" values are non-empty strings', function () { + it('should send both \"adslot\" and \"name\" from \"imp.ext.data.adserver\" if \"ortb2Imp.ext.data.adserver.adslot\" and \"ortb2Imp.ext.data.adserver.name\" values are non-empty strings', function () { const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adserver: { - adSlot: '/a/b/c', - name: 'adserverName1' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '/a/b/c', + name: 'adserverName1' + } } } }; @@ -1751,10 +1781,10 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.adslot'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.name'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.adslot).to.equal('/a/b/c'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.name).to.equal('adserverName1'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.adslot'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.name'); + expect(parsedRequestBody.imp[0].ext.data.adserver.adslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0].ext.data.adserver.name).to.equal('adserverName1'); }); }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 8c25d97dada..36890a2891b 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -827,27 +827,39 @@ describe('the rubicon adapter', function () { }); it('should merge first party data from getConfig with the bid params, if present', () => { - const context = { + const site = { keywords: 'e,f', rating: '4-star', - data: { - page: 'home' + ext: { + data: { + page: 'home' + } } }; const user = { + data: [{ + 'name': 'www.dataprovider1.com', + 'ext': { 'taxonomyname': 'IAB Audience Taxonomy' }, + 'segment': [ + { 'id': '687' }, + { 'id': '123' } + ] + }], gender: 'M', yob: '1984', geo: {country: 'ca'}, keywords: 'd', - data: { - age: 40 + ext: { + data: { + age: 40 + } } }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -861,8 +873,9 @@ describe('the rubicon adapter', function () { 'tg_v.likes': 'sports,video games', 'tg_v.gender': 'M', 'tg_v.age': '40', + 'tg_v.iab': '687,123', 'tg_v.yob': '1984', - 'tg_i.rating': '5-star', + 'tg_i.rating': '4-star,5-star', 'tg_i.page': 'home', 'tg_i.prodtype': 'tech,mobile', }; @@ -1287,12 +1300,12 @@ describe('the rubicon adapter', function () { describe('Prebid AdSlot', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1300,8 +1313,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1310,10 +1323,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '' + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1324,10 +1339,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: 'abc' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abc' + } } } @@ -1339,12 +1356,14 @@ describe('the rubicon adapter', function () { expect(data['tg_i.pbadslot']).to.equal('abc'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } - }; + } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1358,12 +1377,12 @@ describe('the rubicon adapter', function () { describe('GAM ad unit', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1371,8 +1390,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1381,11 +1400,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: '' + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1397,11 +1418,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'abc' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'abc' + } } } } @@ -1414,11 +1437,13 @@ describe('the rubicon adapter', function () { expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'a/b/c' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'a/b/c' + } } } }; @@ -1871,27 +1896,36 @@ describe('the rubicon adapter', function () { it('should include first party data', () => { createVideoBidderRequest(); - const context = { - data: { - page: 'home' + const site = { + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{foo: 'bar'}] }, keywords: 'e,f', - rating: '4-star' + rating: '4-star', + data: [{foo: 'bar'}] }; const user = { - data: { - age: 31 + ext: { + data: { + age: 31 + } }, keywords: 'd', gender: 'M', yob: '1984', - geo: {country: 'ca'} + geo: {country: 'ca'}, + data: [{foo: 'bar'}] }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -1901,19 +1935,19 @@ describe('the rubicon adapter', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const expected = { - site: Object.assign({}, context, context.data, bidderRequest.bids[0].params.inventory), - user: Object.assign({}, user, user.data, bidderRequest.bids[0].params.visitor) + site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), + user: Object.assign({}, user), + siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), + userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), }; - delete expected.site.data; - delete expected.user.data; - delete expected.site.keywords; - delete expected.user.keywords; + delete request.data.site.page; + delete request.data.site.content.language; expect(request.data.site.keywords).to.deep.equal('a,b,c'); expect(request.data.user.keywords).to.deep.equal('d'); - expect(request.data.site.ext.data).to.deep.equal(expected.site); - expect(request.data.user.ext.data).to.deep.equal(expected.user); + expect(request.data.site.ext.data).to.deep.equal(expected.siteData); + expect(request.data.user.ext.data).to.deep.equal(expected.userData); }); it('should include storedAuctionResponse in video bid request', function () { @@ -1932,29 +1966,33 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); }); - it('should include pbAdSlot in bid request', function () { + it('should include pbadslot in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '1234567890' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '1234567890' + } } - }; + } sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.pbadslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); }); it('should include GAM ad unit in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: '1234567890', - name: 'adServerName1' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '1234567890', + name: 'adServerName1' + } } } }; @@ -1964,8 +2002,8 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.imp[0].ext.context.data.adserver.name).to.equal('adServerName1'); + expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); }); it('should use the integration type provided in the config instead of the default', () => { diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 6af0a855800..1bc77fc9572 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -42,7 +42,7 @@ const ADTYPE_IMG = 'Img'; const ADTYPE_RICHMEDIA = 'Richmedia'; const ADTYPE_VIDEO = 'Video'; -const context = { +const site = { keywords: 'power tools,drills' }; @@ -439,8 +439,8 @@ describe('smaatoBidAdapterTest', () => { it('sends fp data', () => { this.sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index eb410c2525d..30377ec0a5d 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -143,10 +143,10 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, - fpd: { - context: { - pbAdSlot: 'homepage-top-rect', + ortb2Imp: { + ext: { data: { + pbAdSlot: 'homepage-top-rect', adUnitSpecificAttribute: 123 } } @@ -663,11 +663,13 @@ describe('triplelift adapter', function () { const sens = null; const category = ['news', 'weather', 'hurricane']; const pmp_elig = 'true'; - const fpd = { - context: { + const ortb2 = { + site: { pmp_elig: pmp_elig, - data: { - category: category + ext: { + data: { + category: category + } } }, user: { @@ -676,7 +678,7 @@ describe('triplelift adapter', function () { } sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd + ortb2 }; return utils.deepAccess(config, key); }); @@ -688,8 +690,8 @@ describe('triplelift adapter', function () { }); it('should send ad unit fpd if kvps are available', function() { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - expect(request.data.imp[0].fpd.context).to.haveOwnProperty('pbAdSlot'); expect(request.data.imp[0].fpd.context).to.haveOwnProperty('data'); + expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('pbAdSlot'); expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); expect(request.data.imp[1].fpd).to.not.exist; }); From 25459d4682c7fa223d6a1a5a9e941003f30e3442 Mon Sep 17 00:00:00 2001 From: Ryan Schweitzer <50628828+r-schweitzer@users.noreply.github.com> Date: Mon, 8 Mar 2021 11:57:29 +0000 Subject: [PATCH 41/96] PBS Bid Adapter: fix s2s alias collision with built-in adapter aliasing (#6379) * fixed overwriting of aliases for s2s * made change * added fix --- modules/prebidServerBidAdapter/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 20cf93caae5..088b5430f46 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -724,7 +724,7 @@ const OPEN_RTB_PROTOCOL = { } if (!utils.isEmpty(aliases)) { - request.ext.prebid.aliases = aliases; + request.ext.prebid.aliases = {...request.ext.prebid.aliases, ...aliases}; } const bidUserIdAsEids = utils.deepAccess(bidRequests, '0.bids.0.userIdAsEids'); From 1d7ab80054178ea5d3a6b4cce97e425c528d7dfb Mon Sep 17 00:00:00 2001 From: Pierre Turpin Date: Mon, 8 Mar 2021 16:03:08 +0100 Subject: [PATCH 42/96] Clean side-effect when checking that local storage is enabled (#6323) --- src/storageManager.js | 9 +++++-- test/spec/unit/core/storageManager_spec.js | 29 ++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/storageManager.js b/src/storageManager.js index 60e5a7706d0..66a0cf68cbf 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,4 +1,4 @@ -import { hook } from './hook.js'; +import {hook} from './hook.js'; import * as utils from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -110,7 +110,12 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { try { localStorage.setItem('prebid.cookieTest', '1'); return localStorage.getItem('prebid.cookieTest') === '1'; - } catch (error) {} + } catch (error) { + } finally { + try { + localStorage.removeItem('prebid.cookieTest'); + } catch (error) {} + } } return false; } diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index de09df5b196..5bb766217f5 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -46,16 +46,17 @@ describe('storage manager', function() { describe('localstorage forbidden access in 3rd-party context', function() { let errorLogSpy; - const originalLocalStorage = { get: () => window.localStorage }; + let originalLocalStorage; const localStorageMock = { get: () => { throw Error } }; beforeEach(function() { + originalLocalStorage = window.localStorage; Object.defineProperty(window, 'localStorage', localStorageMock); errorLogSpy = sinon.spy(utils, 'logError'); }); afterEach(function() { - Object.defineProperty(window, 'localStorage', originalLocalStorage); + Object.defineProperty(window, 'localStorage', { get: () => originalLocalStorage }); errorLogSpy.restore(); }) @@ -70,4 +71,28 @@ describe('storage manager', function() { sinon.assert.calledThrice(errorLogSpy); }) }) + + describe('localstorage is enabled', function() { + let localStorage; + + beforeEach(function() { + localStorage = window.localStorage; + localStorage.clear(); + }); + + afterEach(function() { + localStorage.clear(); + }) + + it('should remove side-effect after checking', function () { + const storage = getStorageManager(); + + localStorage.setItem('unrelated', 'dummy'); + const val = storage.localStorageIsEnabled(); + + expect(val).to.be.true; + expect(localStorage.length).to.be.eq(1); + expect(localStorage.getItem('unrelated')).to.be.eq('dummy'); + }); + }); }); From 387394d03164895f924f8a93f883bcf77c20e407 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Tue, 9 Mar 2021 05:18:34 -0500 Subject: [PATCH 43/96] updates docs and demo for fpd changes (#6302) Co-authored-by: karimJWP --- .../gpt/jwplayerRtdProvider_example.html | 48 +++++++++---------- modules/jwplayerRtdProvider.md | 12 +++-- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html index 75eb85a2d8c..41c27b70ece 100644 --- a/integrationExamples/gpt/jwplayerRtdProvider_example.html +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -10,32 +10,30 @@ var PREBID_TIMEOUT = 1000; var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - fpd: { - context: { - data: { - jwTargeting: { - // Note: the following Ids are placeholders and should be replaced with your Ids. - playerID: '123', - mediaID: 'abc' + code: 'div-gpt-ad-1460505748561-0', + ortb2Imp: { + ext: { + data: { + jwTargeting: { + // Note: the following Ids are placeholders and should be replaced with your Ids. + playerID: '123', + mediaID: 'abc' + } + } } - }, - } - }, - - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'appnexus', - params: { - placementId: 13144370 - } - }] - + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }] }]; var pbjs = pbjs || {}; diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index ae09277979a..0723e8cbb6c 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -25,14 +25,14 @@ pbjs.setConfig({ } }); ``` -Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `fpd.context.data`: +Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `ortb2Imp.ext.data`: ```javascript const adUnit = { code: '/19968336/prebid_native_example_1', ... - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { // Note: the following Ids are placeholders and should be replaced with your Ids. @@ -52,7 +52,7 @@ pbjs.que.push(function() { }); ``` -**Note**: You may also include `jwTargeting` information in the prebid config's `fpd.context.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. +**Note**: You may also include `jwTargeting` information in the prebid config's `ortb2.site.ext.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. ##Prefetching In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var and set `waitForIt` to `true`: @@ -117,3 +117,7 @@ To view an example: `http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html` **Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. + +#Maintainer info + +Maintained by JW Player. For any questions, comments or feedback please contact Karim Mourra, karim@jwplayer.com From e089f707c3a9fb10db5a24f3f08548238b8a3640 Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Tue, 9 Mar 2021 16:50:53 +0530 Subject: [PATCH 44/96] Changed net revenue to True (#6387) --- modules/pubmaticBidAdapter.js | 2 +- test/spec/modules/pubmaticBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 07e5b62ac26..41ca642c869 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -101,7 +101,7 @@ const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ } ] -const NET_REVENUE = false; +const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', 5: 'PREF', diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 37deb0bca9c..09573fe9f85 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2614,7 +2614,7 @@ describe('PubMatic adapter', function () { } expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); expect(response[0].currency).to.equal('USD'); - expect(response[0].netRevenue).to.equal(false); + expect(response[0].netRevenue).to.equal(true); expect(response[0].ttl).to.equal(300); expect(response[0].meta.networkId).to.equal(123); expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); @@ -2638,7 +2638,7 @@ describe('PubMatic adapter', function () { } expect(response[1].dealId).to.equal(bidResponses.body.seatbid[1].bid[0].dealid); expect(response[1].currency).to.equal('USD'); - expect(response[1].netRevenue).to.equal(false); + expect(response[1].netRevenue).to.equal(true); expect(response[1].ttl).to.equal(300); expect(response[1].meta.networkId).to.equal(422); expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789'); From fe93361f37ed6bacf0e5a4280eb1ce652b3b9b29 Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 9 Mar 2021 16:29:02 +0100 Subject: [PATCH 45/96] adagioBidAdapter: add Native support (#6368) --- modules/adagioBidAdapter.js | 116 +++++++++++++- modules/adagioBidAdapter.md | 60 ++++++- test/spec/modules/adagioBidAdapter_spec.js | 173 ++++++++++++++++++++- 3 files changed, 340 insertions(+), 9 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 6f7feec59c9..892411837dd 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -8,16 +8,16 @@ import sha256 from 'crypto-js/sha256.js'; import { getStorageManager } from '../src/storageManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { createEidsArray } from './userId/eids.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import { OUTSTREAM } from '../src/video.js'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.6.0'; +export const VERSION = '2.7.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; -export const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +export const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; export const GVLID = 617; @@ -687,6 +687,112 @@ function _renderer(bid) { }); } +function _parseNativeBidResponse(bid) { + if (!bid.admNative || !Array.isArray(bid.admNative.assets)) { + utils.logError(`${LOG_PREFIX} Invalid native response`); + return; + } + + const native = {} + + function addAssetDataValue(data) { + const map = { + 1: 'sponsoredBy', // sponsored + 2: 'body', // desc + 3: 'rating', + 4: 'likes', + 5: 'downloads', + 6: 'price', + 7: 'salePrice', + 8: 'phone', + 9: 'address', + 10: 'body2', // desc2 + 11: 'displayUrl', + 12: 'cta' + } + if (map.hasOwnProperty(data.type) && typeof data.value === 'string') { + native[map[data.type]] = data.value; + } + } + + // assets + bid.admNative.assets.forEach(asset => { + if (asset.title) { + native.title = asset.title.text + } else if (asset.data) { + addAssetDataValue(asset.data) + } else if (asset.img) { + switch (asset.img.type) { + case 1: + native.icon = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + default: + native.image = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + } + } + }); + + if (bid.admNative.link) { + if (bid.admNative.link.url) { + native.clickUrl = bid.admNative.link.url; + } + if (Array.isArray(bid.admNative.link.clickTrackers)) { + native.clickTrackers = bid.admNative.link.clickTrackers + } + } + + if (Array.isArray(bid.admNative.eventtrackers)) { + native.impressionTrackers = []; + bid.admNative.eventtrackers.forEach(tracker => { + // Only Impression events are supported. Prebid does not support Viewability events yet. + if (tracker.event !== 1) { + return; + } + + // methods: + // 1: image + // 2: js + // note: javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. + switch (tracker.method) { + case 1: + native.impressionTrackers.push(tracker.url); + break; + case 2: + native.javascriptTrackers = ``; + break; + } + }); + } else { + native.impressionTrackers = Array.isArray(bid.admNative.imptrackers) ? bid.admNative.imptrackers : []; + if (bid.admNative.jstracker) { + native.javascriptTrackers = bid.admNative.jstracker; + } + } + + if (bid.admNative.privacy) { + native.privacyLink = bid.admNative.privacy; + } + + if (bid.admNative.ext) { + native.ext = {} + + if (bid.admNative.ext.bvw) { + native.ext.adagio_bvw = bid.admNative.ext.bvw; + } + } + + bid.native = native +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -873,6 +979,10 @@ export const spec = { } } + if (bidObj.mediaType === NATIVE) { + _parseNativeBidResponse(bidObj); + } + bidObj.site = bidReq.params.site; bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index aa79338d79e..46656d88d37 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -38,8 +38,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value // Optional debug mode, used to get a bid response with expected cpm. debug: { enabled: true, @@ -78,8 +78,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value video: { skip: 0 // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. @@ -91,6 +91,58 @@ Connects to Adagio demand source to fetch bids. } } }] + }, + { + code: 'article_native', + mediaTypes: { + native: { + // generic Prebid options + title: { + required: true, + len: 80 + }, + // … + // Custom Adagio data assets + ext: { + adagio_bvw: { + required: false + } + } + } + }, + bids: [{ + bidder: 'adagio', // Required + params: { + organizationId: '1002', // Required - Organization ID provided by Adagio. + site: 'adagio-io', // Required - Site Name provided by Adagio. + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_native', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. + environment: 'mobile', // Recommended. Environment where the page is displayed. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + postBid: false, // Optional. Use it in case of Post-bid integration only. + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value + // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. + native: { + context: 1, + plcmttype: 2 + }, + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 + } + } + }] } ]; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 0a585caaa1a..999773f1a1f 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,5 +1,5 @@ import find from 'core-js-pure/features/array/find.js'; -import { expect } from 'chai'; +import { expect, util } from 'chai'; import { _features, internal as adagio, @@ -13,7 +13,8 @@ import { } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; -import { config } from 'src/config.js'; +import { config } from '../../../src/config.js'; +import { NATIVE } from '../../../src/mediaTypes.js'; const BidRequestBuilder = function BidRequestBuilder(options) { const defaults = { @@ -879,6 +880,174 @@ describe('Adagio bid adapter', () => { expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) }); }); + + describe('Response with native add', () => { + const serverResponseWithNative = utils.deepClone(serverResponse) + serverResponseWithNative.body.bids[0].mediaType = 'native'; + serverResponseWithNative.body.bids[0].admNative = { + ver: '1.2', + link: { + url: 'https://i.am.a.click.url', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ] + }, + privacy: 'http://www.myprivacyurl.url', + ext: { + bvw: 'test' + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://eventrack.local/impression' + }, + { + event: 1, + method: 2, + url: 'https://eventrack.local/impression' + }, + { + event: 2, + method: 1, + url: 'https://eventrack.local/viewable-mrc50' + } + ], + assets: [ + { + required: 1, + title: { + text: 'My title' + } + }, + { + img: { + url: 'https://images.local/image.jpg', + w: 100, + h: 250 + } + }, + { + img: { + type: 1, + url: 'https://images.local/icon.png', + w: 40, + h: 40 + } + }, + { + data: { + type: 1, // sponsored + value: 'Adagio' + } + }, + { + data: { + type: 2, // desc / body + value: 'The super ad text' + } + }, + { + data: { + type: 3, // rating + value: '10 from 10' + } + }, + { + data: { + type: 11, // displayUrl + value: 'https://i.am.a.display.url' + } + } + ] + }; + + const bidRequestNative = utils.deepClone(bidRequest) + bidRequestNative.mediaTypes = { + native: { + sendTargetingKeys: false, + + clickUrl: { + required: true, + }, + title: { + required: true, + }, + body: { + required: true, + }, + sponsoredBy: { + required: false + }, + image: { + required: true + }, + icon: { + required: true + }, + privacyLink: { + required: false + }, + ext: { + adagio_bvw: {} + } + } + }; + + it('Should ignore native parsing due to missing raw admNative property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + delete alternateServerResponse.body.bids[0].admNative + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should ignore native parsing due to invalid raw admNative.assets property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + alternateServerResponse.body.bids[0].admNative.assets = { title: { text: 'test' } }; + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should handle and return a formated Native ad', () => { + const r = spec.interpretResponse(serverResponseWithNative, bidRequestNative); + const expected = { + displayUrl: 'https://i.am.a.display.url', + sponsoredBy: 'Adagio', + body: 'The super ad text', + rating: '10 from 10', + clickUrl: 'https://i.am.a.click.url', + title: 'My title', + impressionTrackers: [ + 'https://eventrack.local/impression' + ], + javascriptTrackers: '', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ], + image: { + url: 'https://images.local/image.jpg', + width: 100, + height: 250 + }, + icon: { + url: 'https://images.local/icon.png', + width: 40, + height: 40 + }, + ext: { + adagio_bvw: 'test' + }, + privacyLink: 'http://www.myprivacyurl.url' + } + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).ok; + expect(r[0].native).to.deep.equal(expected); + }); + }); }); describe('getUserSyncs()', function() { From 3648cfdafc85b82affb40257ab3bfd9d6c5eabbc Mon Sep 17 00:00:00 2001 From: rtuschkany <35923908+rtuschkany@users.noreply.github.com> Date: Wed, 10 Mar 2021 13:02:42 +0100 Subject: [PATCH 46/96] EIDS Support Update (#6394) --- modules/connectadBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index d1811a1b7d1..4fa2a56a004 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -72,7 +72,7 @@ export const spec = { // EIDS Support if (validBidRequests[0].userId) { - data.user.ext.eids = createEidsArray(validBidRequests[0].userId); + utils.deepSetValue(data, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); } validBidRequests.map(bid => { From d7c0fd7fb40e61ff154dec4a16f50de5e2ce984f Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Wed, 10 Mar 2021 15:03:49 +0300 Subject: [PATCH 47/96] TheMediaGridNM Bid Adapter: fix trouble with alias (#6371) * Added TheMediaGridNM Bid Adapter * Updated required params for TheMediaGridNM Bid Adapter * Update TheMediGridNM Bid Adapter * Fix tests for TheMediaGridNM Bid Adapter * Fixes after review for TheMediaGridNM Bid Adapter * Add support of multi-format in TheMediaGrid Bid Adapter * Update sync url for grid and gridNM Bid Adapters * TheMediaGrid Bid Adapter: added keywords adUnit parameter * Update TheMediaGrid Bid Adapter to support keywords from config * Implement new request format for TheMediaGrid Bid Adapter * Fix jwpseg params for TheMediaGrid Bid Adapter * Update unit tests for The Media Grid Bid Adapter * Fix typo in TheMediaGrid Bid Adapter * Added test for jwTargeting in TheMediaGrid Bid Adapter * The new request format was made by default in TheMediaGrid Bid Adapter * Update userId format in ad request for TheMediaGrid Bid Adapter * Added bidFloor parameter for TheMediaGrid Bid Adapter * Fix for review TheMediaGrid Bid Adapter * Support floorModule in TheMediaGrid Bid Adapter * Fix empty bidfloor for TheMediaGrid Bid Adapter * Some change to restart autotests * Fix userIds format for TheMediaGrid Bid Adapter * Remove digitrust userId from TheMediaGrid Bid Adapter * Protocols was added in video section in ad request for TheMediaGrid Bid Adapter * TheMediaGrid: fix trouble with alias using * TheMediaGridNM: fix trouble with alias --- modules/gridNMBidAdapter.js | 1 - test/spec/modules/gridNMBidAdapter_spec.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index ffd6c1b250c..af1e9f84f43 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -134,7 +134,6 @@ export const spec = { } const bidResponse = { requestId: bid.bidId, - bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index 0dbaac0c526..2aec9713000 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -300,7 +300,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': 11, 'width': 300, 'height': 250, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, @@ -317,7 +316,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, From 8de0498a7ae34e36e5931257e644fca34bd782cc Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 10 Mar 2021 07:28:35 -0800 Subject: [PATCH 48/96] Grab sourceAgnostic IDs first, then fallback to regular IDs (#6400) --- modules/rubiconAnalyticsAdapter.js | 4 +- .../modules/rubiconAnalyticsAdapter_spec.js | 44 ++++++++++++++++--- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 08ae8bf2dd8..f6724ffcc7a 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -482,8 +482,8 @@ function subscribeToGamSlots() { // these come in as `null` from Gpt, which when stringified does not get removed // so set explicitly to undefined when not a number 'advertiserId', advertiserId => utils.isNumber(advertiserId) ? advertiserId : undefined, - 'creativeId', creativeId => utils.isNumber(creativeId) ? creativeId : undefined, - 'lineItemId', lineItemId => utils.isNumber(lineItemId) ? lineItemId : undefined, + 'creativeId', creativeId => utils.isNumber(event.sourceAgnosticCreativeId) ? event.sourceAgnosticCreativeId : utils.isNumber(creativeId) ? creativeId : undefined, + 'lineItemId', lineItemId => utils.isNumber(event.sourceAgnosticLineItemId) ? event.sourceAgnosticLineItemId : utils.isNumber(lineItemId) ? lineItemId : undefined, 'adSlot', () => event.slot.getAdUnitPath(), 'isSlotEmpty', () => event.isEmpty || undefined ]); diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 136f5ef240a..a9c1eeef4de 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1436,8 +1436,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot0, isEmpty: false, advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333 + sourceAgnosticCreativeId: 2222, + sourceAgnosticLineItemId: 3333 } }; @@ -1448,8 +1448,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot1, isEmpty: false, advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666 + sourceAgnosticCreativeId: 5555, + sourceAgnosticLineItemId: 6666 } }; }); @@ -1515,8 +1515,8 @@ describe('rubicon analytics adapter', function () { slot: gptSlot1, isEmpty: false, advertiserId: 0, - creativeId: 0, - lineItemId: 0 + sourceAgnosticCreativeId: 0, + sourceAgnosticLineItemId: 0 } }]); expect(server.requests.length).to.equal(1); @@ -1540,6 +1540,38 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); + it('should pick backup Ids if no sourceAgnostic available first', function () { + performStandardAuction([gptSlotRenderEnded0, { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot1, + isEmpty: false, + advertiserId: 0, + lineItemId: 1234, + creativeId: 5678 + } + }]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 0, + creativeId: 5678, + lineItemId: 1234, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + it('should correctly set adUnit for associated slots', function () { performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); expect(server.requests.length).to.equal(1); From 306d59f3715b5c6c4cd87ea58266cb70851637b8 Mon Sep 17 00:00:00 2001 From: David Reischer Date: Wed, 10 Mar 2021 16:16:18 +0000 Subject: [PATCH 49/96] Permutive - add AC support for TrustX (#6393) --- .../gpt/permutiveRtdProvider_example.html | 19 ++- modules/permutiveRtdProvider.js | 7 + modules/permutiveRtdProvider.md | 1 + .../spec/modules/permutiveRtdProvider_spec.js | 147 ++++++++++++++++-- 4 files changed, 153 insertions(+), 21 deletions(-) diff --git a/integrationExamples/gpt/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html index 0814dcece5b..a06430bcdfa 100644 --- a/integrationExamples/gpt/permutiveRtdProvider_example.html +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -50,7 +50,7 @@ params: { placementId: 13144370, keywords: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -64,7 +64,7 @@ area: ['home'] }, visitor: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -78,12 +78,21 @@ { settings: {}, targeting: { - inline_kvs: ['1', '2', '3', '4'] + test_kv: ['true'] } } ], ozoneData: {} } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } } ] }, @@ -127,13 +136,13 @@ pbjs.setConfig({ debug: true, realTimeData: { - auctionDelay: 50, // maximum time for RTD modules to respond + auctionDelay: 80, // maximum time for RTD modules to respond dataProviders: [ { name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon', 'ozone'], + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], maxSegs: 500, overwrites: { rubicon: function (bid, data, acEnabled, utils, defaultFn) { diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 8ec215d3cca..db431ed45a7 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -110,6 +110,13 @@ function getDefaultBidderFn (bidder) { deepSetValue(bid, 'params.customData.0.targeting.p_standard', data.ac) } + return bid + }, + trustx: function (bid, data, acEnabled) { + if (acEnabled && data.ac && data.ac.length) { + deepSetValue(bid, 'params.keywords.p_standard', data.ac) + } + return bid } } diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 55bdf6420cf..fe8c34c1b5c 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -33,6 +33,7 @@ The below bidders are currently support by the Permutive RTD module. Please reac | Xandr | `appnexus` | Yes | Yes | | Magnite | `rubicon` | Yes | Yes | | Ozone | `ozone` | No | Yes | +| TrustX | `trustx` | No | Yes | * **First-party segments:** When enabling the respective Activation for a segment in Permutive, this module will automatically attach that segment to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in Permutive. Permutive segments will be sent in the `permutive` key-value. diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index d55bbc58056..cf1f3861eab 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -54,7 +54,7 @@ describe('permutiveRtdProvider', function () { }) } }) - it('sets segment targeting for Rubicon', function () { + it('sets segment targeting for Magnite', function () { const data = transformedTargeting() const adUnits = getAdUnits() const config = getConfig() @@ -93,10 +93,29 @@ describe('permutiveRtdProvider', function () { }) } }) + it('sets segment targeting for TrustX', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) }) describe('Custom segment targeting', function () { - it('sets custom segment targeting for Rubicon', function () { + it('sets custom segment targeting for Magnite', function () { const data = transformedTargeting() const adUnits = getAdUnits() const config = getConfig() @@ -129,6 +148,81 @@ describe('permutiveRtdProvider', function () { }) }) + describe('Existing key-value targeting', function () { + it('doesn\'t overwrite existing key-values for Xandr', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Magnite', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Ozone', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for TrustX', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + }) + describe('Permutive on page', function () { it('checks if Permutive is on page', function () { expect(isPermutiveOnPage()).to.equal(false) @@ -168,7 +262,7 @@ function getConfig () { name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon', 'ozone'], + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], maxSegs: 500 } } @@ -197,15 +291,20 @@ function getTargetingData () { } function getAdUnits () { + const div_1_sizes = [ + [300, 250], + [300, 600] + ] + const div_2_sizes = [ + [728, 90], + [970, 250] + ] return [ { code: '/19968336/header-bid-tag-0', mediaTypes: { banner: { - sizes: [ - [300, 250], - [300, 600] - ] + sizes: div_1_sizes } }, bids: [ @@ -214,7 +313,7 @@ function getAdUnits () { params: { placementId: 13144370, keywords: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -228,7 +327,7 @@ function getAdUnits () { area: ['home'] }, visitor: { - inline_kvs: ['1'] + test_kv: ['true'] } } }, @@ -242,12 +341,21 @@ function getAdUnits () { { settings: {}, targeting: { - inline_kvs: ['1', '2', '3', '4'] + test_kv: ['true'] } } ], ozoneData: {} } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } } ] }, @@ -255,17 +363,17 @@ function getAdUnits () { code: '/19968336/header-bid-tag-1', mediaTypes: { banner: { - sizes: [ - [728, 90], - [970, 250] - ] + sizes: div_2_sizes } }, bids: [ { bidder: 'appnexus', params: { - placementId: 13144370 + placementId: 13144370, + keywords: { + test_kv: ['true'] + } } }, { @@ -273,7 +381,14 @@ function getAdUnits () { params: { publisherId: 'OZONEGMG0001', siteId: '4204204209', - placementId: '0420420500' + placementId: '0420420500', + customData: [ + { + targeting: { + test_kv: ['true'] + } + } + ] } } ] From 261d1b355a35f0e9270261495c77bad066faca35 Mon Sep 17 00:00:00 2001 From: evanmsmrtb Date: Wed, 10 Mar 2021 12:56:49 -0600 Subject: [PATCH 50/96] SmartRTB Bid Adapter: add alias and update valid opts (#6365) * Add alias, update valid opts * Update bidder tests --- modules/smartrtbBidAdapter.js | 6 ++---- test/spec/modules/smartrtbBidAdapter_spec.js | 7 ++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/modules/smartrtbBidAdapter.js b/modules/smartrtbBidAdapter.js index 12d5a0ae7da..de303f9e4b2 100644 --- a/modules/smartrtbBidAdapter.js +++ b/modules/smartrtbBidAdapter.js @@ -17,11 +17,9 @@ function getDomain () { export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ 'banner', 'video' ], - aliases: ['smrtb'], + aliases: ['rdigital'], isBidRequestValid: function(bid) { - return (bid.params.pubId !== null && - bid.params.medId !== null && - bid.params.zoneId !== null); + return (bid.params.pubId !== null || bid.params.zoneId !== null); }, buildRequests: function(validBidRequests, bidderRequest) { let stack = (bidderRequest.refererInfo && diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/smartrtbBidAdapter_spec.js index cb5ceee0870..a7f30bdec6e 100644 --- a/test/spec/modules/smartrtbBidAdapter_spec.js +++ b/test/spec/modules/smartrtbBidAdapter_spec.js @@ -69,9 +69,6 @@ describe('SmartRTBBidAdapter', function () { it('should return a bidder code of smartrtb', function () { expect(spec.code).to.equal('smartrtb') }) - it('should alias smrtb', function () { - expect(spec.aliases.length > 0 && spec.aliases[0] === 'smrtb').to.be.true - }) }) describe('isBidRequestValid', function () { @@ -79,8 +76,8 @@ describe('SmartRTBBidAdapter', function () { expect(spec.isBidRequestValid(bannerRequest)).to.be.true }) - it('should return false if any zone id missing', function () { - expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { zoneId: null } }))).to.be.false + it('should return false if any zone id and pub id missing', function () { + expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { pubId: null, zoneId: null } }))).to.be.false }) }) From 26f52aca4a27fa7719b1f1ef38a7519744c23cf3 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 10 Mar 2021 13:17:06 -0800 Subject: [PATCH 51/96] Prebid 4.30.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dfd8a60b06d..befc6c79fbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.30.0-pre", + "version": "4.30.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f5fb64002d9c033b22af1087212cec53ff19ddd4 Mon Sep 17 00:00:00 2001 From: robertrmartinez Date: Wed, 10 Mar 2021 13:36:31 -0800 Subject: [PATCH 52/96] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index befc6c79fbd..7943e00f3c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.30.0", + "version": "4.31.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From c0073f5398e335cfa999a364cdf9b25cb0e3d166 Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Thu, 11 Mar 2021 04:00:03 -0500 Subject: [PATCH 53/96] Sonobi Bid Adapter: send eids in bid request. (#6364) * unwrapping id5id uid. Added new eid param for user id modules * set userid to new variable * fixed spelling mistake in unit test Co-authored-by: Scott Menzer * copying userid object so the referenced object does not get updated. * using deepClone instead of spreading the top userId object Co-authored-by: Scott Menzer --- modules/sonobiBidAdapter.js | 16 +++- test/spec/modules/sonobiBidAdapter_spec.js | 85 +++++++++++++++++++++- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index a55992cec22..0e4bfb37829 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,10 +1,9 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, deepClone } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { userSync } from '../src/userSync.js'; - const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); @@ -117,7 +116,18 @@ export const spec = { payload.schain = JSON.stringify(validBidRequests[0].schain) } if (deepAccess(validBidRequests[0], 'userId') && Object.keys(validBidRequests[0].userId).length > 0) { - payload.userid = JSON.stringify(validBidRequests[0].userId); + const userIds = deepClone(validBidRequests[0].userId); + + if (userIds.id5id) { + userIds.id5id = deepAccess(userIds, 'id5id.uid'); + } + + payload.userid = JSON.stringify(userIds); + } + + const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); + if (Array.isArray(eids) && eids.length > 0) { + payload.eids = JSON.stringify(eids); } let keywords = validBidRequests[0].params.keywords; // a CSV of keywords diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 52821072a21..d1ac200394c 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -414,15 +414,92 @@ describe('SonobiBidAdapter', function () { expect(JSON.parse(bidRequests.data.schain)).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids as a JSON-encoded set of eids', function () { + bidRequest[0].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + bidRequest[1].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); + expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.data.ref).not.to.be.empty; + expect(bidRequests.data.s).not.to.be.empty; + expect(JSON.parse(bidRequests.data.eids)).to.eql([ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]); + }); + it('should return a properly formatted request with userid as a JSON-encoded set of User ID results', function () { - bidRequest[0].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; - bidRequest[1].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; + bidRequest[0].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': {'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': {'linkType': 2}}}; + bidRequest[1].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': {'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': {'linkType': 2}}}; const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); expect(bidRequests.method).to.equal('GET'); expect(bidRequests.data.ref).not.to.be.empty; expect(bidRequests.data.s).not.to.be.empty; - expect(JSON.parse(bidRequests.data.userid)).to.eql({'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}); + expect(JSON.parse(bidRequests.data.userid)).to.eql({'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ'}); }); it('should return a properly formatted request with userid omitted if there are no userIds', function () { @@ -469,7 +546,7 @@ describe('SonobiBidAdapter', function () { ]; const bidRequests = spec.buildRequests(bRequest, bidderRequests); expect(bidRequests.url).to.equal('https://iad-2-apex.go.sonobi.com/trinity.json'); - }) + }); }); describe('.interpretResponse', function () { From e2db9502d0cc9c1d6cf27bfdd73310f4ff1c0216 Mon Sep 17 00:00:00 2001 From: thuyhq <61451682+thuyhq@users.noreply.github.com> Date: Fri, 12 Mar 2021 16:48:38 +0700 Subject: [PATCH 54/96] Apacdex Bid Adapter: userId module support, show demo ads in debug mode & other maintenance (#6378) * Upgrade and maintenance apacdexBidAdapter * fix error and add unit test --- modules/apacdexBidAdapter.js | 145 ++++++++++++++++---- test/spec/modules/apacdexBidAdapter_spec.js | 101 +++++++++++++- 2 files changed, 221 insertions(+), 25 deletions(-) diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index 2582e4788c1..62ae3f54125 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,4 +1,5 @@ import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'apacdex'; const CONFIG = { @@ -49,11 +50,34 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { + let siteId; + let schain; + let eids; + let geo; + let test; + var bids = JSON.parse(JSON.stringify(validBidRequests)) bidderConfig = CONFIG[bids[0].bidder]; - const payload = {}; + + test = config.getConfig('debug'); bids.forEach(bidReq => { + siteId = siteId || bidReq.params.siteId; + + if (bidReq.schain) { + schain = schain || bidReq.schain + } + + if (bidReq.userIdAsEids) { + eids = eids || bidReq.userIdAsEids + } + + if (bidReq.params && bidReq.params.geo) { + if (validateGeoObject(bidReq.params.geo)) { + geo = bidReq.params.geo; + } + } + var targetKey = 0; if (bySlotTargetKey[bidReq.adUnitCode] != undefined) { targetKey = bySlotTargetKey[bidReq.adUnitCode]; @@ -73,36 +97,55 @@ export const spec = { bidReq.targetKey = targetKey; }); + const payload = {}; + payload.tmax = bidderRequest.timeout; + if (test) { + payload.test = 1; + } + payload.device = {}; payload.device.ua = navigator.userAgent; - payload.device.height = window.top.innerHeight; - payload.device.width = window.top.innerWidth; + payload.device.height = window.screen.width; + payload.device.width = window.screen.height; payload.device.dnt = _getDoNotTrack(); payload.device.language = navigator.language; + var pageUrl = _extractTopWindowUrlFromBidderRequest(bidderRequest); payload.site = {}; - payload.site.id = bids[0].params.siteId; - payload.site.page = _extractTopWindowUrlFromBidderRequest(bidderRequest); + payload.site.id = siteId; + payload.site.page = pageUrl payload.site.referrer = _extractTopWindowReferrerFromBidderRequest(bidderRequest); - payload.site.hostname = window.top.location.hostname; + payload.site.hostname = getDomain(pageUrl); // Apply GDPR parameters to request. - payload.gdpr = {}; if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr = {}; payload.gdpr.gdprApplies = !!bidderRequest.gdprConsent.gdprApplies; if (bidderRequest.gdprConsent.consentString) { payload.gdpr.consentString = bidderRequest.gdprConsent.consentString; } } - // Apply schain. - if (bids[0].schain) { - payload.schain = bids[0].schain - } + // Apply us_privacy. if (bidderRequest && bidderRequest.uspConsent) { payload.us_privacy = bidderRequest.uspConsent; } + // Apply schain. + if (schain) { + payload.schain = schain + } + + // Apply eids. + if (eids) { + payload.eids = eids + } + + // Apply geo + if (geo) { + payload.geo = geo; + } + payload.bids = bids; return { @@ -115,12 +158,12 @@ export const spec = { }, interpretResponse: function (serverResponse, bidRequest) { const serverBody = serverResponse.body; - const serverBids = serverBody.bids; - // check overall response - if (!serverBody || typeof serverBody !== 'object') { + if (!serverBody || !utils.isPlainObject(serverBody)) { return []; } - if (!serverBids || typeof serverBids !== 'object') { + + const serverBids = serverBody.bids; + if (!serverBids || !utils.isArray(serverBids)) { return []; } @@ -192,15 +235,25 @@ function _getBiggestSize(sizes) { } function _getDoNotTrack() { - if (window.top.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack) { - if (window.top.doNotTrack == '1' || navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') { + try { + if (window.top.doNotTrack && window.top.doNotTrack == '1') { return 1; - } else { - return 0; } - } else { - return 0; - } + } catch (e) { } + + try { + if (navigator.doNotTrack && (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1')) { + return 1; + } + } catch (e) { } + + try { + if (navigator.msDoNotTrack && navigator.msDoNotTrack == '1') { + return 1; + } + } catch (e) { } + + return 0 } /** @@ -210,8 +263,11 @@ function _getDoNotTrack() { * @returns {string} */ function _extractTopWindowUrlFromBidderRequest(bidderRequest) { - if (bidderRequest && utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl')) { - return bidderRequest.refererInfo.canonicalUrl; + if (config.getConfig('pageUrl')) { + return config.getConfig('pageUrl'); + } + if (utils.deepAccess(bidderRequest, 'refererInfo.referer')) { + return bidderRequest.refererInfo.referer; } try { @@ -239,4 +295,45 @@ function _extractTopWindowReferrerFromBidderRequest(bidderRequest) { } } +/** + * Extracts the domain from given page url + * + * @param {string} url + * @returns {string} + */ +export function getDomain(pageUrl) { + if (config.getConfig('publisherDomain')) { + var publisherDomain = config.getConfig('publisherDomain'); + return publisherDomain.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; + } + + if (!pageUrl) { + return pageUrl; + } + + return pageUrl.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; +} + +/** + * Validate geo object + * + * @param {Object} geo + * @returns {boolean} + */ +export function validateGeoObject(geo) { + if (!utils.isPlainObject(geo)) { + return false; + } + if (!geo.lat) { + return false; + } + if (!geo.lon) { + return false; + } + if (!geo.accuracy) { + return false; + } + return true; +} + registerBidder(spec); diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index da9a050a8de..c3d0d025c0c 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -1,7 +1,8 @@ import { expect } from 'chai' -import { spec } from 'modules/apacdexBidAdapter.js' +import { spec, validateGeoObject, getDomain } from '../../../modules/apacdexBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' import { userSync } from '../../../src/userSync.js'; +import { config } from 'src/config.js'; describe('ApacdexBidAdapter', function () { const adapter = newBidder(spec) @@ -199,11 +200,34 @@ describe('ApacdexBidAdapter', function () { 'bidder': 'apacdex', 'params': { 'siteId': '1a2b3c4d5e6f1a2b3c4d', + 'geo': {'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60} }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'targetKey': 0, 'bidId': '30b31c1838de1f', + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'p0cCLF9JazY1ZUFjazJRb3NKbEprVTcwZ0IwRUlGalBjOG9laUZNbFJ0ZGpOSnVFbE9VMjBNMzNBTzladGt4cUVGQzBybDY2Y1FqT1dkUkFsMmJIWDRHNjlvNXJjbiUyQlZDd1dOTmt6VlV2TDhRd0F0RTlBcmpyZU5WRHBPU25GQXpyMnlT', + 'atype': 1 + }] + }, { + 'source': 'pubcid.org', + 'uids': [{ + 'id': '2ae366c2-2576-45e5-bd21-72ed10598f17', + 'atype': 1 + }] + }, { + 'source': 'sharedid.org', + 'uids': [{ + 'id': '01EZXQDVAPER4KE1VBS29XKV4Z', + 'atype': 1, + 'ext': { + 'third': '01EZXQDVAPER4KE1VBS29XKV4Z' + } + }] + }], }, { 'bidder': 'apacdex', @@ -300,10 +324,23 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.schain).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) + }); + it('should return a properly formatted request with geo defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.geo).to.deep.equal(bidRequest[0].params.geo) + }); it('should return a properly formatted request with us_privacy included', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); }); + it('should return a properly formatted request with pbjs_debug is true', function () { + config.setConfig({debug: true}); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.test).to.equal(1) + }); }); describe('.interpretResponse', function () { @@ -601,4 +638,66 @@ describe('ApacdexBidAdapter', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, [])).to.have.length(0); }); }); + + describe('validateGeoObject', function () { + it('should return true if the geo object is valid', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(true); + }); + + it('should return false if the geo object is not plain object', () => { + let geoObject = [{ + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }]; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lat attribute', () => { + let geoObject = { + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lon attribute', () => { + let geoObject = { + lat: 123.5624234, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing accuracy attribute', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + }); + + describe('getDomain', function () { + it('should return valid domain from publisherDomain config', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: pageUrl}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return valid domain from pageUrl argument', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return undefined if pageUrl and publisherDomain not config', () => { + let pageUrl; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal(pageUrl); + }); + }); }); From 1a47cd3c78b79e8717b5221b5a4e2957de270365 Mon Sep 17 00:00:00 2001 From: novatiq <79258366+novatiq@users.noreply.github.com> Date: Fri, 12 Mar 2021 16:01:35 +0200 Subject: [PATCH 55/96] Novatiq ID System: add snowflake userId submodule (#6350) * Novatiq snowflake userId submodule Novatiq snowflake userId submodule initial release * change request updates added novatiq info /modules/userId/userId.md added novatiq info /modules/userId/eids.md added novatiq eids /modules/userId/eids.js added novatiq module in /modules/.submodules.json removed unnecessary value from getId response * Update novatiqIdSystem_spec.js removed unnecessary srcid value * Update novatiqIdSystem.md Novatiq ID System: updated novatiq snowflake ID description --- modules/.submodules.json | 3 +- modules/novatiqIdSystem.js | 88 +++++++++++++++++++++++ modules/novatiqIdSystem.md | 36 ++++++++++ modules/userId/eids.js | 9 +++ modules/userId/eids.md | 7 ++ modules/userId/userId.md | 4 ++ test/spec/modules/novatiqIdSystem_spec.js | 70 ++++++++++++++++++ 7 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 modules/novatiqIdSystem.js create mode 100644 modules/novatiqIdSystem.md create mode 100644 test/spec/modules/novatiqIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index fc69dd276a3..a7cf1f54426 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -20,7 +20,8 @@ "fabrickIdSystem", "verizonMediaIdSystem", "pubProvidedIdSystem", - "tapadIdSystem" + "tapadIdSystem", + "novatiqIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js new file mode 100644 index 00000000000..fbfa6ca8abc --- /dev/null +++ b/modules/novatiqIdSystem.js @@ -0,0 +1,88 @@ +/** + * This module adds novatiqId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/novatiqIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; + +/** @type {Submodule} */ +export const novatiqIdSubmodule = { + +/** +* used to link submodule with config +* @type {string} +*/ + name: 'novatiq', + + /** +* decode the stored id value for passing to bid requests +* @function +* @returns {novatiq: {snowflake: string}} +*/ + decode(novatiqId, config) { + let responseObj = { + novatiq: { + snowflake: novatiqId + } + }; + return responseObj; + }, + + /** +* performs action to obtain id and return a value in the callback's response argument +* @function +* @param {SubmoduleConfig} config +* @returns {id: string} +*/ + getId(config) { + function snowflakeId(placeholder) { + return placeholder + ? (placeholder ^ Math.random() * 16 >> placeholder / 4).toString(16) + : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11 + 1e3).replace(/[018]/g, snowflakeId); + } + + const configParams = config.params || {}; + const srcId = this.getSrcId(configParams); + utils.logInfo('NOVATIQ Sync request used sourceid param: ' + srcId); + + let partnerhost; + partnerhost = window.location.hostname; + utils.logInfo('NOVATIQ partner hostname: ' + partnerhost); + + const novatiqId = snowflakeId(); + const url = 'https://spadsync.com/sync?sptoken=' + novatiqId + '&sspid=' + srcId + '&ssphost=' + partnerhost; + ajax(url, undefined, undefined, { method: 'GET', withCredentials: false }); + + utils.logInfo('NOVATIQ snowflake: ' + novatiqId); + return { 'id': novatiqId } + }, + + getSrcId(configParams) { + utils.logInfo('NOVATIQ Configured sourceid param: ' + configParams.sourceid); + + function isHex(str) { + var a = parseInt(str, 16); + return (a.toString(16) === str) + } + + let srcId; + if (typeof configParams.sourceid === 'undefined' || configParams.sourceid === null || configParams.sourceid === '') { + srcId = '000'; + utils.logInfo('NOVATIQ sourceid param set to value 000 due to undefined parameter or missing value in config section'); + } else if (configParams.sourceid.length < 3 || configParams.sourceid.length > 3) { + srcId = '001'; + utils.logInfo('NOVATIQ sourceid param set to value 001 due to wrong size in config section 3 chars max e.g. 1ab'); + } else if (isHex(configParams.sourceid) == false) { + srcId = '002'; + utils.logInfo('NOVATIQ sourceid param set to value 002 due to wrong format in config section expecting hex value only'); + } else { + srcId = configParams.sourceid; + } + return srcId + } +}; +submodule('userId', novatiqIdSubmodule); diff --git a/modules/novatiqIdSystem.md b/modules/novatiqIdSystem.md new file mode 100644 index 00000000000..ce561a696e3 --- /dev/null +++ b/modules/novatiqIdSystem.md @@ -0,0 +1,36 @@ +# Novatiq Snowflake ID + +Novatiq proprietary dynamic snowflake ID is a unique, non sequential and single use identifier for marketing activation. Our in network solution matches verification requests to telco network IDs, safely and securely inside telecom infrastructure. Novatiq snowflake ID can be used for identity validation and as a secured 1st party data delivery mechanism. + +## Novatiq Snowflake ID Configuration + +Enable by adding the Novatiq submodule to your Prebid.js package with: + +``` +gulp build --modules=novatiqIdSystem,userId +``` + +Module activation and configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'novatiq', + params: { + sourceid '1a3', // change to the Partner Number you received from Novatiq + } + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | Module identification: `"novatiq"` | `"novatiq"` | +| params | Required | Object | Configuration specifications for the Novatiq module. | | +| params.sourceid | Required | String | This is the Novatiq Partner Number obtained via Novatiq registration. | `1a3` | + +If you have any questions, please reach out to us at prebid@novatiq.com. diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 80750ccaae8..a7e5eaf6061 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -172,6 +172,15 @@ const USER_IDS_CONFIG = { 'tapadId': { source: 'tapad.com', atype: 1 + }, + + // Novatiq Snowflake + 'novatiq': { + getValue: function(data) { + return data.snowflake + }, + source: 'novatiq.com', + atype: 1 } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index b69c4b9bd5e..404066d53e4 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -148,6 +148,13 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 1 }] + }, + { + source: 'novatiq.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] } ] ``` diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 267b3a60cea..a7f98fb39a0 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -182,6 +182,10 @@ pbjs.setConfig({ { name: "criteo", value: { "criteoId": "wK-fkF8zaEIlMkZMbHl3eFo4NEtoNmZaeXJtYkFjZlVuWjBhcjJMaTRYd3pZNSUyQnlKRHNGRXlpdzdjd3pjVzhjcSUyQmY4eTFzN3VSZjV1ZyUyRlA0U2ZiR0UwN2I4bDZRJTNEJTNE" } + }, + { + name: "novatiq", + value: { "snowflake": "81b001ec-8914-488c-a96e-8c220d4ee08895ef" } }], syncDelay: 5000 } diff --git a/test/spec/modules/novatiqIdSystem_spec.js b/test/spec/modules/novatiqIdSystem_spec.js new file mode 100644 index 00000000000..60c82626450 --- /dev/null +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -0,0 +1,70 @@ +import { novatiqIdSubmodule } from 'modules/novatiqIdSystem.js'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('novatiqIdSystem', function () { + describe('getSrcId', function() { + it('getSrcId should set srcId value to 000 due to undefined parameter in config section', function() { + const config = { params: { } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set srcId value to 000 due to missing value in config section', function() { + const config = { params: { sourceid: '' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 000 due to null value in config section', function() { + const config = { params: { sourceid: null } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 001 due to wrong length in config section max 3 chars', function() { + const config = { params: { sourceid: '1234' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('001'); + }); + + it('getSrcId should set value to 002 due to wrong format in config section', function() { + const config = { params: { sourceid: '1xc' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('002'); + }); + }); + + describe('getId', function() { + it('should log message if novatiqId has wrong format', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).to.have.length(40); + }); + + it('should log message if novatiqId not provided', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).should.be.not.empty; + }); + }); + + describe('decode', function() { + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).to.have.length(40); + }); + + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).should.be.not.empty; + }); + }); +}) From 0e7cbea03811a363b3f621e5ce6b4026e5b4a154 Mon Sep 17 00:00:00 2001 From: lasloche <62240785+lasloche@users.noreply.github.com> Date: Mon, 15 Mar 2021 16:24:53 +0200 Subject: [PATCH 56/96] Rise Bid Adapter: add session_id & is_wrapper params to adapter (#6407) * add new params to rise adapter * add unit tests for isWrapper and sessionId adapter params --- modules/riseBidAdapter.js | 3 ++- test/spec/modules/riseBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index a891e0aa883..e3265ad5d3e 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -211,7 +211,8 @@ function generateParameters(bid, bidderRequest) { bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: utils.getBidIdParameter('auctionId', bid), + session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), + is_wrapper: !!params.isWrapper, publisher_name: domain, site_domain: domain, bidder_version: BIDDER_VERSION diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 176437c4f27..b3257cbda9d 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -75,6 +75,8 @@ describe('riseAdapter', function () { bidderCode: 'rise', } + const customSessionId = '12345678'; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { @@ -83,6 +85,22 @@ describe('riseAdapter', function () { } }); + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + it('sends bid request to test ENDPOINT via GET', function () { const requests = spec.buildRequests(testModeBidRequests, bidderRequest); for (const request of requests) { From ca646a8ba66d50cb92ac74814415d1c4316a5ba4 Mon Sep 17 00:00:00 2001 From: lasloche <62240785+lasloche@users.noreply.github.com> Date: Mon, 15 Mar 2021 16:25:27 +0200 Subject: [PATCH 57/96] IronSource Bid Adapter: add session_id & is_wrapper params to adapter (#6408) * add new params * add unit tests for isWrapper and sessionId adapter params --- modules/ironsourceBidAdapter.js | 3 ++- test/spec/modules/ironsourceBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/ironsourceBidAdapter.js b/modules/ironsourceBidAdapter.js index ba510e86e7f..5b8531d7a85 100644 --- a/modules/ironsourceBidAdapter.js +++ b/modules/ironsourceBidAdapter.js @@ -211,7 +211,8 @@ function generateParameters(bid, bidderRequest) { bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: utils.getBidIdParameter('auctionId', bid), + session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), + is_wrapper: !!params.isWrapper, publisher_name: domain, site_domain: domain, bidder_version: BIDDER_VERSION diff --git a/test/spec/modules/ironsourceBidAdapter_spec.js b/test/spec/modules/ironsourceBidAdapter_spec.js index 93c3a6fb7b9..cca928ff28b 100644 --- a/test/spec/modules/ironsourceBidAdapter_spec.js +++ b/test/spec/modules/ironsourceBidAdapter_spec.js @@ -75,6 +75,8 @@ describe('ironsourceAdapter', function () { bidderCode: 'ironsource', } + const customSessionId = '12345678'; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { @@ -98,6 +100,22 @@ describe('ironsourceAdapter', function () { } }); + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + it('should send the correct width and height', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { From e51d1faab9c6bd5c4903edc12ddca2befcd233ef Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Mon, 15 Mar 2021 14:29:25 -0400 Subject: [PATCH 58/96] AMX Bid Adapter: add or update general adapter support and code refactoring (#6403) * AMX Bid adapter improvements * fix eslint issues (breaking CI) --- modules/amxBidAdapter.js | 115 +++++++++++------------- test/spec/modules/amxBidAdapter_spec.js | 87 ++++++++++++------ 2 files changed, 114 insertions(+), 88 deletions(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 497c2142b9b..5794b52dce0 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { parseUrl, deepAccess, _each, formatQS, getUniqueIdentifierStr, triggerPixel } from '../src/utils.js'; +import { parseUrl, deepAccess, _each, formatQS, getUniqueIdentifierStr, triggerPixel, isFn, logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -9,7 +9,6 @@ const storage = getStorageManager(737, BIDDER_CODE); const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; const VERSION = 'pba1.2.1'; -const xmlDTDRxp = /^\s*<\?xml[^\?]+\?>/; const VAST_RXP = /^\s*<\??(?:vast|xml)/i; const TRACKING_ENDPOINT = 'https://1x1.a-mo.net/hbx/'; const AMUID_KEY = '__amuidpb'; @@ -45,11 +44,16 @@ function flatMap(input, mapFn) { .reduce((acc, item) => item != null && acc.concat(item), []) } -const generateDTD = (xmlDocument) => - ``; - const isVideoADM = (html) => html != null && VAST_RXP.test(html); -const getMediaType = (bid) => isVideoADM(bid.adm) ? VIDEO : BANNER; + +function getMediaType(bid) { + if (isVideoADM(bid.adm)) { + return VIDEO; + } + + return BANNER; +} + const nullOrType = (value, type) => value == null || (typeof value) === type // eslint-disable-line valid-typeof @@ -103,6 +107,32 @@ const trackEvent = (eventName, data) => eid: getUniqueIdentifierStr(), })}`); +const DEFAULT_MIN_FLOOR = 0; + +function ensureFloor(floorValue) { + return typeof floorValue === 'number' && isFinite(floorValue) && floorValue > 0.0 + ? floorValue : DEFAULT_MIN_FLOOR; +} + +function getFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.floor', DEFAULT_MIN_FLOOR); + } + + try { + const floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + bidRequest: bid + }); + return floor.floor; + } catch (e) { + logError('call to getFloor failed: ', e); + return DEFAULT_MIN_FLOOR; + } +} + function convertRequest(bid) { const size = largestSize(bid.sizes, bid.mediaTypes) || [0, 0]; const isVideoBid = bid.mediaType === VIDEO || VIDEO in bid.mediaTypes @@ -116,16 +146,21 @@ function convertRequest(bid) { bid.sizes, deepAccess(bid, `mediaTypes.${BANNER}.sizes`, []) || [], deepAccess(bid, `mediaTypes.${VIDEO}.sizes`, []) || [], - ] + ]; + + const videoData = deepAccess(bid, `mediaTypes.${VIDEO}`, {}) || {}; const params = { au, av, + vd: videoData, vr: isVideoBid, ms: multiSizes, aw: size[0], ah: size[1], tf: 0, + sc: bid.schain || {}, + f: ensureFloor(getFloor(bid)) }; if (typeof tid === 'string' && tid.length > 0) { @@ -143,52 +178,6 @@ function decorateADM(bid) { return bid.adm + impressions; } -function transformXmlSimple(bid) { - const pixels = [] - _each([bid.nurl].concat(bid.ext != null && bid.ext.himp != null ? bid.ext.himp : []), (pixel) => { - if (pixel != null) { - pixels.push(``) - } - }); - // find the current "Impression" here & slice ours in - const impressionIndex = bid.adm.indexOf(' url != null); - - _each(pixels, (pxl) => { - const imagePixel = doc.createElement('Impression'); - const cdata = doc.createCDATASection(pxl); - imagePixel.appendChild(cdata); - root.appendChild(imagePixel); - }); - - const dtdMatch = xmlDTDRxp.exec(bid.adm); - return (dtdMatch != null ? dtdMatch[0] : generateDTD(doc)) + getOuterHTML(doc.documentElement); -} - function resolveSize(bid, request, bidId) { if (bid.w != null && bid.w > 1 && bid.h != null && bid.h > 1) { return [bid.w, bid.h]; @@ -212,14 +201,16 @@ function values(source) { }); } +const isTrue = (boolValue) => + boolValue === true || boolValue === 1 || boolValue === 'true'; + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid(bid) { return nullOrType(deepAccess(bid, 'params.endpoint', null), 'string') && - nullOrType(deepAccess(bid, 'params.tagId', null), 'string') && - nullOrType(deepAccess(bid, 'params.testMode', null), 'boolean'); + nullOrType(deepAccess(bid, 'params.tagId', null), 'string') }, buildRequests(bidRequests, bidderRequest) { @@ -239,8 +230,9 @@ export const spec = { brc: fbid.bidderRequestsCount || 0, bwc: fbid.bidderWinsCount || 0, trc: fbid.bidRequestsCount || 0, - tm: testMode, + tm: isTrue(testMode), V: '$prebid.version$', + vg: '$$PREBID_GLOBAL$$', i: (testMode && tagId != null) ? tagId : getID(loc), l: {}, f: 0.01, @@ -259,7 +251,8 @@ export const spec = { d: '', m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, - fpd: config.getLegacyFpd(config.getConfig('ortb2')), + fpd2: config.getConfig('ortb2'), + tmax: config.getConfig('bidderTimeout'), eids: values(bidRequests.reduce((all, bid) => { // we only want unique ones in here if (bid == null || bid.userIdAsEids == null) { @@ -306,7 +299,6 @@ export const spec = { }, interpretResponse(serverResponse, request) { - // validate the body/response const response = serverResponse.body; if (response == null || typeof response === 'string') { return []; @@ -320,13 +312,14 @@ export const spec = { return flatMap(response.r[bidID], (siteBid) => siteBid.b.map((bid) => { const mediaType = getMediaType(bid); - // let ad = null; - let ad = mediaType === BANNER ? decorateADM(bid) : decorateVideoADM(bid); + const ad = mediaType === BANNER ? decorateADM(bid) : bid.adm; + if (ad == null) { return null; } const size = resolveSize(bid, request.data, bidID); + const defaultExpiration = mediaType === BANNER ? 240 : 300; return ({ requestId: bidID, @@ -341,7 +334,7 @@ export const spec = { advertiserDomains: bid.adomain, mediaType, }, - ttl: mediaType === VIDEO ? 90 : 70 + ttl: typeof bid.exp === 'number' ? bid.exp : defaultExpiration, }); })).filter((possibleBid) => possibleBid != null); }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 0658fe9f33c..863a8a1d0fc 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -31,20 +31,6 @@ const sampleFPD = { } }; -const legacySampleFPD = { - context: { - keywords: 'sample keywords', - data: { - pageType: 'article' - - } - }, - user: { - gender: 'O', - yob: 1982, - } -}; - const stubConfig = (withStub) => { const stub = sinon.stub(config, 'getConfig').callsFake( (arg) => arg === 'ortb2' ? sampleFPD : null @@ -74,6 +60,15 @@ const sampleBidRequestBase = { endpoint: 'https://httpbin.org/post', }, sizes: [[320, 50]], + getFloor(params) { + if (params.size == null || params.currency == null || params.mediaType == null) { + throw new Error(`getFloor called with incomplete params: ${JSON.stringify(params)}`) + } + return { + floor: 0.5, + currency: 'USD' + } + }, mediaTypes: { [BANNER]: { sizes: [[300, 250]] @@ -85,13 +80,28 @@ const sampleBidRequestBase = { auctionId: utils.getUniqueIdentifierStr(), }; +const schainConfig = { + ver: '1.0', + nodes: [{ + asi: 'greatnetwork.exchange', + sid: '000001', + hp: 1, + rid: 'bid_request_1', + domain: 'publisher.com' + }] +}; + const sampleBidRequestVideo = { ...sampleBidRequestBase, bidId: sampleRequestId + '_video', sizes: [[300, 150]], + schain: schainConfig, mediaTypes: { [VIDEO]: { - sizes: [[360, 250]] + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' } } }; @@ -120,6 +130,7 @@ const sampleServerResponse = { 'h': 600, 'id': '2014691335735134254', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 300 }, @@ -139,6 +150,7 @@ const sampleServerResponse = { 'h': 1, 'id': '7735706981389902829', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 1 }, @@ -160,8 +172,11 @@ describe('AmxBidAdapter', () => { expect(spec.isBidRequestValid({params: { tagId: 'test' }})).to.equal(true) }); - it('testMode is an optional boolean', () => { - expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(false) + it('testMode is an optional truthy value', () => { + expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(true) + expect(spec.isBidRequestValid({params: { testMode: 'true' }})).to.equal(true) + // ignore invalid values (falsy) + expect(spec.isBidRequestValid({params: { testMode: 'non-truthy-invalid-value' }})).to.equal(true) expect(spec.isBidRequestValid({params: { testMode: false }})).to.equal(true) }); @@ -195,6 +210,17 @@ describe('AmxBidAdapter', () => { expect(url).to.equal('https://prebid.a-mo.net/a/c') }); + it('will read the prebid version & global', () => { + const { data: { V: prebidVersion, vg: prebidGlobal } } = spec.buildRequests([{ + ...sampleBidRequestBase, + params: { + testMode: true + } + }], sampleBidderRequest); + expect(prebidVersion).to.equal('$prebid.version$') + expect(prebidGlobal).to.equal('$$PREBID_GLOBAL$$') + }); + it('reads test mode from the first bid request', () => { const { data } = spec.buildRequests([{ ...sampleBidRequestBase, @@ -269,7 +295,7 @@ describe('AmxBidAdapter', () => { it('will forward first-party data', () => { stubConfig(() => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.fpd).to.deep.equal(legacySampleFPD) + expect(data.fpd2).to.deep.equal(sampleFPD) }); }); @@ -315,20 +341,24 @@ describe('AmxBidAdapter', () => { expect(data.m[sampleRequestId]).to.deep.equal({ av: true, au: 'div-gpt-ad-example', + vd: {}, ms: [ [[320, 50]], [[300, 250]], [] ], aw: 300, + sc: {}, ah: 250, tf: 0, + f: 0.5, vr: false }); expect(data.m[sampleRequestId + '_2']).to.deep.equal({ av: true, aw: 300, au: 'div-gpt-ad-example', + sc: {}, ms: [ [[320, 50]], [[300, 250]], @@ -336,7 +366,9 @@ describe('AmxBidAdapter', () => { ], i: 'example', ah: 250, + vd: {}, tf: 0, + f: 0.5, vr: false, }); }); @@ -354,7 +386,15 @@ describe('AmxBidAdapter', () => { av: true, aw: 360, ah: 250, + sc: schainConfig, + vd: { + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' + }, tf: 0, + f: 0.5, vr: true }); }); @@ -401,7 +441,7 @@ describe('AmxBidAdapter', () => { }, width: 300, height: 600, // from the bid itself - ttl: 70, + ttl: 90, ad: sampleDisplayAd( `` + `` @@ -412,20 +452,13 @@ describe('AmxBidAdapter', () => { it('can parse a video ad', () => { const parsed = spec.interpretResponse({ body: sampleServerResponse }, baseRequest) expect(parsed.length).to.equal(2) - - // we should have display, video, display - const xml = parsed[1].vastXml - delete parsed[1].vastXml - - expect(xml).to.have.string(``) - expect(xml).to.have.string(``) - expect(parsed[1]).to.deep.equal({ ...baseBidResponse, meta: { ...baseBidResponse.meta, mediaType: VIDEO, }, + vastXml: sampleVideoAd(''), width: 300, height: 250, ttl: 90, From 926c6d03e280657f5b7044b1ee9a4d64ca1185e2 Mon Sep 17 00:00:00 2001 From: hybrid-ai <58724131+hybrid-ai@users.noreply.github.com> Date: Tue, 16 Mar 2021 07:25:34 +0300 Subject: [PATCH 59/96] VOX Bid adapter, Hybrid Bid adapter: fix global var name to avoid conflicts with astraOne adapter. (#6416) Co-authored-by: Petrov Denis --- modules/hybridBidAdapter.js | 2 +- modules/voxBidAdapter.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index dd55483ef33..e7281086a92 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -164,7 +164,7 @@ function wrapAd(bid, bidData) { parentDocument.style.width = "100%"; } var _content = "${encodeURIComponent(JSON.stringify(bid.inImageContent))}"; - window._ao_ssp.registerInImage(JSON.parse(decodeURIComponent(_content))); + window._hyb_prebid_ssp.registerInImage(JSON.parse(decodeURIComponent(_content))); `; diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index 450f270db31..73df9bb8b9b 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -129,7 +129,7 @@ function wrapInImageBanner(bid, bidData) { var s = document.getElementById("prebidrenderer"); s.onload = function () { var _html = "${encodeURIComponent(JSON.stringify(bid))}"; - window._ao_ssp.registerInImage(JSON.parse(decodeURIComponent(_html))); + window._hyb_prebid_ssp.registerInImage(JSON.parse(decodeURIComponent(_html))); } s.src = "https://st.hybrid.ai/prebidrenderer.js?t=" + Date.now(); if (parent.window.frames[window.name]) { @@ -157,7 +157,7 @@ function wrapBanner(bid, bidData) { var s = document.getElementById("prebidrenderer"); s.onload = function () { var _html = "${encodeURIComponent(JSON.stringify(bid))}"; - window._ao_ssp.registerAds(JSON.parse(decodeURIComponent(_html))); + window._hyb_prebid_ssp.registerAds(JSON.parse(decodeURIComponent(_html))); } s.src = "https://st.hybrid.ai/prebidrenderer.js?t=" + Date.now(); From 97e033dd4cb7f77f9764dd8af67e76284af0ac9f Mon Sep 17 00:00:00 2001 From: Yevhenii Melnyk Date: Tue, 16 Mar 2021 05:31:55 +0100 Subject: [PATCH 60/96] LiveIntent Id System: fix for parsing response twice (#6418) * Don't parse response twice in LiveIntent Id submodule * Update the liveintent module test to have the 204 response --- modules/liveIntentIdSystem.js | 10 +--------- test/spec/modules/liveIntentIdSystem_spec.js | 17 ++++++++++------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index f87d67aae8e..5a955eefa92 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -166,15 +166,7 @@ export const liveIntentIdSubmodule = { const result = function(callback) { liveConnect.resolve( response => { - let responseObj = {}; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - } - callback(responseObj); + callback(response); }, error => { utils.logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 3b4e4b9d9a7..f1de2f3bf93 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -36,7 +36,7 @@ describe('LiveIntentId', function() { resetLiveIntentIdSubmodule(); }); - it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function() { + it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function () { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, @@ -47,12 +47,16 @@ describe('LiveIntentId', function() { submoduleCallback(callBackSpy); let request = server.requests[1]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&gdpr_consent=consentDataString.*/); + const response = { + unifiedId: 'a_unified_id', + segments: [123, 234] + } request.respond( 200, responseHeader, - JSON.stringify({}) + JSON.stringify(response) ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith(response)).to.be.true; }); it('should fire an event when getId', function() { @@ -131,11 +135,10 @@ describe('LiveIntentId', function() { let request = server.requests[1]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899'); request.respond( - 200, - responseHeader, - JSON.stringify({}) + 204, + responseHeader ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith({})).to.be.true; }); it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { From 7eddc4b60d4f5ca750040473116ecab45c96efbb Mon Sep 17 00:00:00 2001 From: Aleksa Trajkovic Date: Tue, 16 Mar 2021 13:54:50 +0100 Subject: [PATCH 61/96] PBJS Core: use mediaType renderer when backupOnly and no bid.renderer (#6419) * use mediaType renderer when backupOnly and no bid.renderer * check if necessary renderer properties are defined --- src/auction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auction.js b/src/auction.js index 217a50be3d6..7005d56827e 100644 --- a/src/auction.js +++ b/src/auction.js @@ -533,9 +533,9 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { var renderer = null; // the renderer for the mediaType takes precendence - if (mediaTypeRenderer && mediaTypeRenderer.url && !(mediaTypeRenderer.backupOnly === true && mediaTypeRenderer.render)) { + if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) { renderer = mediaTypeRenderer; - } else if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly === true && bid.renderer)) { + } else if (adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render && !(adUnitRenderer.backupOnly === true && bid.renderer)) { renderer = adUnitRenderer; } From a8f8b8927a7313280550aacdb7577ef65a99f6e5 Mon Sep 17 00:00:00 2001 From: susyt Date: Tue, 16 Mar 2021 07:56:52 -0700 Subject: [PATCH 62/96] GumGum Bid Adapter: pass bidfloor currency in bidrequest (#6391) * adds support for zone and pubId params * adds support for iriscat field * sets mediatype depending on product id * Update doc for mediaType needed for video products * makes slot and invideo products avail for pubId * updates gumgum doc * lint * adds missing comma in gumgum doc * adds currency in ad request, adds unit test * readd the previous irisid changes * remove the only in testing --- modules/gumgumBidAdapter.js | 24 +++++++++++----------- test/spec/modules/gumgumBidAdapter_spec.js | 4 ++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 2b0f7e03d22..9a01cd21fa4 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -208,25 +208,24 @@ function _getVidParams (attributes) { * @param {Object} bid * @returns {Number} floor */ -function _getFloor (mediaTypes, bidfloor, bid) { +function _getFloor (mediaTypes, staticBidfloor, bid) { const curMediaType = Object.keys(mediaTypes)[0] || 'banner'; - let floor = bidfloor || 0; + const bidFloor = { floor: 0, currency: 'USD' }; if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', + const { currency, floor } = bid.getFloor({ mediaType: curMediaType, size: '*' }); + floor && (bidFloor.floor = floor); + currency && (bidFloor.currency = currency); - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); + if (staticBidfloor && floor && currency === 'USD') { + bidFloor.floor = Math.max(staticBidfloor, parseFloat(floor)); } } - return floor; + return bidFloor; } /** @@ -250,7 +249,7 @@ function buildRequests (validBidRequests, bidderRequest) { transactionId, userId = {} } = bidRequest; - const bidFloor = _getFloor(mediaTypes, params.bidfloor, bidRequest); + const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); let sizes = [1, 1]; let data = {}; @@ -265,8 +264,9 @@ function buildRequests (validBidRequests, bidderRequest) { data.pv = pageViewId; } - if (bidFloor) { - data.fp = bidFloor; + if (floor) { + data.fp = floor; + data.fpc = currency; } if (params.iriscat && typeof params.iriscat === 'string') { diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 0d37c8b1d25..3f5d32bcef7 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -258,6 +258,10 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.fp).to.equal(bidfloor); }); + it('should return a floor currency', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.fpc).to.equal(floorTestData.currency); + }) }); it('sends bid request to ENDPOINT via GET', function () { From 97f71b27170344a817549a2783f32aab650f3465 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 16 Mar 2021 21:37:28 -0700 Subject: [PATCH 63/96] Documentation: fixed a typo and sentence structure (#6421) --- modules/geoedgeRtdProvider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md index e4aa046a97d..5414606612c 100644 --- a/modules/geoedgeRtdProvider.md +++ b/modules/geoedgeRtdProvider.md @@ -4,7 +4,7 @@ Module Name: Geoedge Rtd provider Module Type: Rtd Provider Maintainer: guy.books@geoedge.com -The Geoedge Realtime module let pusblishers to block bad ads such as automatic redirects, malware, offensive creatives and landing pages. +The Geoedge Realtime module lets publishers block bad ads such as automatic redirects, malware, offensive creatives and landing pages. To use this module, you'll need to work with [Geoedge](https://www.geoedge.com/publishers-real-time-protection/) to get an account and cutomer key. ## Integration From ff1890d7c29d0270c0858a08e38c104233779f90 Mon Sep 17 00:00:00 2001 From: Daniel Liebner Date: Wed, 17 Mar 2021 01:03:06 -0400 Subject: [PATCH 64/96] Bid Glass Bid Adapter: pass options in bid request (#6424) * Added bidglass adapter + test * PR Review Updates: - Added formal params to getUserSyncs function definition - getUserSyncs now always returns an array - Improved unit test coverage * PR Review Updates: - Removed unused methods: getUserSyncs, onTimeout, onBidWon, onSetTargeting - Removed getUserSyncs unit test - Removed "dead code" - Removed some unnecessary comments - Fixed usage of parseInt * Bid Glass Bid Adapter: pass options in bid request --- modules/bidglassBidAdapter.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index 6db35f184ca..3162228ce58 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { return window === window.top ? window.location.href : window.parent === window.top ? document.referrer : null; }; let getOrigins = function() { - var ori = ['https://' + window.location.hostname]; + var ori = [window.location.protocol + '//' + window.location.hostname]; if (window.location.ancestorOrigins) { for (var i = 0; i < window.location.ancestorOrigins.length; i++) { @@ -56,7 +56,7 @@ export const spec = { // Derive the parent origin var parts = document.referrer.split('/'); - ori.push('https://' + parts[2]); + ori.push(parts[0] + '//' + parts[2]); if (window.parent !== window.top) { // Additional unknown origins exist @@ -71,11 +71,15 @@ export const spec = { bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); bid.sizes = bid.sizes.filter(size => utils.isArray(size)); - // Stuff to send: [bid id, sizes, adUnitId] + var options = utils.deepClone(bid.params); + delete options.adUnitId; + + // Stuff to send: [bid id, sizes, adUnitId, options] imps.push({ bidId: bid.bidId, sizes: bid.sizes, - adUnitId: utils.getBidIdParameter('adUnitId', bid.params) + adUnitId: utils.getBidIdParameter('adUnitId', bid.params), + options: options }); }); From 5688fee1ab174000021f3ad1e8983193bca697a3 Mon Sep 17 00:00:00 2001 From: Nick Peceniak Date: Wed, 17 Mar 2021 00:27:41 -0600 Subject: [PATCH 65/96] Spotx Bid Adapter: add publisher support for cache.ignoreBidderCacheKey (#6413) * Support ignoreBidderCacheKey in spotxBidAdapter * Update spotxBidAdapter_spec.js * Update spotxBidAdapter_spec.js Co-authored-by: Nick Peceniak --- modules/spotxBidAdapter.js | 16 ++++++++++--- test/spec/modules/spotxBidAdapter_spec.js | 28 +++++++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index c85a836435e..05e4e0ba1ef 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -215,6 +215,12 @@ export const spec = { } }; + // If the publisher asks to ignore the bidder cache key we need to return the full vast xml + // so that it can be cached on the publishes specified server. + if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { + requestPayload['ext']['wrap_response'] = 0; + } + if (utils.getBidIdParameter('number_of_ads', bid.params)) { requestPayload['ext']['number_of_ads'] = utils.getBidIdParameter('number_of_ads', bid.params); } @@ -336,14 +342,18 @@ export const spec = { ttl: 360, netRevenue: true, channel_id: serverResponseBody.id, - cache_key: spotxBid.ext.cache_key, - vastUrl: 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key, - videoCacheKey: spotxBid.ext.cache_key, mediaType: VIDEO, width: spotxBid.w, height: spotxBid.h }; + if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { + bid.vastXml = spotxBid.adm; + } else { + bid.cache_key = spotxBid.ext.cache_key; + bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key + } + bid.meta = bid.meta || {}; if (spotxBid && spotxBid.adomain && spotxBid.adomain.length > 0) { bid.meta.advertiserDomains = spotxBid.adomain; diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index 927599ac986..873914441aa 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -379,6 +379,32 @@ describe('the spotx adapter', function () { expect(request.data.site.page).to.equal('prebid.js'); }); + + it('should set ext.wrap_response to 0 when cache url is set and ignoreBidderCacheKey is true', function() { + var request; + + var origGetConfig = config.getConfig; + sinon.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'cache') { + return { + url: 'prebidCacheLocation', + ignoreBidderCacheKey: true + }; + } + if (key === 'cache.url') { + return 'prebidCacheLocation'; + } + if (key === 'cache.ignoreBidderCacheKey') { + return true; + } + return origGetConfig.apply(config, arguments); + }); + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.ext.wrap_response).to.equal(0); + config.getConfig.restore(); + }); }); describe('interpretResponse', function() { @@ -469,7 +495,6 @@ describe('the spotx adapter', function () { expect(responses[0].requestId).to.equal(123); expect(responses[0].ttl).to.equal(360); expect(responses[0].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); - expect(responses[0].videoCacheKey).to.equal('cache123'); expect(responses[0].width).to.equal(400); expect(responses[1].cache_key).to.equal('cache124'); expect(responses[1].channel_id).to.equal(12345); @@ -483,7 +508,6 @@ describe('the spotx adapter', function () { expect(responses[1].requestId).to.equal(124); expect(responses[1].ttl).to.equal(360); expect(responses[1].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache124'); - expect(responses[1].videoCacheKey).to.equal('cache124'); expect(responses[1].width).to.equal(200); }); }); From 5adeb0a4ddeeff36ebd022f189d1ae7ede6e28a0 Mon Sep 17 00:00:00 2001 From: BizzClick <73241175+BizzClick@users.noreply.github.com> Date: Wed, 17 Mar 2021 12:57:21 +0200 Subject: [PATCH 66/96] update prebid adapter. Add at, ccpa, gdpr and coppa support (#6405) --- modules/bizzclickBidAdapter.js | 19 ++++++++++++ test/spec/modules/bizzclickBidAdapter_spec.js | 29 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 80d2f6b5395..2af9a7afed2 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -81,10 +81,12 @@ export const spec = { let data = { id: bidRequest.bidId, test: config.getConfig('debug') ? 1 : 0, + at: 1, cur: ['USD'], device: { w: winTop.screen.width, h: winTop.screen.height, + dnt: utils.getDNT() ? 1 : 0, language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', }, site: { @@ -94,9 +96,26 @@ export const spec = { source: { tid: bidRequest.transactionId }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} + }, + user: { + ext: {} + }, tmax: bidRequest.timeout, imp: [impObject], }; + if (bidRequest) { + if (bidRequest.gdprConsent && bidRequest.gdprConsent.gdprApplies) { + utils.deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0); + utils.deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString); + } + + if (bidRequest.uspConsent !== undefined) { + utils.deepSetValue(data, 'regs.ext.us_privacy', bidRequest.uspConsent); + } + } bids.push(data) } return { diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js index 39ad4ae39c9..e0698c9eda8 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/bizzclickBidAdapter.js'; +import {config} from 'src/config.js'; const NATIVE_BID_REQUEST = { code: 'native_example', @@ -53,7 +54,11 @@ const BANNER_BID_REQUEST = { accountId: 'accountId' }, timeout: 1000, - + gdprConsent: { + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + gdprApplies: 1, + }, + uspConsent: 'uspConsent' } const bidRequest = { @@ -172,6 +177,22 @@ const NATIVE_BID_RESPONSE = { }; describe('BizzclickAdapter', function() { + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([BANNER_BID_REQUEST]); + expect(serverRequest.data[0].regs.coppa).to.equal(1); + }); + }); + describe('isBidRequestValid', function() { it('should return true when required params found', function () { expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); @@ -225,6 +246,12 @@ describe('BizzclickAdapter', function() { expect(request.method).to.equal('POST'); }); + it('check consent and ccpa string is set properly', function() { + expect(request.data[0].regs.ext.gdpr).to.equal(1); + expect(request.data[0].user.ext.consent).to.equal(BANNER_BID_REQUEST.gdprConsent.consentString); + expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); + }) + it('Returns valid URL', function () { expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); }); From 620acff0811e17bc338f10f36ef97f0d30aedad3 Mon Sep 17 00:00:00 2001 From: pro-nsk <32703851+pro-nsk@users.noreply.github.com> Date: Thu, 18 Mar 2021 00:25:10 +0700 Subject: [PATCH 67/96] Qwarry Bid Adapter: add referer detection (#6412) * qwarry bid adapter * formatting fixes * fix tests for qwarry * qwarry bid adapter * add header for qwarry bid adapter * bid requests fix * fix tests * response fix * fix tests for Qwarry bid adapter * add pos parameter to qwarry bid adapter * qwarryBidAdapter onBidWon hotfix * Change bidder endpoint url for Qwarry adapter * add referer JS detection * use bidderRequest.refererInfo * fix tests Co-authored-by: Artem Kostritsa Co-authored-by: Alexander Kascheev --- modules/qwarryBidAdapter.js | 2 +- test/spec/modules/qwarryBidAdapter_spec.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index 7cb83520979..5ff48ec53f6 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -27,7 +27,7 @@ export const spec = { return { method: 'POST', url: ENDPOINT, - data: { requestId: bidderRequest.bidderRequestId, bids }, + data: { requestId: bidderRequest.bidderRequestId, bids, referer: bidderRequest.refererInfo.referer }, options: { contentType: 'application/json', customHeaders: { diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 91e3cf4bfdf..bc776f7ebe7 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -70,11 +70,12 @@ describe('qwarryBidAdapter', function () { describe('buildRequests', function () { let bidRequests = [REQUEST] - const bidderRequest = spec.buildRequests(bidRequests, { bidderRequestId: '123' }) + const bidderRequest = spec.buildRequests(bidRequests, { bidderRequestId: '123', refererInfo: { referer: 'http://test.com/path.html' } }) it('sends bid request to ENDPOINT via POST', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') + expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') From 69872efeff43f0e0f2e4874b4cfc83b95f3e553a Mon Sep 17 00:00:00 2001 From: SmartyAdsSSP <41569976+SmartyAdsSSP@users.noreply.github.com> Date: Wed, 17 Mar 2021 19:35:58 +0200 Subject: [PATCH 68/96] Smartyads Bid Adapter: add coppa field from config (#6402) * update adapter. Add coppa field from config * move stubs and restores for coppa tests --- modules/smartyadsBidAdapter.js | 2 ++ test/spec/modules/smartyadsBidAdapter_spec.js | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index ff60d08e48b..610617155ed 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,5 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; import * as utils from '../src/utils.js'; const BIDDER_CODE = 'smartyads'; @@ -49,6 +50,7 @@ export const spec = { 'secure': 1, 'host': location.host, 'page': location.pathname, + 'coppa': config.getConfig('coppa') === true ? 1 : 0, 'placements': placements }; request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 2780e88255d..8804050134a 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from '../../../modules/smartyadsBidAdapter.js'; +import { config } from '../../../src/config.js'; describe('SmartyadsAdapter', function () { let bid = { @@ -38,9 +39,10 @@ describe('SmartyadsAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); + expect(data.coppa).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); @@ -57,6 +59,23 @@ describe('SmartyadsAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); + + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([bid]); + expect(serverRequest.data.coppa).to.equal(1); + }); + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { From d3bf135988c69b32c3808ecd42e29470807680c1 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:15:24 -0400 Subject: [PATCH 69/96] PBS Bid Adapter: Fpd2.0 bug fix for first party data issue (#6428) * Bug fix for PBS data from FPD2.0 update: Merging request.site and request.user with site and user object in FPD. --- modules/prebidServerBidAdapter/index.js | 4 ++-- test/spec/modules/prebidServerBidAdapter_spec.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 088b5430f46..d3bbb347720 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -769,10 +769,10 @@ const OPEN_RTB_PROTOCOL = { const commonFpd = getConfig('ortb2') || {}; if (commonFpd.site) { - utils.deepSetValue(request, 'site', commonFpd.site); + utils.mergeDeep(request, {site: commonFpd.site}); } if (commonFpd.user) { - utils.deepSetValue(request, 'user', commonFpd.user); + utils.mergeDeep(request, {user: commonFpd.user}); } addBidderFirstPartyDataToRequest(request); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index babee7e10d7..45d96b96d66 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1631,13 +1631,14 @@ describe('S2S Adapter', function () { } } })); + const commonContextExpected = utils.mergeDeep({'page': 'http://mytestpage.com', 'publisher': {'id': '1'}}, commonContext); config.setConfig({ fpd: { context: commonContext, user: commonUser } }); config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user } } }); adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); - expect(parsedRequestBody.site).to.deep.equal(commonContext); + expect(parsedRequestBody.site).to.deep.equal(commonContextExpected); expect(parsedRequestBody.user).to.deep.equal(commonUser); }); From f7ac0bacdaef911cbdf8d6b82b6428ae9f60a536 Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 17 Mar 2021 15:26:26 -0400 Subject: [PATCH 70/96] Prebid 4.31.0 Release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7943e00f3c6..5af93962114 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.31.0-pre", + "version": "4.31.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 0d1a30eef9aad234ae828aef94b7969c81a15aed Mon Sep 17 00:00:00 2001 From: Eric Harper Date: Wed, 17 Mar 2021 15:46:06 -0400 Subject: [PATCH 71/96] Increment pre version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5af93962114..ed13b6ae7da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.31.0", + "version": "4.32.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 41041790be031a0ba1c957fed1b0a12cbc995725 Mon Sep 17 00:00:00 2001 From: Junus Date: Thu, 18 Mar 2021 08:19:43 +0600 Subject: [PATCH 72/96] a4g Bid Adapter: delete adid and use crid if it exists (#6409) * Deleted adid * set crid if it's exist and added unit tests --- modules/a4gBidAdapter.js | 3 +- test/spec/modules/a4gBidAdapter_spec.js | 50 ++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js index b7d8722e9f9..1961dba1f10 100644 --- a/modules/a4gBidAdapter.js +++ b/modules/a4gBidAdapter.js @@ -70,8 +70,7 @@ export const spec = { if (response.cpm > 0) { const bidResponse = { requestId: response.id, - creativeId: response.id, - adId: response.id, + creativeId: response.crid || response.id, cpm: response.cpm, width: response.width, height: response.height, diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js index 3dccbb28426..cb05fa62ab6 100644 --- a/test/spec/modules/a4gBidAdapter_spec.js +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/a4gBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('a4gAdapterTests', function () { describe('bidRequestValidity', function () { @@ -139,15 +140,54 @@ describe('a4gAdapterTests', function () { const bidResponse = { body: [{ - 'id': 'div-gpt-ad-1460505748561-0', + 'id': '51ef8751f9aead', 'ad': 'test ad', 'width': 320, 'height': 250, - 'cpm': 5.2 + 'cpm': 5.2, + 'crid': '111' }], headers: {} }; + it('should get correct bid response for banner ad', function () { + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '111', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set creativeId to default value if not provided', function () { + const bidResponseWithoutCrid = utils.deepClone(bidResponse); + delete bidResponseWithoutCrid.body[0].crid; + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '51ef8751f9aead', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponseWithoutCrid, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }) + it('required keys', function () { const result = spec.interpretResponse(bidResponse, bidRequest); @@ -169,5 +209,11 @@ describe('a4gAdapterTests', function () { expect(requiredKeys.indexOf(key) !== -1).to.equal(true); }); }) + + it('adId should not be equal to requestId', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].requestId).to.not.equal(result[0].adId); + }) }); }); From c58c6090a172d59e70dad7876614f96c7d1c6477 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Thu, 18 Mar 2021 07:47:26 -0500 Subject: [PATCH 73/96] Mediawallah ID System: add openlink userId submodule (#5921) * My first commit * Removed unnecessary await operation * Bug fixes and compliance fixes * Fixing some formatting and naming * Updating code based on automated feedback. * Parking refactoring change for team review * update mwOpenLink module * remove .git 2 folder * Trying to force a change * update the PR comments * applying the changes * update submodules.json and userId.md * fix typo of module names * update module decode function and test code * update test codes * apply the suggestions from Prebid * fix count of modules. Co-authored-by: Eric Brown Co-authored-by: hanna Co-authored-by: hannapanova190119 <71532550+hannapanova190119@users.noreply.github.com> --- modules/.submodules.json | 1 + modules/mwOpenLinkIdSystem.js | 142 +++++++++++++++++++ modules/mwOpenLinkIdSystem.md | 43 ++++++ modules/userId/eids.js | 6 +- modules/userId/eids.md | 7 + modules/userId/userId.md | 7 + test/spec/modules/mwOpenLinkIdSystem_spec.js | 20 +++ test/spec/modules/userId_spec.js | 71 +++++++--- 8 files changed, 280 insertions(+), 17 deletions(-) create mode 100644 modules/mwOpenLinkIdSystem.js create mode 100644 modules/mwOpenLinkIdSystem.md create mode 100644 test/spec/modules/mwOpenLinkIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index a7cf1f54426..0f62627822a 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -20,6 +20,7 @@ "fabrickIdSystem", "verizonMediaIdSystem", "pubProvidedIdSystem", + "mwOpenLinkIdSystem", "tapadIdSystem", "novatiqIdSystem" ], diff --git a/modules/mwOpenLinkIdSystem.js b/modules/mwOpenLinkIdSystem.js new file mode 100644 index 00000000000..b2381836d5d --- /dev/null +++ b/modules/mwOpenLinkIdSystem.js @@ -0,0 +1,142 @@ +/** + * This module adds MediaWallah OpenLink to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/mwOpenLinkIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const openLinkID = { + name: 'mwol', + cookie_expiration: (86400 * 1000 * 365 * 1) // 1 year +} + +const storage = getStorageManager(); + +function getExpirationDate() { + return (new Date(utils.timestamp() + openLinkID.cookie_expiration)).toGMTString(); +} + +function isValidConfig(configParams) { + if (!configParams) { + utils.logError('User ID - mwOlId submodule requires configParams'); + return false; + } + if (!configParams.accountId) { + utils.logError('User ID - mwOlId submodule requires accountId to be defined'); + return false; + } + if (!configParams.partnerId) { + utils.logError('User ID - mwOlId submodule requires partnerId to be defined'); + return false; + } + return true; +} + +function deserializeMwOlId(mwOlIdStr) { + const mwOlId = {}; + const mwOlIdArr = mwOlIdStr.split(','); + + mwOlIdArr.forEach(function(value) { + const pair = value.split(':'); + // unpack a value of 1 as true + mwOlId[pair[0]] = +pair[1] === 1 ? true : pair[1]; + }); + + return mwOlId; +} + +function serializeMwOlId(mwOlId) { + let components = []; + + if (mwOlId.eid) { + components.push('eid:' + mwOlId.eid); + } + if (mwOlId.ibaOptout) { + components.push('ibaOptout:1'); + } + if (mwOlId.ccpaOptout) { + components.push('ccpaOptout:1'); + } + + return components.join(','); +} + +function readCookie(name) { + if (!name) name = openLinkID.name; + const mwOlIdStr = storage.getCookie(name); + if (mwOlIdStr) { + return deserializeMwOlId(decodeURIComponent(mwOlIdStr)); + } + return null; +} + +function writeCookie(mwOlId) { + if (mwOlId) { + const mwOlIdStr = encodeURIComponent(serializeMwOlId(mwOlId)); + storage.setCookie(openLinkID.name, mwOlIdStr, getExpirationDate(), 'lax'); + } +} + +function register(configParams, olid) { + const { accountId, partnerId, uid } = configParams; + const url = 'https://ol.mediawallahscript.com/?account_id=' + accountId + + '&partner_id=' + partnerId + + '&uid=' + uid + + '&olid=' + olid + + '&cb=' + Math.random() + ; + ajax(url); +} + +function setID(configParams) { + if (!isValidConfig(configParams)) return undefined; + const mwOlId = readCookie(); + const newMwOlId = mwOlId ? utils.deepClone(mwOlId) : {eid: utils.generateUUID()}; + writeCookie(newMwOlId); + register(configParams, newMwOlId.eid); + return { + id: newMwOlId + }; +}; + +/* End MW */ + +export { writeCookie }; + +/** @type {Submodule} */ +export const mwOpenLinkIdSubModule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'mwOpenLinkId', + /** + * decode the stored id value for passing to bid requests + * @function + * @param {MwOlId} mwOlId + * @return {(Object|undefined} + */ + decode(mwOlId) { + const id = mwOlId && utils.isPlainObject(mwOlId) ? mwOlId.eid : undefined; + return id ? { 'mwOpenLinkId': id } : undefined; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} [submoduleParams] + * @returns {id:MwOlId | undefined} + */ + getId(submoduleConfig) { + const submoduleConfigParams = (submoduleConfig && submoduleConfig.params) || {}; + if (!isValidConfig(submoduleConfigParams)) return undefined; + return setID(submoduleConfigParams); + } +}; + +submodule('userId', mwOpenLinkIdSubModule); diff --git a/modules/mwOpenLinkIdSystem.md b/modules/mwOpenLinkIdSystem.md new file mode 100644 index 00000000000..f55913f2983 --- /dev/null +++ b/modules/mwOpenLinkIdSystem.md @@ -0,0 +1,43 @@ +## MediaWallah openLink User ID Submodule + +OpenLink ID User ID Module generates a UUID that can be utilized to improve user matching. This module enables timely synchronization which handles MediaWallah optout. You must have a pre-existing relationship with MediaWallah prior to integrating. + +### Building Prebid with openLink Id Support +Your Prebid build must include the modules for both **userId** and **mwOpenLinkId** submodule. Follow the build instructions for Prebid as +explained in the top level README.md file of the Prebid source tree. + +ex: $ gulp build --modules=userId,mwOpenLinkIdSystem + +##$ MediaWallah openLink ID Example Configuration + +When the module is included, it's automatically enabled and saves an id to both cookie with an expiration time of 1 year. + +### Prebid Params + +Individual params may be set for the MediaWallah openLink User ID Submodule. At least accountId and partnerId must be set in the params. + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'mwOpenLinkId', + params: { + accountId: '1000', + partnerId: '1001', + uid: 'u-123xyz' + } + }] + } +}); +``` + +### Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the MediaWallah OpenLink ID User ID Module integration. + +| Params under usersync.userIds[]| Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `'mwOpenLinkId'` | +| params | Required | Object | Details for mwOLID syncing. | | +| params.accountId | Required | String | The MediaWallah assigned Account Id | `1000` | +| params.partnerId | Required | String | The MediaWallah assign Partner Id | `1001` | +| params.uid | Optional | String | Your unique Id for the user or browser. Used for matching | `u-123xyz` | \ No newline at end of file diff --git a/modules/userId/eids.js b/modules/userId/eids.js index a7e5eaf6061..9b26eff2ebf 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -169,11 +169,15 @@ const USER_IDS_CONFIG = { source: 'neustar.biz', atype: 1 }, + // MediaWallah OpenLink + 'mwOpenLinkId': { + source: 'mediawallahscript.com', + atype: 1 + }, 'tapadId': { source: 'tapad.com', atype: 1 }, - // Novatiq Snowflake 'novatiq': { getValue: function(data) { diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 404066d53e4..bd14ea0b9e7 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -142,6 +142,13 @@ userIdAsEids = [ atype: 1 }] }, + { + source: 'mediawallahscript.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { source: 'tapad.com', uids: [{ diff --git a/modules/userId/userId.md b/modules/userId/userId.md index a7f98fb39a0..828b2993e40 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -77,6 +77,13 @@ pbjs.setConfig({ name: '_criteoId', expires: 1 } + }, { + name: 'mwOpenLinkId', + params: { + accountId: 0000, + partnerId: 0000, + uid: '12345xyz' + } }], syncDelay: 5000, auctionDelay: 1000 diff --git a/test/spec/modules/mwOpenLinkIdSystem_spec.js b/test/spec/modules/mwOpenLinkIdSystem_spec.js new file mode 100644 index 00000000000..fb082b8cd16 --- /dev/null +++ b/test/spec/modules/mwOpenLinkIdSystem_spec.js @@ -0,0 +1,20 @@ +import { writeCookie, mwOpenLinkIdSubModule } from 'modules/mwOpenLinkIdSystem.js'; + +const P_CONFIG_MOCK = { + params: { + accountId: '123', + partnerId: '123' + } +}; + +describe('mwOpenLinkId module', function () { + beforeEach(function() { + writeCookie(''); + }); + + it('getId() should return a MediaWallah openLink Id when the MediaWallah openLink first party cookie exists', function () { + writeCookie({eid: 'XX-YY-ZZ-123'}); + const id = mwOpenLinkIdSubModule.getId(P_CONFIG_MOCK); + expect(id).to.be.deep.equal({id: {eid: 'XX-YY-ZZ-123'}}); + }); +}); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 8a7be41b2b5..95279380232 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -38,6 +38,7 @@ import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {haloIdSubmodule} from 'modules/haloIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; +import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; import {getPrebidInternal} from 'src/utils.js'; @@ -458,7 +459,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -466,14 +467,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -484,7 +485,7 @@ describe('User ID', function () { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -501,15 +502,15 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 14 configurations should result in 14 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + it('config with 15 configurations should result in 15 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -549,17 +550,19 @@ describe('User ID', function () { name: 'zeotapIdPlus' }, { name: 'criteo' + }, { + name: 'mwOpenLinkId' }, { name: 'tapadId', storage: {name: 'tapad_id', type: 'cookie'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 14 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 15 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -574,7 +577,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -589,7 +592,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -1622,7 +1625,27 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId and Criteo have data to pass', function (done) { + it('test hook from mwOpenLinkId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([mwOpenLinkIdSubModule]); + init(config); + config.setConfig(getConfigMock(['mwOpenLinkId', 'mwol', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + }); + }); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId, Criteo and mwOpenLinkId have data to pass', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1637,8 +1660,9 @@ describe('User ID', function () { }), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1651,6 +1675,7 @@ describe('User ID', function () { ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'])); requestBidsHook(function () { @@ -1691,8 +1716,11 @@ describe('User ID', function () { // also check that criteo id was copied to bid expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); + // also check that mwOpenLink id was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); - expect(bid.userIdAsEids.length).to.equal(11); + expect(bid.userIdAsEids.length).to.equal(12); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1706,11 +1734,12 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId and haloId have their modules added before and after init', function (done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId, haloId and mwOpenLinkId have their modules added before and after init', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1725,6 +1754,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); @@ -1744,6 +1774,7 @@ describe('User ID', function () { attachIdSystem(zeotapIdPlusSubmodule); attachIdSystem(haloIdSubmodule); attachIdSystem(criteoIdSubmodule); + attachIdSystem(mwOpenLinkIdSubModule); attachIdSystem(tapadIdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], @@ -1757,6 +1788,7 @@ describe('User ID', function () { ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'])); requestBidsHook(function () { @@ -1800,7 +1832,11 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); - expect(bid.userIdAsEids.length).to.equal(11); + // also check that mwOpenLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + + expect(bid.userIdAsEids.length).to.equal(12); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1814,6 +1850,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -2129,6 +2166,7 @@ describe('User ID', function () { // also check that haloId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.haloId'); expect(bid.userId.haloId).to.equal('testHaloId'); + expect(bid.userIdAsEids.length).to.equal(10); }); }); @@ -2143,6 +2181,7 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); From 25e857fdf9af92bec23f1e493e71a2d497040c11 Mon Sep 17 00:00:00 2001 From: Skylinar <53079123+Skylinar@users.noreply.github.com> Date: Thu, 18 Mar 2021 17:06:19 +0100 Subject: [PATCH 74/96] Documentation: Adjust desired bitrate examples smartx adapter (#6438) --- modules/smartxBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index b25ce68bb6e..223e51763b9 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -40,7 +40,7 @@ This adapter requires setup and approval from the smartclip team. skipOffset: 0, startOpen: true, endingScreen: true, - desiredBitrate: 1600, + desiredBitrate: 800, }, } }], @@ -75,7 +75,7 @@ This adapter requires setup and approval from the smartclip team. skipOffset: 0, startOpen: true, endingScreen: true, - desiredBitrate: 1600, + desiredBitrate: 800, }, user: { data: [{ From 35ec0060cff8ed93bdaee774edcaa920ef3f7bad Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Thu, 18 Mar 2021 19:14:28 +0100 Subject: [PATCH 75/96] Remove adId (autogenerated by Prebid) (#6441) --- modules/improvedigitalBidAdapter.js | 3 +-- test/spec/modules/improvedigitalBidAdapter_spec.js | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 0432adf2b2d..dc0911ff5da 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -11,7 +11,7 @@ const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; export const spec = { - version: '7.2.0', + version: '7.3.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -126,7 +126,6 @@ export const spec = { } // Common properties - bid.adId = bidObject.id; bid.cpm = parseFloat(bidObject.price); bid.creativeId = bidObject.crid; bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 4d0da695d95..f34a75ef8f3 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -750,7 +750,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBid = [ { 'ad': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', @@ -767,7 +766,6 @@ describe('Improve Digital Adapter Tests', function () { expectedBid[0], { 'ad': '', - 'adId': '1234', 'creativeId': '422033', 'cpm': 1.23, 'currency': 'USD', @@ -783,7 +781,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidNative = [ { mediaType: 'native', - adId: '33e9500b21129f', creativeId: '422031', cpm: 1.45888594164456, currency: 'USD', @@ -832,7 +829,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidInstreamVideo = [ { 'vastXml': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', From 30bacedadf3c45c10260c9dee147b1f7172763a7 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Fri, 19 Mar 2021 02:00:32 +0100 Subject: [PATCH 76/96] RichAudience Bid Adapter: add render video in banner (#6392) --- modules/richaudienceBidAdapter.js | 62 +++++++----- modules/richaudienceBidAdapter.md | 2 +- .../modules/richaudienceBidAdapter_spec.js | 99 ++++++++++++++++--- 3 files changed, 121 insertions(+), 42 deletions(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 07de3e40594..37a9554e9a4 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -14,20 +14,20 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO], /*** - * Determines whether or not the given bid request is valid - * - * @param {bidRequest} bid The bid params to validate. - * @returns {boolean} True if this is a valid bid, and false otherwise - */ + * Determines whether or not the given bid request is valid + * + * @param {bidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise + */ isBidRequestValid: function (bid) { return !!(bid.params && bid.params.pid && bid.params.supplyType); }, /*** - * Build a server request from the list of valid BidRequests - * @param {validBidRequests} is an array of the valid bids - * @param {bidderRequest} bidder request object - * @returns {ServerRequest} Info describing the request to the server - */ + * Build a server request from the list of valid BidRequests + * @param {validBidRequests} is an array of the valid bids + * @param {bidderRequest} bidder request object + * @returns {ServerRequest} Info describing the request to the server + */ buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bid => { var payload = { @@ -77,11 +77,11 @@ export const spec = { }); }, /*** - * Read the response from the server and build a list of bids - * @param {serverResponse} Response from the server. - * @param {bidRequest} Bid request object - * @returns {bidResponses} Array of bids which were nested inside the server - */ + * Read the response from the server and build a list of bids + * @param {serverResponse} Response from the server. + * @param {bidRequest} Bid request object + * @returns {bidResponses} Array of bids which were nested inside the server + */ interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; // try catch @@ -103,10 +103,16 @@ export const spec = { if (response.media_type === 'video') { bidResponse.vastXml = response.vastXML; try { - if (JSON.parse(bidRequest.data).videoData.format == 'outstream') { - bidResponse.renderer = Renderer.install({ - url: 'https://cdn3.richaudience.com/prebidVideo/player.js' - }); + if (bidResponse.vastXml != null) { + if (JSON.parse(bidRequest.data).videoData.format == 'outstream' || JSON.parse(bidRequest.data).videoData.format == 'banner') { + bidResponse.renderer = Renderer.install({ + id: bidRequest.bidId, + adunitcode: bidRequest.tagId, + loaded: false, + config: response.media_type, + url: 'https://cdn3.richaudience.com/prebidVideo/player.js' + }); + } bidResponse.renderer.setRender(renderer); } } catch (e) { @@ -121,13 +127,13 @@ export const spec = { return bidResponses }, /*** - * User Syncs - * - * @param {syncOptions} Publisher prebid configuration - * @param {serverResponses} Response from the server - * @param {gdprConsent} GPDR consent object - * @returns {Array} - */ + * User Syncs + * + * @param {syncOptions} Publisher prebid configuration + * @param {serverResponses} Response from the server + * @param {gdprConsent} GPDR consent object + * @returns {Array} + */ getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { const syncs = []; @@ -197,6 +203,10 @@ function raiGetVideoInfo(bid) { playerSize: bid.mediaTypes.video.playerSize, mimes: bid.mediaTypes.video.mimes }; + } else { + videoData = { + format: 'banner' + } } return videoData; } diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index fbf59a0208a..f888117b166 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -39,7 +39,7 @@ Please reach out to your account manager for more information. "pid":"ADb1f40rmo", "supplyType":"site", "bidfloor":0.40, - "keywords": "bici=scott;coche=audi;coche=mercedes;" + "keywords": "key1=value1;key2=value2;key3=value3;" } }] } diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 3b9f07b6efc..5deb2463523 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -20,7 +20,7 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.5, pid: 'ADb1f40rmi', supplyType: 'site', - keywords: 'coche=mercedes;coche=audi' + keywords: 'key1=value1;key2=value2' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -75,26 +75,19 @@ describe('Richaudience adapter tests', function () { user: {} }]; - var DEFAULT_PARAMS_VIDEO_OUT_PARAMS = [{ + var DEFAULT_PARAMS_BANNER_OUTSTREAM = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'] + banner: { + sizes: [[300, 250], [600, 300]] } }, bidder: 'richaudience', params: { bidfloor: 0.5, pid: 'ADb1f40rmi', - supplyType: 'site', - player: { - init: 'close', - end: 'close', - skin: 'dark' - } + supplyType: 'site' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -242,7 +235,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('numIframes').and.to.equal(0); expect(typeof requestContent.scr_rsl === 'string') expect(typeof requestContent.cpuc === 'number') - expect(requestContent).to.have.property('kws').and.to.equal('coche=mercedes;coche=audi'); + expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) it('Verify build request to prebid video inestream', function() { @@ -262,8 +255,6 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('640'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('480'); }) it('Verify build request to prebid video outstream', function() { @@ -281,6 +272,7 @@ describe('Richaudience adapter tests', function () { expect(request[0]).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); }) @@ -623,9 +615,19 @@ describe('Richaudience adapter tests', function () { }); const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); expect(bid.mediaType).to.equal('video'); expect(bid.vastXml).to.equal(''); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); }); it('no banner media response outstream', function () { @@ -640,6 +642,35 @@ describe('Richaudience adapter tests', function () { } }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('banner media and response VAST', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_BANNER_OUTSTREAM, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); const bid = bids[0]; expect(bid.mediaType).to.equal('video'); @@ -656,6 +687,16 @@ describe('Richaudience adapter tests', function () { expect(spec.aliases[0]).to.equal('ra'); }); + it('Verifies bidder gvlid', function () { + expect(spec.gvlid).to.equal(108); + }); + + it('Verifies bidder supportedMediaTypes', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('video'); + }); + it('Verifies if bid request is valid', function () { expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); @@ -717,6 +758,34 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.50, } })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + bidfloor: 0.50, + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); }); it('Verifies user syncs iframe', function () { From 78605f3010ea1664cdc45e90bf740a190c71976f Mon Sep 17 00:00:00 2001 From: SKOCHERI <37454420+SKOCHERI@users.noreply.github.com> Date: Fri, 19 Mar 2021 02:58:20 -0700 Subject: [PATCH 77/96] Integration Example: ID import library example (#6434) * ID Import Library example * Fixing review comments Co-authored-by: skocheri --- .../gpt/idImportLibrary_example.html | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 integrationExamples/gpt/idImportLibrary_example.html diff --git a/integrationExamples/gpt/idImportLibrary_example.html b/integrationExamples/gpt/idImportLibrary_example.html new file mode 100644 index 00000000000..da1581d1c89 --- /dev/null +++ b/integrationExamples/gpt/idImportLibrary_example.html @@ -0,0 +1,351 @@ + + + + + + + + + + + + + +Prebid + +

ID Import Library Example

+

Steps before logging in:

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

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

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